<commit_before>'use strict';

const cluster = require('cluster');
const DgramSocket = require('dgram').Socket;
const { EventEmitter } = require('events');
const { Socket, Server } = require('net');
const ChildProcess = require('child_process').ChildProcess;

const bunyan = require('bunyan');

const Status = require('./status');
const removeValue = require('./remove-value');
const prependListener = require('./prepend-listener');

let isInstantiated = false;

// Server status listener
const serverStatusEvent = new EventEmitter();
serverStatusEvent.fetch = () => {
    if (!cluster.isMaster) process.send({ event: 'serverStatus' });
};

function trackResponseEnd(request, response) {
    function changeStatus(state) {
        if (request.flora.state === 'aborted') {
            // remove connection (parent) from status as it's already closed:
            request.flora.status.parent.close();
        } else {
            request.flora.state = state;
        }
    }

    // inject our "end" method to track "sending" status:
    response._floraOrigEnd = response.end;
    response.end = (...args) => {
        changeStatus('sending');
        return response._floraOrigEnd(...args);
    };

    // track if someone pipes to us (no "end" call is guaranteed then):
    response.once('pipe', () => changeStatus('piping'));
}

/**
 * Cluster worker.
 */
class Worker extends EventEmitter {
    /**
     * Constructor.
     *
     * * Options:
     * - shutdownTimeout: worker shutdown timeout (default: 30000)
     * - log: bunyan instance for logging (default: new bunyan logger)
     * - httpServer: http.Server instance (optional)
     *
     * @param {Object} [opts]
     */
    constructor(opts = {}) {
        super();

        this.config = opts;

        this.config.shutdownTimeout = this.config.shutdownTimeout || 30000;

        if (this.config.log) {
            this.log = opts.log.child({ component: 'worker' });
        } else {
            this.log = bunyan.createLogger({ name: 'flora-cluster', component: 'worker' });
        }

        this.httpServer = null;
        this.isRunning = true;
        this.activeConnections = [];

        this.status = new Status();
        this.status.set('state', 'initializing');
        this.status.set('startTime', new Date());
        this.status.set('pid', process.pid);
        this.status.set('generation', null); // just for nicer order - set later in master
        this.status.onStatus(() => {
            this.status.set('state', this.isRunning ? 'running' : 'shutdown');
            this.status.set('memoryUsage', process.memoryUsage());
        });

        if (isInstantiated) this.log.warn('Worker should be instantiated only once per process');
        isInstantiated = true;

        process.on('message', (message) => {
            if (!message.event) return;
            this.log.trace(message, 'message from master');
            process.emit(`flora::${message.event}`, message);
        });

        process.on('flora::shutdown', this.shutdown.bind(this));

        process.on('flora::status', this.sendStatus.bind(this));

        process.on('flora::serverStatus', (message) => {
            serverStatusEvent.emit('serverStatus', message.serverStatus);
        });

        process.on('SIGTERM', () => {
            this.log.info('received SIGTERM, exiting immediately');
            process.exit(0);
        });

        process.on('SIGINT', () => {
            this.log.info('received SIGINT, exiting immediately');
            process.exit(0);
        });

        process.on('SIGQUIT', () => {
            this.log.info('received SIGQUIT, shutting down gracefully');
            this.shutdown();
        });

        process.on('uncaughtException', (err) => {
            this.log.error(err, 'uncaught exception, shutting down gracefully');
            process.exitCode = 1;
            this.shutdown();
        });

        process.on('unhandledRejection', (err /* , promise */) => {
            this.log.error(err, 'unhandled rejection, shutting down gracefully');
            process.exitCode = 1;
            this.shutdown();
        });

        if (this.config.httpServer) {
            this.attach(this.config.httpServer);
        }
    }

    /**
     * Tell the master that the worker is ready.
     *
     * This function needs to be called before `startupTimeout` is over, otherwise the worker
     * is assumed to failed to start and thus being killed by the master process.
     */
    ready() {
        if (!cluster.isMaster) {
            this.log.debug('sending "ready" event to master');
            this.send({ event: 'ready' });
        }
    }

    /**
     * Attach to an exising HTTP server.
     *
     * This may be necessary after creating the Worker instance, so we can instantiate the
     * http.Server later.
     *
     * @param {http.Server} server
     */
    attach(server) {
        this.httpServer = server;
        prependListener(this.httpServer, 'request', this.onRequest.bind(this));
        prependListener(this.httpServer, 'connection', this.onConnection.bind(this));
        this.httpServer.on('close', this.onCloseServer.bind(this));
    }

    /**
     * Shutdown the worker
     */
    shutdown() {
        if (!this.isRunning) return;
        this.isRunning = false;

        if (!this.httpServer) {
            if (!cluster.isMaster) cluster.worker.disconnect();
            return;
        }

        try {
            // make sure we close down within given timeout:
            const shutdownTimeout = setTimeout(() => {
                this.logActiveHandles();
                this.log.warn('exit after timeout');
                setTimeout(() => {
                    process.exit(2);
                }, 100); // give async logging a chance before hard exit
            }, this.config.shutdownTimeout);

            // but don't keep the process open just for that:
            shutdownTimeout.unref();

            // notify master that we are shutting down (so new workers can be spawned immediately):
            if (!cluster.isMaster) this.send({ event: 'shutdown' });

            this.emit('close');

            // close keep-alive-connection (and do not wait for FIN ACK from client):
            this.activeConnections.forEach((connection) => {
                if (connection.flora.isIdle) connection.destroy();
            });

            this.sendStatus();
        } catch (e) {
            this.log.error(e, 'error on graceful shutdown');
        }
    }

    logActiveHandles() {
        if (typeof process._getActiveHandles !== 'function') {
            this.log.debug("Can't check for active handles - process._getActiveHandles not available");
            return;
        }

        const whitelist = [];
        if (process.stdout) whitelist.push(process.stdout);
        if (process.stderr) whitelist.push(process.stderr);
        if (cluster.worker && cluster.worker.suicide && process._channel) {
            whitelist.push(process._channel); // cluster IPC pipe
        }

        function dumpObjectProperties(obj) {
            const properties = [];

            do {
                const name = (obj.constructor && obj.constructor.name) || '(Unknown)';
                properties.push(name + '[' + Object.getOwnPropertyNames(obj).join(', ') + ']');
            } while ((obj = Object.getPrototypeOf(obj)) && obj !== Object.prototype);

            return properties.join(' ~ ');
        }

        const handles = process._getActiveHandles(); // internal node interface!

        const handleInfos = [];
        handles.forEach((handle) => {
            let name = typeof handle;
            let description = null;

            if (whitelist.indexOf(handle) !== -1) return;

            if (handle === null) {
                name = 'null-handle';
                description = 'wtf?!';
            } else if (handle && typeof handle === 'object') {
                if (handle.constructor && handle.constructor.name) {
                    name = handle.constructor.name;
                }

                if (handle instanceof Socket) {
                    if (handle.remoteAddress) {
                        description = handle.localAddress + ':' + handle.localPort;
                        description += ' -> ' + handle.remoteAddress + ':' + handle.remotePort;
                    } else {
                        description = dumpObjectProperties(handle);
                    }
                    description += handle.destroyed ? ' (destroyed)' : '';
                } else if (handle instanceof DgramSocket) {
                    name = 'dgram.Socket';
                    description = 'type: ' + handle.type;
                } else if (handle instanceof Server) {
                    const { address, port } = handle.address();
                    description = `${address}:${port}`;
                } else if (handle instanceof ChildProcess) {
                    description = 'PID: ' + handle.pid;
                    description += '; args: ' + (handle.spawnargs ? handle.spawnargs.join(' ') : '-');
                } else {
                    description = dumpObjectProperties(handle);
                }
            }

            handleInfos.push({ name, description });
        });

        handleInfos.forEach((handleInfo) => {
            this.log.warn(
                `Active handle: ${handleInfo.name}` + (handleInfo.description ? ` (${handleInfo.description})` : '')
            );
        });
    }

    onRequest(request, response) {
        request.flora = request.flora || {};
        request.flora.startTime = process.hrtime();
        request.flora.state = 'processing';

        request.connection.flora.isIdle = false;
        request.connection.flora.status.increment('requests');
        request.connection.flora.status.set('idleSince', null);

        const status = request.connection.flora.status.child('request');
        request.flora.status = status;

        status.set('state', null);
        status.set('startTime', new Date());
        status.onStatus(() => {
            status.set('state', request.flora.state);
            status.set('ip', request.ip || (request.connection ? request.connection.remoteAddress : null));
            status.set('method', request.method);
            status.set('url', request.url);
            status.set('httpVersion', request.httpVersion);
        });

        // TODO: Set different timeout for running requests than for idle keep-alive-connections

        // emulate response.on('end') - when finished processing the request:
        trackResponseEnd(request, response);

        response.on('finish', () => {
            request.flora.state = 'finished';
            status.close();
            request.connection.flora.isIdle = true;
            request.connection.flora.status.set('idleSince', new Date());
            if (request.connection.flora.isClosed) {
                request.connection.flora.status.close();
            }

            if (!this.isRunning) {
                // close keep-alive-connection (and do not wait for FIN ACK from clients):
                request.connection.destroy();
            }
        });

        response.on('close', () => {
            if (request.flora.state !== 'processing') {
                status.close();
                if (request.connection.flora.isClosed) {
                    request.connection.flora.status.close();
                }
            }

            request.flora.state = 'aborted';
        });
    }

    onConnection(connection) {
        connection.setNoDelay(true);
        connection.setKeepAlive(true);

        connection.flora = connection.flora || {};
        connection.flora.isIdle = true;

        this.status.increment('connections');

        const status = this.status.addChild('activeConnections');
        connection.flora.status = status;

        status.set('state', 'connected');
        status.set('startTime', new Date());
        status.set('remoteAddress', connection.remoteAddress);
        status.set('remotePort', connection.remotePort);
        status.set('idleSince', new Date());
        status.onStatus(() => {
            status.setIncrement('bytesRead', connection.bytesRead);
            status.setIncrement('bytesWritten', connection.bytesWritten);
        });

        this.activeConnections.push(connection);

        connection.on('close', () => {
            status.setIncrement('bytesRead', connection.bytesRead);
            status.setIncrement('bytesWritten', connection.bytesWritten);
            status.set('state', 'closed');
            if (connection.flora.isIdle) {
                status.close();
            } else {
                connection.flora.isClosed = true;
            }
            removeValue(this.activeConnections, connection);
        });
    }

    onCloseServer() {
        this.sendStatus();
        this.log.info('Closing IPC channel - should exit now');

        // if all references are closed properly, worker exits here,
        // otherwise the kill-timeout from shutdown() should catch it:
        if (!cluster.isMaster) cluster.worker.disconnect();
    }

    sendStatus() {
        if (!cluster.isMaster) this.send({ event: 'status', status: this.status.getStatus() });
    }

    send(message) {
        process.send(message, (err) => {
            if (err) {
                // catch IPC channel errors:
                this.log.error(err, 'error sending IPC message to master');
            }
        });
    }

    /**
     * Retrieve the cluster status
     *
     * @returns {Promise}
     */
    async serverStatus() {
        if (cluster.isMaster) return this.status.getStatus();

        return new Promise((resolve) => {
            serverStatusEvent.fetch();
            serverStatusEvent.once('serverStatus', (status) => resolve(status));
        });
    }
}

module.exports = Worker;
<commit_msg>worker: refactored "state" flag (new "isActive" and "isAborted" flag for request-end-detection)<commit_after>'use strict';

const cluster = require('cluster');
const DgramSocket = require('dgram').Socket;
const { EventEmitter } = require('events');
const { Socket, Server } = require('net');
const ChildProcess = require('child_process').ChildProcess;

const bunyan = require('bunyan');

const Status = require('./status');
const removeValue = require('./remove-value');
const prependListener = require('./prepend-listener');

let isInstantiated = false;

// Server status listener
const serverStatusEvent = new EventEmitter();
serverStatusEvent.fetch = () => {
    if (!cluster.isMaster) process.send({ event: 'serverStatus' });
};

function trackResponseEnd(request, response) {
    function setEndStatus(state) {
        if (request.flora.isAborted) {
            // remove connection (parent) from status as it's already closed:
            request.flora.status.parent.close();
        } else {
            request.flora.isActive = false;
            request.flora.state = state;
        }
    }

    // inject our "end" method to track "sending" status:
    response._floraOrigEnd = response.end;
    response.end = (...args) => {
        setEndStatus('sending');
        return response._floraOrigEnd(...args);
    };

    // track if someone pipes to us (no "end" call is guaranteed then):
    response.once('pipe', () => setEndStatus('piping'));
}

/**
 * Cluster worker.
 */
class Worker extends EventEmitter {
    /**
     * Constructor.
     *
     * * Options:
     * - shutdownTimeout: worker shutdown timeout (default: 30000)
     * - log: bunyan instance for logging (default: new bunyan logger)
     * - httpServer: http.Server instance (optional)
     *
     * @param {Object} [opts]
     */
    constructor(opts = {}) {
        super();

        this.config = opts;

        this.config.shutdownTimeout = this.config.shutdownTimeout || 30000;

        if (this.config.log) {
            this.log = opts.log.child({ component: 'worker' });
        } else {
            this.log = bunyan.createLogger({ name: 'flora-cluster', component: 'worker' });
        }

        this.httpServer = null;
        this.isRunning = true;
        this.activeConnections = [];

        this.status = new Status();
        this.status.set('state', 'initializing');
        this.status.set('startTime', new Date());
        this.status.set('pid', process.pid);
        this.status.set('generation', null); // just for nicer order - set later in master
        this.status.onStatus(() => {
            this.status.set('state', this.isRunning ? 'running' : 'shutdown');
            this.status.set('memoryUsage', process.memoryUsage());
        });

        if (isInstantiated) this.log.warn('Worker should be instantiated only once per process');
        isInstantiated = true;

        process.on('message', (message) => {
            if (!message.event) return;
            this.log.trace(message, 'message from master');
            process.emit(`flora::${message.event}`, message);
        });

        process.on('flora::shutdown', this.shutdown.bind(this));

        process.on('flora::status', this.sendStatus.bind(this));

        process.on('flora::serverStatus', (message) => {
            serverStatusEvent.emit('serverStatus', message.serverStatus);
        });

        process.on('SIGTERM', () => {
            this.log.info('received SIGTERM, exiting immediately');
            process.exit(0);
        });

        process.on('SIGINT', () => {
            this.log.info('received SIGINT, exiting immediately');
            process.exit(0);
        });

        process.on('SIGQUIT', () => {
            this.log.info('received SIGQUIT, shutting down gracefully');
            this.shutdown();
        });

        process.on('uncaughtException', (err) => {
            this.log.error(err, 'uncaught exception, shutting down gracefully');
            process.exitCode = 1;
            this.shutdown();
        });

        process.on('unhandledRejection', (err /* , promise */) => {
            this.log.error(err, 'unhandled rejection, shutting down gracefully');
            process.exitCode = 1;
            this.shutdown();
        });

        if (this.config.httpServer) {
            this.attach(this.config.httpServer);
        }
    }

    /**
     * Tell the master that the worker is ready.
     *
     * This function needs to be called before `startupTimeout` is over, otherwise the worker
     * is assumed to failed to start and thus being killed by the master process.
     */
    ready() {
        if (!cluster.isMaster) {
            this.log.debug('sending "ready" event to master');
            this.send({ event: 'ready' });
        }
    }

    /**
     * Attach to an exising HTTP server.
     *
     * This may be necessary after creating the Worker instance, so we can instantiate the
     * http.Server later.
     *
     * @param {http.Server} server
     */
    attach(server) {
        this.httpServer = server;
        prependListener(this.httpServer, 'request', this.onRequest.bind(this));
        prependListener(this.httpServer, 'connection', this.onConnection.bind(this));
        this.httpServer.on('close', this.onCloseServer.bind(this));
    }

    /**
     * Shutdown the worker
     */
    shutdown() {
        if (!this.isRunning) return;
        this.isRunning = false;

        if (!this.httpServer) {
            if (!cluster.isMaster) cluster.worker.disconnect();
            return;
        }

        try {
            // make sure we close down within given timeout:
            const shutdownTimeout = setTimeout(() => {
                this.logActiveHandles();
                this.log.warn('exit after timeout');
                setTimeout(() => {
                    process.exit(2);
                }, 100); // give async logging a chance before hard exit
            }, this.config.shutdownTimeout);

            // but don't keep the process open just for that:
            shutdownTimeout.unref();

            // notify master that we are shutting down (so new workers can be spawned immediately):
            if (!cluster.isMaster) this.send({ event: 'shutdown' });

            this.emit('close');

            // close keep-alive-connection (and do not wait for FIN ACK from client):
            this.activeConnections.forEach((connection) => {
                if (connection.flora.isIdle) connection.destroy();
            });

            this.sendStatus();
        } catch (e) {
            this.log.error(e, 'error on graceful shutdown');
        }
    }

    logActiveHandles() {
        if (typeof process._getActiveHandles !== 'function') {
            this.log.debug("Can't check for active handles - process._getActiveHandles not available");
            return;
        }

        const whitelist = [];
        if (process.stdout) whitelist.push(process.stdout);
        if (process.stderr) whitelist.push(process.stderr);
        if (cluster.worker && cluster.worker.suicide && process._channel) {
            whitelist.push(process._channel); // cluster IPC pipe
        }

        function dumpObjectProperties(obj) {
            const properties = [];

            do {
                const name = (obj.constructor && obj.constructor.name) || '(Unknown)';
                properties.push(name + '[' + Object.getOwnPropertyNames(obj).join(', ') + ']');
            } while ((obj = Object.getPrototypeOf(obj)) && obj !== Object.prototype);

            return properties.join(' ~ ');
        }

        const handles = process._getActiveHandles(); // internal node interface!

        const handleInfos = [];
        handles.forEach((handle) => {
            let name = typeof handle;
            let description = null;

            if (whitelist.indexOf(handle) !== -1) return;

            if (handle === null) {
                name = 'null-handle';
                description = 'wtf?!';
            } else if (handle && typeof handle === 'object') {
                if (handle.constructor && handle.constructor.name) {
                    name = handle.constructor.name;
                }

                if (handle instanceof Socket) {
                    if (handle.remoteAddress) {
                        description = handle.localAddress + ':' + handle.localPort;
                        description += ' -> ' + handle.remoteAddress + ':' + handle.remotePort;
                    } else {
                        description = dumpObjectProperties(handle);
                    }
                    description += handle.destroyed ? ' (destroyed)' : '';
                } else if (handle instanceof DgramSocket) {
                    name = 'dgram.Socket';
                    description = 'type: ' + handle.type;
                } else if (handle instanceof Server) {
                    const { address, port } = handle.address();
                    description = `${address}:${port}`;
                } else if (handle instanceof ChildProcess) {
                    description = 'PID: ' + handle.pid;
                    description += '; args: ' + (handle.spawnargs ? handle.spawnargs.join(' ') : '-');
                } else {
                    description = dumpObjectProperties(handle);
                }
            }

            handleInfos.push({ name, description });
        });

        handleInfos.forEach((handleInfo) => {
            this.log.warn(
                `Active handle: ${handleInfo.name}` + (handleInfo.description ? ` (${handleInfo.description})` : '')
            );
        });
    }

    onRequest(request, response) {
        request.flora = request.flora || {};
        request.flora.startTime = process.hrtime();
        request.flora.isActive = true; // false if response.end/pipe has been called
        request.flora.isAborted = false; // true if client has closed connection
        request.flora.state = 'processing';

        request.connection.flora.isIdle = false;
        request.connection.flora.status.increment('requests');
        request.connection.flora.status.set('idleSince', null);

        const status = request.connection.flora.status.child('request');
        request.flora.status = status;

        status.set('state', null);
        status.set('startTime', new Date());
        status.onStatus(() => {
            status.set('state', request.flora.state);
            status.set('ip', request.ip || (request.connection ? request.connection.remoteAddress : null));
            status.set('method', request.method);
            status.set('url', request.url);
            status.set('httpVersion', request.httpVersion);
        });

        // TODO: Set different timeout for running requests than for idle keep-alive-connections

        // emulate response.on('end') - when finished processing the request:
        trackResponseEnd(request, response);

        response.on('finish', () => {
            request.flora.isActive = false; // should already be false here by trackResponseEnd()
            request.flora.state = 'finished';
            status.close();
            request.connection.flora.isIdle = true;
            request.connection.flora.status.set('idleSince', new Date());
            if (request.connection.flora.isClosed) {
                request.connection.flora.status.close();
            }

            if (!this.isRunning) {
                // close keep-alive-connection (and do not wait for FIN ACK from clients):
                request.connection.destroy();
            }
        });

        response.on('close', () => {
            if (!request.flora.isActive) {
                status.close();
                if (request.connection.flora.isClosed) {
                    request.connection.flora.status.close();
                }
            }

            request.flora.isAborted = true;
            request.flora.state = 'aborted';
        });
    }

    onConnection(connection) {
        connection.setNoDelay(true);
        connection.setKeepAlive(true);

        connection.flora = connection.flora || {};
        connection.flora.isIdle = true;

        this.status.increment('connections');

        const status = this.status.addChild('activeConnections');
        connection.flora.status = status;

        status.set('state', 'connected');
        status.set('startTime', new Date());
        status.set('remoteAddress', connection.remoteAddress);
        status.set('remotePort', connection.remotePort);
        status.set('idleSince', new Date());
        status.onStatus(() => {
            status.setIncrement('bytesRead', connection.bytesRead);
            status.setIncrement('bytesWritten', connection.bytesWritten);
        });

        this.activeConnections.push(connection);

        connection.on('close', () => {
            status.setIncrement('bytesRead', connection.bytesRead);
            status.setIncrement('bytesWritten', connection.bytesWritten);
            status.set('state', 'closed');
            if (connection.flora.isIdle) {
                status.close();
            } else {
                connection.flora.isClosed = true;
            }
            removeValue(this.activeConnections, connection);
        });
    }

    onCloseServer() {
        this.sendStatus();
        this.log.info('Closing IPC channel - should exit now');

        // if all references are closed properly, worker exits here,
        // otherwise the kill-timeout from shutdown() should catch it:
        if (!cluster.isMaster) cluster.worker.disconnect();
    }

    sendStatus() {
        if (!cluster.isMaster) this.send({ event: 'status', status: this.status.getStatus() });
    }

    send(message) {
        process.send(message, (err) => {
            if (err) {
                // catch IPC channel errors:
                this.log.error(err, 'error sending IPC message to master');
            }
        });
    }

    /**
     * Retrieve the cluster status
     *
     * @returns {Promise}
     */
    async serverStatus() {
        if (cluster.isMaster) return this.status.getStatus();

        return new Promise((resolve) => {
            serverStatusEvent.fetch();
            serverStatusEvent.once('serverStatus', (status) => resolve(status));
        });
    }
}

module.exports = Worker;
<|endoftext|>


<commit_before>const request = require("request");

/** Call the WoW API.
 * @param {String} url
 * @param {String} token
 * @param {String} cb
 * @param {Function} errcb
 */
function callAPI(url, token, cb, errcb) {
  try {
    request(url + token, cb);
  } catch (err) {
    errcb("WoWAPIError: " + err.message);
  }
}

/**Encodes a string in base64.
 * @param {string} toEncode
 * @returns {String} the encoded string
 */
function base64(toEncode) {
  return Buffer.from(toEncode).toString("base64");
}

/**Call the WoW API and get the price for a WoW Token.
 * @param {string} token The access token.
 * @param {function} cb Callback for success.
 * @param {function} ecb Callback for failure.
 */
function getWowTokenPrice(token, cb, ecb) {
  const base_URL = "https://us.api.blizzard.com/data/wow/token/?namespace=dynamic-us&locale=en_US&access_token=";
  callAPI(base_URL, token, (error, response, body) => {
    if (!error && response.statusCode === 200) {
      const val = Number(JSON.parse(body).price);
      cb("WoW Token is worth " + parseInt(val / 10000) + "g on US servers");
    } else {
      if (error) {
        ecb("WoWTokenError: " + error.msg);
      } else {
        ecb("WoW Token: received response code" + response.statusCode);
      }
    }
  }, console.log);
}

function getCharacterProgressionData(key, cb, realm, character) {
  const base_URL = "https://us.api.blizzard.com/wow/character/" + realm + "/" + character + "?fields=progression&locale=en_US&access_token=";
  callAPI(base_URL, key, (error, response, body) => {
    if (response.statusCode === 404) {
      cb(null, "Error: Character not found.");
    }
    if (!error && response.statusCode === 200) {
      cb(JSON.parse(body));
    } else {
      if (error) {
        console.log("WoWAPIError: " + error.msg);
      } else {
        console.log("WoWAPIError: HTTP Error" + response.statusCode);
      }
    }
  }, console.log);
}

function getProgressionData(chardata, names) {
  let raids = chardata.progression.raids;
  let ret = new Array(names.length);
  let nbii = new Array(names.length); //number of bosses in instance
  for (let i = 0; i < names.length; i++) { //iterate over the expac's raids
    for (let j = 0; j < raids.length; j++) { //iterate over all the raid progression things
      if (raids[j].name === names[i]) {
        const DIFFS = ["lfrKills", "normalKills", "heroicKills", "mythicKills"];
        ret[i] = [0, 0, 0, 0]; //LFR-N-H-M
        nbii[i] = raids[j].bosses.length;
        for (let k = 0; k < raids[j].bosses.length; k++) {
          for (let dn = 0; dn < DIFFS.length; dn++) {
            // console.log(raids[j].bosses[k]);
            if (raids[j].bosses[k][DIFFS[dn]] > 0) { //if the number of kills on the difficulty is non-zero
              ret[i][dn]++;
            }
          }
        }
        break;
      }
    }
  }
  return {
    kills: ret,
    outof: nbii
  };
}

/**Pads a single digit number to have a length of 2.
 * @param {number} num
 * @returns {string}
 */
function formatProgNum(num) {
  if (num <= 9) {
    return " " + String(num);
  } else {
    return String(num);
  }
}

function prettyProg(prog, names, abbrevs, realm, character) {
  let ret = "Progression for " + character + "-" + realm + "\n";
  ret += "```\n";
  ret += "   | LFR |  N  |  H  |  M  \n";
  for (let i = 0; i < abbrevs.length; i++) {
    ret += abbrevs[i];
    for (let j = 0; j < 4; j++) {
      ret += "|" + formatProgNum(prog.kills[i][j]) + "/" + formatProgNum(prog.outof[i]);
    }
    ret += "\n";
  }
  ret += "\n```";
  if (prog.kills[1][3] > 0) {
    ret += "(mythic raider btw)";
  }
  return ret;
}

function argsToNameAndRealm(args, HOMEREALM) {
  let toon, realm;
  if (args.length === 2) {
    toon = args[0];
    realm = args[1];
  } else if (args.length === 1 && args[0].includes("-") === true) {
    let temp = args[0].split("-");
    toon = temp[0];
    realm = temp[1];
  } else if (args.length === 1) {
    toon = args[0];
    realm = HOMEREALM;
  }
  return [toon, realm];
}

/**Given client credentials, request an authorization token from the
 *  Blizzard API.
 * @param {string} clientID
 * @param {string} clientSecret
 * @param {function} cb Callback, called on success.
 */
function getOAuthToken(clientID, clientSecret, cb) {
  const baseURI = "https://us.battle.net/oauth/token";
  request.post({
        url: baseURI,
        headers: {
          "content-type": "application/x-www-form-urlencoded",
          "Authorization": "Basic " + base64(clientID + ":" + clientSecret)
        },
        form: {
          "grant_type": "client_credentials",
          "client_id": clientID,
          "client_secret": clientSecret
        }
      },
      (err, resp, body) => {
        if (err) {
          console.log(err);
        } else {
          cb(JSON.parse(body).access_token);
        }
      });
}

module.exports = {
  getWowTokenPrice,
  getCharacterProgressionData,
  getProgressionData,
  prettyProg,
  argsToNameAndRealm,
  getOAuthToken
};<commit_msg>forgot mythic raider thing<commit_after>const request = require("request");

/** Call the WoW API.
 * @param {String} url
 * @param {String} token
 * @param {String} cb
 * @param {Function} errcb
 */
function callAPI(url, token, cb, errcb) {
  try {
    request(url + token, cb);
  } catch (err) {
    errcb("WoWAPIError: " + err.message);
  }
}

/**Encodes a string in base64.
 * @param {string} toEncode
 * @returns {String} the encoded string
 */
function base64(toEncode) {
  return Buffer.from(toEncode).toString("base64");
}

/**Call the WoW API and get the price for a WoW Token.
 * @param {string} token The access token.
 * @param {function} cb Callback for success.
 * @param {function} ecb Callback for failure.
 */
function getWowTokenPrice(token, cb, ecb) {
  const base_URL = "https://us.api.blizzard.com/data/wow/token/?namespace=dynamic-us&locale=en_US&access_token=";
  callAPI(base_URL, token, (error, response, body) => {
    if (!error && response.statusCode === 200) {
      const val = Number(JSON.parse(body).price);
      cb("WoW Token is worth " + parseInt(val / 10000) + "g on US servers");
    } else {
      if (error) {
        ecb("WoWTokenError: " + error.msg);
      } else {
        ecb("WoW Token: received response code" + response.statusCode);
      }
    }
  }, console.log);
}

function getCharacterProgressionData(key, cb, realm, character) {
  const base_URL = "https://us.api.blizzard.com/wow/character/" + realm + "/" + character + "?fields=progression&locale=en_US&access_token=";
  callAPI(base_URL, key, (error, response, body) => {
    if (response.statusCode === 404) {
      cb(null, "Error: Character not found.");
    }
    if (!error && response.statusCode === 200) {
      cb(JSON.parse(body));
    } else {
      if (error) {
        console.log("WoWAPIError: " + error.msg);
      } else {
        console.log("WoWAPIError: HTTP Error" + response.statusCode);
      }
    }
  }, console.log);
}

function getProgressionData(chardata, names) {
  let raids = chardata.progression.raids;
  let ret = new Array(names.length);
  let nbii = new Array(names.length); //number of bosses in instance
  for (let i = 0; i < names.length; i++) { //iterate over the expac's raids
    for (let j = 0; j < raids.length; j++) { //iterate over all the raid progression things
      if (raids[j].name === names[i]) {
        const DIFFS = ["lfrKills", "normalKills", "heroicKills", "mythicKills"];
        ret[i] = [0, 0, 0, 0]; //LFR-N-H-M
        nbii[i] = raids[j].bosses.length;
        for (let k = 0; k < raids[j].bosses.length; k++) {
          for (let dn = 0; dn < DIFFS.length; dn++) {
            // console.log(raids[j].bosses[k]);
            if (raids[j].bosses[k][DIFFS[dn]] > 0) { //if the number of kills on the difficulty is non-zero
              ret[i][dn]++;
            }
          }
        }
        break;
      }
    }
  }
  return {
    kills: ret,
    outof: nbii
  };
}

/**Pads a single digit number to have a length of 2.
 * @param {number} num
 * @returns {string}
 */
function formatProgNum(num) {
  if (num <= 9) {
    return " " + String(num);
  } else {
    return String(num);
  }
}

function prettyProg(prog, names, abbrevs, realm, character) {
  let ret = "Progression for " + character + "-" + realm + "\n";
  ret += "```\n";
  ret += "   | LFR |  N  |  H  |  M  \n";
  for (let i = 0; i < abbrevs.length; i++) {
    ret += abbrevs[i];
    for (let j = 0; j < 4; j++) {
      ret += "|" + formatProgNum(prog.kills[i][j]) + "/" + formatProgNum(prog.outof[i]);
    }
    ret += "\n";
  }
  ret += "\n```";
  if (prog.kills[2][3] > 0) {
    ret += "(mythic raider btw)";
  }
  return ret;
}

function argsToNameAndRealm(args, HOMEREALM) {
  let toon, realm;
  if (args.length === 2) {
    toon = args[0];
    realm = args[1];
  } else if (args.length === 1 && args[0].includes("-") === true) {
    let temp = args[0].split("-");
    toon = temp[0];
    realm = temp[1];
  } else if (args.length === 1) {
    toon = args[0];
    realm = HOMEREALM;
  }
  return [toon, realm];
}

/**Given client credentials, request an authorization token from the
 *  Blizzard API.
 * @param {string} clientID
 * @param {string} clientSecret
 * @param {function} cb Callback, called on success.
 */
function getOAuthToken(clientID, clientSecret, cb) {
  const baseURI = "https://us.battle.net/oauth/token";
  request.post({
        url: baseURI,
        headers: {
          "content-type": "application/x-www-form-urlencoded",
          "Authorization": "Basic " + base64(clientID + ":" + clientSecret)
        },
        form: {
          "grant_type": "client_credentials",
          "client_id": clientID,
          "client_secret": clientSecret
        }
      },
      (err, resp, body) => {
        if (err) {
          console.log(err);
        } else {
          cb(JSON.parse(body).access_token);
        }
      });
}

module.exports = {
  getWowTokenPrice,
  getCharacterProgressionData,
  getProgressionData,
  prettyProg,
  argsToNameAndRealm,
  getOAuthToken
};<|endoftext|>


<commit_before>/*  wrench.js
 *
 *  A collection of various utility functions I've found myself in need of
 *  for use with Node.js (http://nodejs.org/). This includes things like:
 *
 *  - Recursively deleting directories in Node.js (Sync, not Async)
 *  - Recursively copying directories in Node.js (Sync, not Async)
 *  - Recursively chmoding a directory structure from Node.js (Sync, not Async)
 *  - Other things that I'll add here as time goes on. Shhhh...
 *
 *  ~ Ryan McGrath (ryan [at] venodesigns.net)
 */

var fs = require("fs"),
    _path = require("path");

/*  wrench.readdirSyncRecursive("directory_path");
 *
 *  Recursively dives through directories and read the contents of all the
 *  children directories.
 */
exports.readdirSyncRecursive = function(baseDir) {
    baseDir = baseDir.replace(/\/$/, '');

    var readdirSyncRecursive = function(baseDir) {
        var files = [],
            curFiles,
            nextDirs,
            isDir = function(fname){
                return fs.statSync( _path.join(baseDir, fname) ).isDirectory();
            },
            prependBaseDir = function(fname){
                return _path.join(baseDir, fname);
            };

        curFiles = fs.readdirSync(baseDir);
        nextDirs = curFiles.filter(isDir);
        curFiles = curFiles.map(prependBaseDir);

        files = files.concat( curFiles );

        while (nextDirs.length) {
            files = files.concat( readdirSyncRecursive( _path.join(baseDir, nextDirs.shift()) ) );
        }

        return files;
    };

    // convert absolute paths to relative
    var fileList = readdirSyncRecursive(baseDir).map(function(val){
        return _path.relative(baseDir, val);
    });

    return fileList;
};

/*  wrench.readdirRecursive("directory_path", function(error, files) {});
 *
 *  Recursively dives through directories and read the contents of all the
 *  children directories.
 *
 *  Asynchronous, so returns results/error in callback.
 *  Callback receives the of files in currently recursed directory.
 *  When no more directories are left, callback is called with null for all arguments.
 *
 */
exports.readdirRecursive = function(baseDir, fn) {
    baseDir = baseDir.replace(/\/$/, '');

    var waitCount = 0;

    function readdirRecursive(curDir) {
        var prependcurDir = function(fname){
            return _path.join(curDir, fname);
        };

        waitCount++;
        fs.readdir(curDir, function(e, curFiles) {
            if (e) {
                fn(e);
                return;
            }
            waitCount--;

            curFiles = curFiles.map(prependcurDir);

            curFiles.forEach(function(it) {
                waitCount++;

                fs.stat(it, function(e, stat) {
                    waitCount--;

                    if (e) {
                        fn(e);
                    } else {
                        if (stat.isDirectory()) {
                            readdirRecursive(it);
                        }
                    }

                    if (waitCount == 0) {
                        fn(null, null);
                    }
                });
            });

            fn(null, curFiles.map(function(val) {
                // convert absolute paths to relative
                return _path.relative(baseDir, val);
            }));

            if (waitCount == 0) {
                fn(null, null);
            }
        });
    };

    readdirRecursive(baseDir);
};

/*  wrench.rmdirSyncRecursive("directory_path", forceDelete, failSilent);
 *
 *  Recursively dives through directories and obliterates everything about it. This is a
 *  Sync-function, which blocks things until it's done. No idea why anybody would want an
 *  Asynchronous version. :\
 */
exports.rmdirSyncRecursive = function(path, failSilent) {
    var files;

    try {
        files = fs.readdirSync(path);
    } catch (err) {
        if(failSilent) return;
        throw new Error(err.message);
    }

    /*  Loop through and delete everything in the sub-tree after checking it */
    for(var i = 0; i < files.length; i++) {
        var currFile = fs.lstatSync(_path.join(path, files[i]));

        if(currFile.isDirectory()) // Recursive function back to the beginning
            exports.rmdirSyncRecursive(_path.join(path, files[i]));

        else if(currFile.isSymbolicLink()) // Unlink symlinks
            fs.unlinkSync(_path.join(path, files[i]));

        else // Assume it's a file - perhaps a try/catch belongs here?
            fs.unlinkSync(_path.join(path, files[i]));
    }

    /*  Now that we know everything in the sub-tree has been deleted, we can delete the main
     directory. Huzzah for the shopkeep. */
    return fs.rmdirSync(path);
};

/*  wrench.copyDirSyncRecursive("directory_to_copy", "new_directory_location", opts);
 *
 *  Recursively dives through a directory and moves all its files to a new location. This is a
 *  Synchronous function, which blocks things until it's done. If you need/want to do this in
 *  an Asynchronous manner, look at wrench.copyDirRecursively() below. Specify forceDelete to force directory overwrite.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.copyDirSyncRecursive = function(sourceDir, newDirLocation, opts) {
    try {
    	if(fs.statSync(newDirLocation).isDirectory()) {
    		if(typeof opts !== 'undefined' && opts.forceDelete) {
			exports.rmdirSyncRecursive(newDirLocation);
        	} else {
	    		return new Error('You are trying to delete a directory that already exists. Specify forceDelete in the opts argument to override this. Bailing~');
        	}
    	}
    } catch(e) { }

    /*  Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */
    var checkDir = fs.statSync(sourceDir);
    try {
        fs.mkdirSync(newDirLocation, checkDir.mode);
    } catch (e) {
        //if the directory already exists, that's okay
        if (e.code !== 'EEXIST') throw e;
    }

    var files = fs.readdirSync(sourceDir);

    for(var i = 0; i < files.length; i++) {
        // ignores all files or directories which match the RegExp in opts.filter
		if(typeof opts !== 'undefined') {
            if(!opts.whitelist && opts.filter && files[i].match(opts.filter)) continue;
		    // if opts.whitelist is true every file or directory which doesn't match opts.filter will be ignored
		    if(opts.whitelist && opts.filter && !files[i].match(opts.filter)) continue;
            if (opts.excludeHiddenUnix && /^\./.test(files[i])) continue;
        }

        var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));

        var fCopyFile = function(srcFile, destFile) {
            if(typeof opts !== 'undefined' && opts.preserveFiles && fs.existsSync(destFile)) return;

            var contents = fs.readFileSync(srcFile);
            fs.writeFileSync(destFile, contents);
        };

        if(currFile.isDirectory()) {
            /*  recursion this thing right on back. */
            exports.copyDirSyncRecursive(_path.join(sourceDir, files[i]), _path.join(newDirLocation, files[i]), opts);
        } else if(currFile.isSymbolicLink()) {
            var symlinkFull = fs.readlinkSync(_path.join(sourceDir, files[i]));

            if (typeof opts !== 'undefined' && !opts.inflateSymlinks) {
                fs.symlinkSync(symlinkFull, _path.join(newDirLocation, files[i]));
                continue;
            }

            var tmpCurrFile = fs.lstatSync(_path.join(sourceDir, symlinkFull));
            if (tmpCurrFile.isDirectory()) {
                exports.copyDirSyncRecursive(_path.join(sourceDir, symlinkFull), _path.join(newDirLocation, files[i]), opts);
            } else {
                /*  At this point, we've hit a file actually worth copying... so copy it on over. */
                fCopyFile(_path.join(sourceDir, symlinkFull), _path.join(newDirLocation, files[i]));
            }
        } else {
            /*  At this point, we've hit a file actually worth copying... so copy it on over. */
            fCopyFile(_path.join(sourceDir, files[i]), _path.join(newDirLocation, files[i]));
        }
    }
};

/*  wrench.chmodSyncRecursive("directory", filemode);
 *
 *  Recursively dives through a directory and chmods everything to the desired mode. This is a
 *  Synchronous function, which blocks things until it's done.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.chmodSyncRecursive = function(sourceDir, filemode) {
    var files = fs.readdirSync(sourceDir);

    for(var i = 0; i < files.length; i++) {
        var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));

        if(currFile.isDirectory()) {
            /*  ...and recursion this thing right on back. */
            exports.chmodSyncRecursive(_path.join(sourceDir, files[i]), filemode);
        } else {
            /*  At this point, we've hit a file actually worth copying... so copy it on over. */
            fs.chmod(_path.join(sourceDir, files[i]), filemode);
        }
    }

    /*  Finally, chmod the parent directory */
    fs.chmod(sourceDir, filemode);
};

/*  wrench.chownSyncRecursive("directory", uid, gid);
 *
 *  Recursively dives through a directory and chowns everything to the desired user and group. This is a
 *  Synchronous function, which blocks things until it's done.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.chownSyncRecursive = function(sourceDir, uid, gid) {
    var files = fs.readdirSync(sourceDir);

    for(var i = 0; i < files.length; i++) {
        var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));

        if(currFile.isDirectory()) {
            /*  ...and recursion this thing right on back. */
            exports.chownSyncRecursive(_path.join(sourceDir, files[i]), uid, gid);
        } else {
            /*  At this point, we've hit a file actually worth chowning... so own it. */
            fs.chownSync(_path.join(sourceDir, files[i]), uid, gid);
        }
    }

    /*  Finally, chown the parent directory */
    fs.chownSync(sourceDir, uid, gid);
};

/*  wrench.rmdirRecursive("directory_path", callback);
 *
 *  Recursively dives through directories and obliterates everything about it.
 */
exports.rmdirRecursive = function rmdirRecursive(dir, clbk){
    fs.readdir(dir, function(err, files){
        if (err) return clbk(err);
        (function rmFile(err){
            if (err) return clbk(err);

            var filename = files.shift();
            if (filename === null || typeof filename == 'undefined')
                return fs.rmdir(dir, clbk);

            var file = dir+'/'+filename;
            fs.lstat(file, function(err, stat){
                if (err) return clbk(err);
                if (stat.isDirectory())
                    rmdirRecursive(file, rmFile);
                else
                    fs.unlink(file, rmFile);
            });
        })();
    });
};

/*  wrench.copyDirRecursive("directory_to_copy", "new_location", {forceDelete: bool}, callback);
 *
 *  Recursively dives through a directory and moves all its files to a new
 *  location. Specify forceDelete to force directory overwrite.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.copyDirRecursive = function copyDirRecursive(srcDir, newDir, opts, clbk) {
    fs.stat(newDir, function(err, newDirStat){
        if(!err) {
		if(typeof opts !== 'undefined' && typeof opts !== 'function' && opts.forceDelete)
			return exports.rmdirRecursive(newDir, function(err){
				copyDirRecursive.apply(this, arguments);
        		});
		else
			return clbk(new Error('You are trying to delete a directory that already exists. Specify forceDelete in an options object to override this.'));
	}

        fs.stat(srcDir, function(err, srcDirStat){
            if (err) return clbk(err);
            fs.mkdir(newDir, srcDirStat.mode, function(err){
                if (err) return clbk(err);
                fs.readdir(srcDir, function(err, files){
                    if (err) return clbk(err);
                    (function copyFiles(err){
                        if (err) return clbk(err);

                        var filename = files.shift();
                        if (filename === null || typeof filename == 'undefined')
                            return clbk(null);

                        var file = srcDir+'/'+filename,
                            newFile = newDir+'/'+filename;

                        fs.stat(file, function(err, fileStat){
                            if (fileStat.isDirectory())
                                copyDirRecursive(file, newFile, copyFiles);
                            else if (fileStat.isSymbolicLink())
                                fs.readlink(file, function(err, link){
                                    fs.symlink(link, newFile, copyFiles);
                                });
                            else
                                fs.readFile(file, function(err, data){
                                    fs.writeFile(newFile, data, copyFiles);
                                });
                        });
                    })();
                });
            });
        });
    });
};

var mkdirSyncRecursive = function(path, mode) {
    var self = this;
    path = _path.normalize(path)

    try {
        fs.mkdirSync(path, mode);
    } catch(err) {
        if(err.code == "ENOENT") {
            var slashIdx = path.lastIndexOf(_path.sep);

            if(slashIdx > 0) {
                var parentPath = path.substring(0, slashIdx);
                mkdirSyncRecursive(parentPath, mode);
                mkdirSyncRecursive(path, mode);
            } else {
                throw err;
            }
        } else if(err.code == "EEXIST") {
            return;
        } else {
            throw err;
        }
    }
};
exports.mkdirSyncRecursive = mkdirSyncRecursive;

exports.LineReader = function(filename, bufferSize) {
    this.bufferSize = bufferSize || 8192;
    this.buffer = "";
    this.fd = fs.openSync(filename, "r");
    this.currentPosition = 0;
};

exports.LineReader.prototype = {
    close: function() {
        return fs.closeSync(this.fd);
    },

    getBufferAndSetCurrentPosition: function(position) {
        var res = fs.readSync(this.fd, this.bufferSize, position, "ascii");

        this.buffer += res[0];
        if(res[1] === 0) {
            this.currentPosition = -1;
        } else {
            this.currentPosition = position + res[1];
        }

        return this.currentPosition;
    },

    hasNextLine: function() {
        while(this.buffer.indexOf('\n') === -1) {
            this.getBufferAndSetCurrentPosition(this.currentPosition);
            if(this.currentPosition === -1) return false;
        }

        if(this.buffer.indexOf("\n") > -1 || this.buffer.length !== 0) return true;
        return false;
    },

    getNextLine: function() {
        var lineEnd = this.buffer.indexOf("\n"),
            result = this.buffer.substring(0, lineEnd ? lineEnd : this.buffer.length);

        this.buffer = this.buffer.substring(result.length + 1, this.buffer.length);
        return result;
    }
};

// vim: et ts=4 sw=4
<commit_msg>Bit of args switching to keep old users in running order<commit_after>/*  wrench.js
 *
 *  A collection of various utility functions I've found myself in need of
 *  for use with Node.js (http://nodejs.org/). This includes things like:
 *
 *  - Recursively deleting directories in Node.js (Sync, not Async)
 *  - Recursively copying directories in Node.js (Sync, not Async)
 *  - Recursively chmoding a directory structure from Node.js (Sync, not Async)
 *  - Other things that I'll add here as time goes on. Shhhh...
 *
 *  ~ Ryan McGrath (ryan [at] venodesigns.net)
 */

var fs = require("fs"),
    _path = require("path");

/*  wrench.readdirSyncRecursive("directory_path");
 *
 *  Recursively dives through directories and read the contents of all the
 *  children directories.
 */
exports.readdirSyncRecursive = function(baseDir) {
    baseDir = baseDir.replace(/\/$/, '');

    var readdirSyncRecursive = function(baseDir) {
        var files = [],
            curFiles,
            nextDirs,
            isDir = function(fname){
                return fs.statSync( _path.join(baseDir, fname) ).isDirectory();
            },
            prependBaseDir = function(fname){
                return _path.join(baseDir, fname);
            };

        curFiles = fs.readdirSync(baseDir);
        nextDirs = curFiles.filter(isDir);
        curFiles = curFiles.map(prependBaseDir);

        files = files.concat( curFiles );

        while (nextDirs.length) {
            files = files.concat( readdirSyncRecursive( _path.join(baseDir, nextDirs.shift()) ) );
        }

        return files;
    };

    // convert absolute paths to relative
    var fileList = readdirSyncRecursive(baseDir).map(function(val){
        return _path.relative(baseDir, val);
    });

    return fileList;
};

/*  wrench.readdirRecursive("directory_path", function(error, files) {});
 *
 *  Recursively dives through directories and read the contents of all the
 *  children directories.
 *
 *  Asynchronous, so returns results/error in callback.
 *  Callback receives the of files in currently recursed directory.
 *  When no more directories are left, callback is called with null for all arguments.
 *
 */
exports.readdirRecursive = function(baseDir, fn) {
    baseDir = baseDir.replace(/\/$/, '');

    var waitCount = 0;

    function readdirRecursive(curDir) {
        var prependcurDir = function(fname){
            return _path.join(curDir, fname);
        };

        waitCount++;
        fs.readdir(curDir, function(e, curFiles) {
            if (e) {
                fn(e);
                return;
            }
            waitCount--;

            curFiles = curFiles.map(prependcurDir);

            curFiles.forEach(function(it) {
                waitCount++;

                fs.stat(it, function(e, stat) {
                    waitCount--;

                    if (e) {
                        fn(e);
                    } else {
                        if (stat.isDirectory()) {
                            readdirRecursive(it);
                        }
                    }

                    if (waitCount == 0) {
                        fn(null, null);
                    }
                });
            });

            fn(null, curFiles.map(function(val) {
                // convert absolute paths to relative
                return _path.relative(baseDir, val);
            }));

            if (waitCount == 0) {
                fn(null, null);
            }
        });
    };

    readdirRecursive(baseDir);
};

/*  wrench.rmdirSyncRecursive("directory_path", forceDelete, failSilent);
 *
 *  Recursively dives through directories and obliterates everything about it. This is a
 *  Sync-function, which blocks things until it's done. No idea why anybody would want an
 *  Asynchronous version. :\
 */
exports.rmdirSyncRecursive = function(path, failSilent) {
    var files;

    try {
        files = fs.readdirSync(path);
    } catch (err) {
        if(failSilent) return;
        throw new Error(err.message);
    }

    /*  Loop through and delete everything in the sub-tree after checking it */
    for(var i = 0; i < files.length; i++) {
        var currFile = fs.lstatSync(_path.join(path, files[i]));

        if(currFile.isDirectory()) // Recursive function back to the beginning
            exports.rmdirSyncRecursive(_path.join(path, files[i]));

        else if(currFile.isSymbolicLink()) // Unlink symlinks
            fs.unlinkSync(_path.join(path, files[i]));

        else // Assume it's a file - perhaps a try/catch belongs here?
            fs.unlinkSync(_path.join(path, files[i]));
    }

    /*  Now that we know everything in the sub-tree has been deleted, we can delete the main
     directory. Huzzah for the shopkeep. */
    return fs.rmdirSync(path);
};

/*  wrench.copyDirSyncRecursive("directory_to_copy", "new_directory_location", opts);
 *
 *  Recursively dives through a directory and moves all its files to a new location. This is a
 *  Synchronous function, which blocks things until it's done. If you need/want to do this in
 *  an Asynchronous manner, look at wrench.copyDirRecursively() below. Specify forceDelete to force directory overwrite.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.copyDirSyncRecursive = function(sourceDir, newDirLocation, opts) {
    try {
    	if(fs.statSync(newDirLocation).isDirectory()) {
    		if(typeof opts !== 'undefined' && opts.forceDelete) {
			exports.rmdirSyncRecursive(newDirLocation);
        	} else {
	    		return new Error('You are trying to delete a directory that already exists. Specify forceDelete in the opts argument to override this. Bailing~');
        	}
    	}
    } catch(e) { }

    /*  Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */
    var checkDir = fs.statSync(sourceDir);
    try {
        fs.mkdirSync(newDirLocation, checkDir.mode);
    } catch (e) {
        //if the directory already exists, that's okay
        if (e.code !== 'EEXIST') throw e;
    }

    var files = fs.readdirSync(sourceDir);

    for(var i = 0; i < files.length; i++) {
        // ignores all files or directories which match the RegExp in opts.filter
		if(typeof opts !== 'undefined') {
            if(!opts.whitelist && opts.filter && files[i].match(opts.filter)) continue;
		    // if opts.whitelist is true every file or directory which doesn't match opts.filter will be ignored
		    if(opts.whitelist && opts.filter && !files[i].match(opts.filter)) continue;
            if (opts.excludeHiddenUnix && /^\./.test(files[i])) continue;
        }

        var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));

        var fCopyFile = function(srcFile, destFile) {
            if(typeof opts !== 'undefined' && opts.preserveFiles && fs.existsSync(destFile)) return;

            var contents = fs.readFileSync(srcFile);
            fs.writeFileSync(destFile, contents);
        };

        if(currFile.isDirectory()) {
            /*  recursion this thing right on back. */
            exports.copyDirSyncRecursive(_path.join(sourceDir, files[i]), _path.join(newDirLocation, files[i]), opts);
        } else if(currFile.isSymbolicLink()) {
            var symlinkFull = fs.readlinkSync(_path.join(sourceDir, files[i]));

            if (typeof opts !== 'undefined' && !opts.inflateSymlinks) {
                fs.symlinkSync(symlinkFull, _path.join(newDirLocation, files[i]));
                continue;
            }

            var tmpCurrFile = fs.lstatSync(_path.join(sourceDir, symlinkFull));
            if (tmpCurrFile.isDirectory()) {
                exports.copyDirSyncRecursive(_path.join(sourceDir, symlinkFull), _path.join(newDirLocation, files[i]), opts);
            } else {
                /*  At this point, we've hit a file actually worth copying... so copy it on over. */
                fCopyFile(_path.join(sourceDir, symlinkFull), _path.join(newDirLocation, files[i]));
            }
        } else {
            /*  At this point, we've hit a file actually worth copying... so copy it on over. */
            fCopyFile(_path.join(sourceDir, files[i]), _path.join(newDirLocation, files[i]));
        }
    }
};

/*  wrench.chmodSyncRecursive("directory", filemode);
 *
 *  Recursively dives through a directory and chmods everything to the desired mode. This is a
 *  Synchronous function, which blocks things until it's done.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.chmodSyncRecursive = function(sourceDir, filemode) {
    var files = fs.readdirSync(sourceDir);

    for(var i = 0; i < files.length; i++) {
        var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));

        if(currFile.isDirectory()) {
            /*  ...and recursion this thing right on back. */
            exports.chmodSyncRecursive(_path.join(sourceDir, files[i]), filemode);
        } else {
            /*  At this point, we've hit a file actually worth copying... so copy it on over. */
            fs.chmod(_path.join(sourceDir, files[i]), filemode);
        }
    }

    /*  Finally, chmod the parent directory */
    fs.chmod(sourceDir, filemode);
};

/*  wrench.chownSyncRecursive("directory", uid, gid);
 *
 *  Recursively dives through a directory and chowns everything to the desired user and group. This is a
 *  Synchronous function, which blocks things until it's done.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.chownSyncRecursive = function(sourceDir, uid, gid) {
    var files = fs.readdirSync(sourceDir);

    for(var i = 0; i < files.length; i++) {
        var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));

        if(currFile.isDirectory()) {
            /*  ...and recursion this thing right on back. */
            exports.chownSyncRecursive(_path.join(sourceDir, files[i]), uid, gid);
        } else {
            /*  At this point, we've hit a file actually worth chowning... so own it. */
            fs.chownSync(_path.join(sourceDir, files[i]), uid, gid);
        }
    }

    /*  Finally, chown the parent directory */
    fs.chownSync(sourceDir, uid, gid);
};

/*  wrench.rmdirRecursive("directory_path", callback);
 *
 *  Recursively dives through directories and obliterates everything about it.
 */
exports.rmdirRecursive = function rmdirRecursive(dir, failSilent, clbk){
    fs.readdir(dir, function(err, files){
        if(err && typeof failSilent === 'boolean' && !failSilent)
		return clbk(err);

	if(typof failSilent === 'function')
		clbk = failSilent;

        (function rmFile(err){
            if (err) return clbk(err);

            var filename = files.shift();
            if (filename === null || typeof filename == 'undefined')
                return fs.rmdir(dir, clbk);

            var file = dir+'/'+filename;
            fs.lstat(file, function(err, stat){
                if (err) return clbk(err);
                if (stat.isDirectory())
                    rmdirRecursive(file, rmFile);
                else
                    fs.unlink(file, rmFile);
            });
        })();
    });
};

/*  wrench.copyDirRecursive("directory_to_copy", "new_location", {forceDelete: bool}, callback);
 *
 *  Recursively dives through a directory and moves all its files to a new
 *  location. Specify forceDelete to force directory overwrite.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.copyDirRecursive = function copyDirRecursive(srcDir, newDir, opts, clbk) {
    fs.stat(newDir, function(err, newDirStat){
        if(!err) {
		if(typeof opts !== 'undefined' && typeof opts !== 'function' && opts.forceDelete)
			return exports.rmdirRecursive(newDir, function(err){
				copyDirRecursive.apply(this, arguments);
        		});
		else
			return clbk(new Error('You are trying to delete a directory that already exists. Specify forceDelete in an options object to override this.'));
	}

	if(typeof opts === 'function')
		clbk = opts;

        fs.stat(srcDir, function(err, srcDirStat){
            if (err) return clbk(err);
            fs.mkdir(newDir, srcDirStat.mode, function(err){
                if (err) return clbk(err);
                fs.readdir(srcDir, function(err, files){
                    if (err) return clbk(err);
                    (function copyFiles(err){
                        if (err) return clbk(err);

                        var filename = files.shift();
                        if (filename === null || typeof filename == 'undefined')
                            return clbk(null);

                        var file = srcDir+'/'+filename,
                            newFile = newDir+'/'+filename;

                        fs.stat(file, function(err, fileStat){
                            if (fileStat.isDirectory())
                                copyDirRecursive(file, newFile, copyFiles);
                            else if (fileStat.isSymbolicLink())
                                fs.readlink(file, function(err, link){
                                    fs.symlink(link, newFile, copyFiles);
                                });
                            else
                                fs.readFile(file, function(err, data){
                                    fs.writeFile(newFile, data, copyFiles);
                                });
                        });
                    })();
                });
            });
        });
    });
};

var mkdirSyncRecursive = function(path, mode) {
    var self = this;
    path = _path.normalize(path)

    try {
        fs.mkdirSync(path, mode);
    } catch(err) {
        if(err.code == "ENOENT") {
            var slashIdx = path.lastIndexOf(_path.sep);

            if(slashIdx > 0) {
                var parentPath = path.substring(0, slashIdx);
                mkdirSyncRecursive(parentPath, mode);
                mkdirSyncRecursive(path, mode);
            } else {
                throw err;
            }
        } else if(err.code == "EEXIST") {
            return;
        } else {
            throw err;
        }
    }
};
exports.mkdirSyncRecursive = mkdirSyncRecursive;

exports.LineReader = function(filename, bufferSize) {
    this.bufferSize = bufferSize || 8192;
    this.buffer = "";
    this.fd = fs.openSync(filename, "r");
    this.currentPosition = 0;
};

exports.LineReader.prototype = {
    close: function() {
        return fs.closeSync(this.fd);
    },

    getBufferAndSetCurrentPosition: function(position) {
        var res = fs.readSync(this.fd, this.bufferSize, position, "ascii");

        this.buffer += res[0];
        if(res[1] === 0) {
            this.currentPosition = -1;
        } else {
            this.currentPosition = position + res[1];
        }

        return this.currentPosition;
    },

    hasNextLine: function() {
        while(this.buffer.indexOf('\n') === -1) {
            this.getBufferAndSetCurrentPosition(this.currentPosition);
            if(this.currentPosition === -1) return false;
        }

        if(this.buffer.indexOf("\n") > -1 || this.buffer.length !== 0) return true;
        return false;
    },

    getNextLine: function() {
        var lineEnd = this.buffer.indexOf("\n"),
            result = this.buffer.substring(0, lineEnd ? lineEnd : this.buffer.length);

        this.buffer = this.buffer.substring(result.length + 1, this.buffer.length);
        return result;
    }
};

// vim: et ts=4 sw=4
<|endoftext|>


<commit_before>/*  wrench.js
 *
 *  A collection of various utility functions I've found myself in need of
 *  for use with Node.js (http://nodejs.org/). This includes things like:
 *
 *  - Recursively deleting directories in Node.js (Sync, not Async)
 *  - Recursively copying directories in Node.js (Sync, not Async)
 *  - Recursively chmoding a directory structure from Node.js (Sync, not Async)
 *  - Other things that I'll add here as time goes on. Shhhh...
 *
 *  ~ Ryan McGrath (ryan [at] venodesigns.net)
 */

var fs = require("fs"),
    _path = require("path");

/*  wrench.readdirSyncRecursive("directory_path");
 *
 *  Recursively dives through directories and read the contents of all the
 *  children directories.
 */
exports.readdirSyncRecursive = function(baseDir) {
    baseDir = baseDir.replace(/\/$/, '');

    var readdirSyncRecursive = function(baseDir) {
        var files = [],
            curFiles,
            nextDirs,
            isDir = function(fname){
                return fs.statSync( _path.join(baseDir, fname) ).isDirectory();
            },
            prependBaseDir = function(fname){
                return _path.join(baseDir, fname);
            };

        curFiles = fs.readdirSync(baseDir);
        nextDirs = curFiles.filter(isDir);
        curFiles = curFiles.map(prependBaseDir);

        files = files.concat( curFiles );

        while (nextDirs.length) {
            files = files.concat( readdirSyncRecursive( _path.join(baseDir, nextDirs.shift()) ) );
        }

        return files;
    };

    // convert absolute paths to relative
    var fileList = readdirSyncRecursive(baseDir).map(function(val){
        return _path.relative(baseDir, val);
    });

    return fileList;
};

/*  wrench.readdirRecursive("directory_path", function(error, files) {});
 *
 *  Recursively dives through directories and read the contents of all the
 *  children directories.
 *
 *  Asynchronous, so returns results/error in callback.
 *  Callback receives the of files in currently recursed directory.
 *  When no more directories are left, callback is called with null for all arguments.
 *
 */
exports.readdirRecursive = function(baseDir, fn) {
    baseDir = baseDir.replace(/\/$/, '');

    var waitCount = 0;

    function readdirRecursive(curDir) {
        var files = [],
            curFiles,
            nextDirs,
            prependcurDir = function(fname){
                return _path.join(curDir, fname);
            };

        waitCount++;
        fs.readdir(curDir, function(e, curFiles) {
            if (e) {
                fn(e);
                return;
            }
            waitCount--;

            curFiles = curFiles.map(prependcurDir);

            curFiles.forEach(function(it) {
                waitCount++;

                fs.stat(it, function(e, stat) {
                    waitCount--;

                    if (e) {
                        fn(e);
                    } else {
                        if (stat.isDirectory()) {
                            readdirRecursive(it);
                        }
                    }

                    if (waitCount == 0) {
                        fn(null, null);
                    }
                });
            });

            fn(null, curFiles.map(function(val) {
                // convert absolute paths to relative
                return _path.relative(baseDir, val);
            }));

            if (waitCount == 0) {
                fn(null, null);
            }
        });
    };

    readdirRecursive(baseDir);
};

/*  wrench.rmdirSyncRecursive("directory_path", forceDelete, failSilent);
 *
 *  Recursively dives through directories and obliterates everything about it. This is a
 *  Sync-function, which blocks things until it's done. No idea why anybody would want an
 *  Asynchronous version. :\
 */
exports.rmdirSyncRecursive = function(path, failSilent) {
    var files;

    try {
        files = fs.readdirSync(path);
    } catch (err) {
        if(failSilent) return;
        throw new Error(err.message);
    }

    /*  Loop through and delete everything in the sub-tree after checking it */
    for(var i = 0; i < files.length; i++) {
        var currFile = fs.lstatSync(_path.join(path, files[i]));

        if(currFile.isDirectory()) // Recursive function back to the beginning
            exports.rmdirSyncRecursive(_path.join(path, files[i]));

        else if(currFile.isSymbolicLink()) // Unlink symlinks
            fs.unlinkSync(_path.join(path, files[i]));

        else // Assume it's a file - perhaps a try/catch belongs here?
            fs.unlinkSync(_path.join(path, files[i]));
    }

    /*  Now that we know everything in the sub-tree has been deleted, we can delete the main
     directory. Huzzah for the shopkeep. */
    return fs.rmdirSync(path);
};

/*  wrench.copyDirSyncRecursive("directory_to_copy", "new_directory_location", opts);
 *
 *  Recursively dives through a directory and moves all its files to a new location. This is a
 *  Synchronous function, which blocks things until it's done. If you need/want to do this in
 *  an Asynchronous manner, look at wrench.copyDirRecursively() below.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.copyDirSyncRecursive = function(sourceDir, newDirLocation, opts) {
    if (!opts || !opts.preserve) {
        try {
            if(fs.statSync(newDirLocation).isDirectory()) exports.rmdirSyncRecursive(newDirLocation);
        } catch(e) { }
    }

    /*  Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */
    var checkDir = fs.statSync(sourceDir);
    try {
        fs.mkdirSync(newDirLocation, checkDir.mode);
    } catch (e) {
        //if the directory already exists, that's okay
        if (e.code !== 'EEXIST') throw e;
    }

    var files = fs.readdirSync(sourceDir);

    for(var i = 0; i < files.length; i++) {
        // ignores all files or directories which match the RegExp in opts.filter
		if(typeof opts !== 'undefined') {
            if(!opts.whitelist && opts.filter && files[i].match(opts.filter)) continue;
		    // if opts.whitelist is true every file or directory which doesn't match opts.filter will be ignored
		    if(opts.whitelist && opts.filter && !files[i].match(opts.filter)) continue;
            if (opts.excludeHiddenUnix && /^\./.test(files[i])) continue;
        }

        var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));

        var fCopyFile = function(srcFile, destFile) {
            if(typeof opts !== 'undefined' && opts.preserveFiles && fs.existsSync(destFile)) return;

            var contents = fs.readFileSync(srcFile);
            fs.writeFileSync(destFile, contents);
        };

        if(currFile.isDirectory()) {
            /*  recursion this thing right on back. */
            exports.copyDirSyncRecursive(_path.join(sourceDir, files[i]), _path.join(newDirLocation, files[i]), opts);
        } else if(currFile.isSymbolicLink()) {
            var symlinkFull = fs.readlinkSync(_path.join(sourceDir, files[i]));

            if (typeof opts !== 'undefined' && !opts.inflateSymlinks) {
                fs.symlinkSync(symlinkFull, _path.join(newDirLocation, files[i]));
                continue;
            }

            var tmpCurrFile = fs.lstatSync(_path.join(sourceDir, symlinkFull));
            if (tmpCurrFile.isDirectory()) {
                exports.copyDirSyncRecursive(_path.join(sourceDir, symlinkFull), _path.join(newDirLocation, files[i]), opts);
            } else {
                /*  At this point, we've hit a file actually worth copying... so copy it on over. */
                fCopyFile(_path.join(sourceDir, symlinkFull), _path.join(newDirLocation, files[i]));
            }
        } else {
            /*  At this point, we've hit a file actually worth copying... so copy it on over. */
            fCopyFile(_path.join(sourceDir, files[i]), _path.join(newDirLocation, files[i]));
        }
    }
};

/*  wrench.chmodSyncRecursive("directory", filemode);
 *
 *  Recursively dives through a directory and chmods everything to the desired mode. This is a
 *  Synchronous function, which blocks things until it's done.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.chmodSyncRecursive = function(sourceDir, filemode) {
    var files = fs.readdirSync(sourceDir);

    for(var i = 0; i < files.length; i++) {
        var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));

        if(currFile.isDirectory()) {
            /*  ...and recursion this thing right on back. */
            exports.chmodSyncRecursive(_path.join(sourceDir, files[i]), filemode);
        } else {
            /*  At this point, we've hit a file actually worth copying... so copy it on over. */
            fs.chmod(_path.join(sourceDir, files[i]), filemode);
        }
    }

    /*  Finally, chmod the parent directory */
    fs.chmod(sourceDir, filemode);
};

/*  wrench.chownSyncRecursive("directory", uid, gid);
 *
 *  Recursively dives through a directory and chowns everything to the desired user and group. This is a
 *  Synchronous function, which blocks things until it's done.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.chownSyncRecursive = function(sourceDir, uid, gid) {
    var files = fs.readdirSync(sourceDir);

    for(var i = 0; i < files.length; i++) {
        var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));

        if(currFile.isDirectory()) {
            /*  ...and recursion this thing right on back. */
            exports.chownSyncRecursive(_path.join(sourceDir, files[i]), uid, gid);
        } else {
            /*  At this point, we've hit a file actually worth chowning... so own it. */
            fs.chownSync(_path.join(sourceDir, files[i]), uid, gid);
        }
    }

    /*  Finally, chown the parent directory */
    fs.chownSync(sourceDir, uid, gid);
};

/*  wrench.rmdirRecursive("directory_path", callback);
 *
 *  Recursively dives through directories and obliterates everything about it.
 */
exports.rmdirRecursive = function rmdirRecursive(dir, clbk){
    fs.readdir(dir, function(err, files){
        if (err) return clbk(err);
        (function rmFile(err){
            if (err) return clbk(err);

            var filename = files.shift();
            if (filename === null || typeof filename == 'undefined')
                return fs.rmdir(dir, clbk);

            var file = dir+'/'+filename;
            fs.lstat(file, function(err, stat){
                if (err) return clbk(err);
                if (stat.isDirectory())
                    rmdirRecursive(file, rmFile);
                else
                    fs.unlink(file, rmFile);
            });
        })();
    });
};

/*  wrench.copyDirRecursive("directory_to_copy", "new_location", callback);
 *
 *  Recursively dives through a directory and moves all its files to a new
 *  location.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.copyDirRecursive = function copyDirRecursive(srcDir, newDir, clbk) {
    fs.stat(newDir, function(err, newDirStat){
        if (!err) return exports.rmdirRecursive(newDir, function(err){
            copyDirRecursive(srcDir, newDir, clbk);
        });

        fs.stat(srcDir, function(err, srcDirStat){
            if (err) return clbk(err);
            fs.mkdir(newDir, srcDirStat.mode, function(err){
                if (err) return clbk(err);
                fs.readdir(srcDir, function(err, files){
                    if (err) return clbk(err);
                    (function copyFiles(err){
                        if (err) return clbk(err);

                        var filename = files.shift();
                        if (filename === null || typeof filename == 'undefined')
                            return clbk();

                        var file = srcDir+'/'+filename,
                            newFile = newDir+'/'+filename;

                        fs.stat(file, function(err, fileStat){
                            if (fileStat.isDirectory())
                                copyDirRecursive(file, newFile, copyFiles);
                            else if (fileStat.isSymbolicLink())
                                fs.readlink(file, function(err, link){
                                    fs.symlink(link, newFile, copyFiles);
                                });
                            else
                                fs.readFile(file, function(err, data){
                                    fs.writeFile(newFile, data, copyFiles);
                                });
                        });
                    })();
                });
            });
        });
    });
};

var mkdirSyncRecursive = function(path, mode) {
    var self = this;
    path = _path.normalize(path)

    try {
        fs.mkdirSync(path, mode);
    } catch(err) {
        if(err.code == "ENOENT") {
            var slashIdx = path.lastIndexOf(_path.sep);

            if(slashIdx > 0) {
                var parentPath = path.substring(0, slashIdx);
                mkdirSyncRecursive(parentPath, mode);
                mkdirSyncRecursive(path, mode);
            } else {
                throw err;
            }
        } else if(err.code == "EEXIST") {
            return;
        } else {
            throw err;
        }
    }
};
exports.mkdirSyncRecursive = mkdirSyncRecursive;

exports.LineReader = function(filename, bufferSize) {
    this.bufferSize = bufferSize || 8192;
    this.buffer = "";
    this.fd = fs.openSync(filename, "r");
    this.currentPosition = 0;
};

exports.LineReader.prototype = {
    close: function() {
        return fs.closeSync(this.fd);
    },

    getBufferAndSetCurrentPosition: function(position) {
        var res = fs.readSync(this.fd, this.bufferSize, position, "ascii");

        this.buffer += res[0];
        if(res[1] === 0) {
            this.currentPosition = -1;
        } else {
            this.currentPosition = position + res[1];
        }

        return this.currentPosition;
    },

    hasNextLine: function() {
        while(this.buffer.indexOf('\n') === -1) {
            this.getBufferAndSetCurrentPosition(this.currentPosition);
            if(this.currentPosition === -1) return false;
        }

        if(this.buffer.indexOf("\n") > -1) return true;
        return false;
    },

    getNextLine: function() {
        var lineEnd = this.buffer.indexOf("\n"),
            result = this.buffer.substring(0, lineEnd);

        this.buffer = this.buffer.substring(result.length + 1, this.buffer.length);
        return result;
    }
};

// vim: et ts=4 sw=4
<commit_msg>removed unused variables<commit_after>/*  wrench.js
 *
 *  A collection of various utility functions I've found myself in need of
 *  for use with Node.js (http://nodejs.org/). This includes things like:
 *
 *  - Recursively deleting directories in Node.js (Sync, not Async)
 *  - Recursively copying directories in Node.js (Sync, not Async)
 *  - Recursively chmoding a directory structure from Node.js (Sync, not Async)
 *  - Other things that I'll add here as time goes on. Shhhh...
 *
 *  ~ Ryan McGrath (ryan [at] venodesigns.net)
 */

var fs = require("fs"),
    _path = require("path");

/*  wrench.readdirSyncRecursive("directory_path");
 *
 *  Recursively dives through directories and read the contents of all the
 *  children directories.
 */
exports.readdirSyncRecursive = function(baseDir) {
    baseDir = baseDir.replace(/\/$/, '');

    var readdirSyncRecursive = function(baseDir) {
        var files = [],
            curFiles,
            nextDirs,
            isDir = function(fname){
                return fs.statSync( _path.join(baseDir, fname) ).isDirectory();
            },
            prependBaseDir = function(fname){
                return _path.join(baseDir, fname);
            };

        curFiles = fs.readdirSync(baseDir);
        nextDirs = curFiles.filter(isDir);
        curFiles = curFiles.map(prependBaseDir);

        files = files.concat( curFiles );

        while (nextDirs.length) {
            files = files.concat( readdirSyncRecursive( _path.join(baseDir, nextDirs.shift()) ) );
        }

        return files;
    };

    // convert absolute paths to relative
    var fileList = readdirSyncRecursive(baseDir).map(function(val){
        return _path.relative(baseDir, val);
    });

    return fileList;
};

/*  wrench.readdirRecursive("directory_path", function(error, files) {});
 *
 *  Recursively dives through directories and read the contents of all the
 *  children directories.
 *
 *  Asynchronous, so returns results/error in callback.
 *  Callback receives the of files in currently recursed directory.
 *  When no more directories are left, callback is called with null for all arguments.
 *
 */
exports.readdirRecursive = function(baseDir, fn) {
    baseDir = baseDir.replace(/\/$/, '');

    var waitCount = 0;

    function readdirRecursive(curDir) {
        var prependcurDir = function(fname){
            return _path.join(curDir, fname);
        };

        waitCount++;
        fs.readdir(curDir, function(e, curFiles) {
            if (e) {
                fn(e);
                return;
            }
            waitCount--;

            curFiles = curFiles.map(prependcurDir);

            curFiles.forEach(function(it) {
                waitCount++;

                fs.stat(it, function(e, stat) {
                    waitCount--;

                    if (e) {
                        fn(e);
                    } else {
                        if (stat.isDirectory()) {
                            readdirRecursive(it);
                        }
                    }

                    if (waitCount == 0) {
                        fn(null, null);
                    }
                });
            });

            fn(null, curFiles.map(function(val) {
                // convert absolute paths to relative
                return _path.relative(baseDir, val);
            }));

            if (waitCount == 0) {
                fn(null, null);
            }
        });
    };

    readdirRecursive(baseDir);
};

/*  wrench.rmdirSyncRecursive("directory_path", forceDelete, failSilent);
 *
 *  Recursively dives through directories and obliterates everything about it. This is a
 *  Sync-function, which blocks things until it's done. No idea why anybody would want an
 *  Asynchronous version. :\
 */
exports.rmdirSyncRecursive = function(path, failSilent) {
    var files;

    try {
        files = fs.readdirSync(path);
    } catch (err) {
        if(failSilent) return;
        throw new Error(err.message);
    }

    /*  Loop through and delete everything in the sub-tree after checking it */
    for(var i = 0; i < files.length; i++) {
        var currFile = fs.lstatSync(_path.join(path, files[i]));

        if(currFile.isDirectory()) // Recursive function back to the beginning
            exports.rmdirSyncRecursive(_path.join(path, files[i]));

        else if(currFile.isSymbolicLink()) // Unlink symlinks
            fs.unlinkSync(_path.join(path, files[i]));

        else // Assume it's a file - perhaps a try/catch belongs here?
            fs.unlinkSync(_path.join(path, files[i]));
    }

    /*  Now that we know everything in the sub-tree has been deleted, we can delete the main
     directory. Huzzah for the shopkeep. */
    return fs.rmdirSync(path);
};

/*  wrench.copyDirSyncRecursive("directory_to_copy", "new_directory_location", opts);
 *
 *  Recursively dives through a directory and moves all its files to a new location. This is a
 *  Synchronous function, which blocks things until it's done. If you need/want to do this in
 *  an Asynchronous manner, look at wrench.copyDirRecursively() below.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.copyDirSyncRecursive = function(sourceDir, newDirLocation, opts) {
    if (!opts || !opts.preserve) {
        try {
            if(fs.statSync(newDirLocation).isDirectory()) exports.rmdirSyncRecursive(newDirLocation);
        } catch(e) { }
    }

    /*  Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */
    var checkDir = fs.statSync(sourceDir);
    try {
        fs.mkdirSync(newDirLocation, checkDir.mode);
    } catch (e) {
        //if the directory already exists, that's okay
        if (e.code !== 'EEXIST') throw e;
    }

    var files = fs.readdirSync(sourceDir);

    for(var i = 0; i < files.length; i++) {
        // ignores all files or directories which match the RegExp in opts.filter
		if(typeof opts !== 'undefined') {
            if(!opts.whitelist && opts.filter && files[i].match(opts.filter)) continue;
		    // if opts.whitelist is true every file or directory which doesn't match opts.filter will be ignored
		    if(opts.whitelist && opts.filter && !files[i].match(opts.filter)) continue;
            if (opts.excludeHiddenUnix && /^\./.test(files[i])) continue;
        }

        var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));

        var fCopyFile = function(srcFile, destFile) {
            if(typeof opts !== 'undefined' && opts.preserveFiles && fs.existsSync(destFile)) return;

            var contents = fs.readFileSync(srcFile);
            fs.writeFileSync(destFile, contents);
        };

        if(currFile.isDirectory()) {
            /*  recursion this thing right on back. */
            exports.copyDirSyncRecursive(_path.join(sourceDir, files[i]), _path.join(newDirLocation, files[i]), opts);
        } else if(currFile.isSymbolicLink()) {
            var symlinkFull = fs.readlinkSync(_path.join(sourceDir, files[i]));

            if (typeof opts !== 'undefined' && !opts.inflateSymlinks) {
                fs.symlinkSync(symlinkFull, _path.join(newDirLocation, files[i]));
                continue;
            }

            var tmpCurrFile = fs.lstatSync(_path.join(sourceDir, symlinkFull));
            if (tmpCurrFile.isDirectory()) {
                exports.copyDirSyncRecursive(_path.join(sourceDir, symlinkFull), _path.join(newDirLocation, files[i]), opts);
            } else {
                /*  At this point, we've hit a file actually worth copying... so copy it on over. */
                fCopyFile(_path.join(sourceDir, symlinkFull), _path.join(newDirLocation, files[i]));
            }
        } else {
            /*  At this point, we've hit a file actually worth copying... so copy it on over. */
            fCopyFile(_path.join(sourceDir, files[i]), _path.join(newDirLocation, files[i]));
        }
    }
};

/*  wrench.chmodSyncRecursive("directory", filemode);
 *
 *  Recursively dives through a directory and chmods everything to the desired mode. This is a
 *  Synchronous function, which blocks things until it's done.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.chmodSyncRecursive = function(sourceDir, filemode) {
    var files = fs.readdirSync(sourceDir);

    for(var i = 0; i < files.length; i++) {
        var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));

        if(currFile.isDirectory()) {
            /*  ...and recursion this thing right on back. */
            exports.chmodSyncRecursive(_path.join(sourceDir, files[i]), filemode);
        } else {
            /*  At this point, we've hit a file actually worth copying... so copy it on over. */
            fs.chmod(_path.join(sourceDir, files[i]), filemode);
        }
    }

    /*  Finally, chmod the parent directory */
    fs.chmod(sourceDir, filemode);
};

/*  wrench.chownSyncRecursive("directory", uid, gid);
 *
 *  Recursively dives through a directory and chowns everything to the desired user and group. This is a
 *  Synchronous function, which blocks things until it's done.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.chownSyncRecursive = function(sourceDir, uid, gid) {
    var files = fs.readdirSync(sourceDir);

    for(var i = 0; i < files.length; i++) {
        var currFile = fs.lstatSync(_path.join(sourceDir, files[i]));

        if(currFile.isDirectory()) {
            /*  ...and recursion this thing right on back. */
            exports.chownSyncRecursive(_path.join(sourceDir, files[i]), uid, gid);
        } else {
            /*  At this point, we've hit a file actually worth chowning... so own it. */
            fs.chownSync(_path.join(sourceDir, files[i]), uid, gid);
        }
    }

    /*  Finally, chown the parent directory */
    fs.chownSync(sourceDir, uid, gid);
};

/*  wrench.rmdirRecursive("directory_path", callback);
 *
 *  Recursively dives through directories and obliterates everything about it.
 */
exports.rmdirRecursive = function rmdirRecursive(dir, clbk){
    fs.readdir(dir, function(err, files){
        if (err) return clbk(err);
        (function rmFile(err){
            if (err) return clbk(err);

            var filename = files.shift();
            if (filename === null || typeof filename == 'undefined')
                return fs.rmdir(dir, clbk);

            var file = dir+'/'+filename;
            fs.lstat(file, function(err, stat){
                if (err) return clbk(err);
                if (stat.isDirectory())
                    rmdirRecursive(file, rmFile);
                else
                    fs.unlink(file, rmFile);
            });
        })();
    });
};

/*  wrench.copyDirRecursive("directory_to_copy", "new_location", callback);
 *
 *  Recursively dives through a directory and moves all its files to a new
 *  location.
 *
 *  Note: Directories should be passed to this function without a trailing slash.
 */
exports.copyDirRecursive = function copyDirRecursive(srcDir, newDir, clbk) {
    fs.stat(newDir, function(err, newDirStat){
        if (!err) return exports.rmdirRecursive(newDir, function(err){
            copyDirRecursive(srcDir, newDir, clbk);
        });

        fs.stat(srcDir, function(err, srcDirStat){
            if (err) return clbk(err);
            fs.mkdir(newDir, srcDirStat.mode, function(err){
                if (err) return clbk(err);
                fs.readdir(srcDir, function(err, files){
                    if (err) return clbk(err);
                    (function copyFiles(err){
                        if (err) return clbk(err);

                        var filename = files.shift();
                        if (filename === null || typeof filename == 'undefined')
                            return clbk();

                        var file = srcDir+'/'+filename,
                            newFile = newDir+'/'+filename;

                        fs.stat(file, function(err, fileStat){
                            if (fileStat.isDirectory())
                                copyDirRecursive(file, newFile, copyFiles);
                            else if (fileStat.isSymbolicLink())
                                fs.readlink(file, function(err, link){
                                    fs.symlink(link, newFile, copyFiles);
                                });
                            else
                                fs.readFile(file, function(err, data){
                                    fs.writeFile(newFile, data, copyFiles);
                                });
                        });
                    })();
                });
            });
        });
    });
};

var mkdirSyncRecursive = function(path, mode) {
    var self = this;
    path = _path.normalize(path)

    try {
        fs.mkdirSync(path, mode);
    } catch(err) {
        if(err.code == "ENOENT") {
            var slashIdx = path.lastIndexOf(_path.sep);

            if(slashIdx > 0) {
                var parentPath = path.substring(0, slashIdx);
                mkdirSyncRecursive(parentPath, mode);
                mkdirSyncRecursive(path, mode);
            } else {
                throw err;
            }
        } else if(err.code == "EEXIST") {
            return;
        } else {
            throw err;
        }
    }
};
exports.mkdirSyncRecursive = mkdirSyncRecursive;

exports.LineReader = function(filename, bufferSize) {
    this.bufferSize = bufferSize || 8192;
    this.buffer = "";
    this.fd = fs.openSync(filename, "r");
    this.currentPosition = 0;
};

exports.LineReader.prototype = {
    close: function() {
        return fs.closeSync(this.fd);
    },

    getBufferAndSetCurrentPosition: function(position) {
        var res = fs.readSync(this.fd, this.bufferSize, position, "ascii");

        this.buffer += res[0];
        if(res[1] === 0) {
            this.currentPosition = -1;
        } else {
            this.currentPosition = position + res[1];
        }

        return this.currentPosition;
    },

    hasNextLine: function() {
        while(this.buffer.indexOf('\n') === -1) {
            this.getBufferAndSetCurrentPosition(this.currentPosition);
            if(this.currentPosition === -1) return false;
        }

        if(this.buffer.indexOf("\n") > -1) return true;
        return false;
    },

    getNextLine: function() {
        var lineEnd = this.buffer.indexOf("\n"),
            result = this.buffer.substring(0, lineEnd);

        this.buffer = this.buffer.substring(result.length + 1, this.buffer.length);
        return result;
    }
};

// vim: et ts=4 sw=4
<|endoftext|>