<commit_before>
// Load modules

var Url = require('url');
var Http = require('http');
var Https = require('https');
var Stream = require('stream');
var Zlib = require('zlib');
var Hoek = require('hoek');
var Boom = require('boom');

// Declare internals

var internals = {};

exports.request = function (method, url, options, callback, _trace) {

    options = options || {};

    Hoek.assert(options.payload === null || options.payload === undefined || typeof options.payload === 'string' || options.payload instanceof Stream || Buffer.isBuffer(options.payload), 'options.payload must be a string, a Buffer, or a Stream');

    // Setup request

    var uri = Url.parse(url);
    var timeoutId;
    uri.method = method.toUpperCase();
    uri.headers = options.headers;

    if (options.rejectUnauthorized !== undefined && uri.protocol === 'https:') {
        uri.rejectUnauthorized = options.rejectUnauthorized;
    }

    var redirects = (options.hasOwnProperty('redirects') ? options.redirects : false);      // Needed to allow 0 as valid value when passed recursively

    _trace = (_trace || []);
    _trace.push({ method: uri.method, url: url });

    var client = (uri.protocol === 'https:' ? Https : Http);
    if (options.agent) {
        uri.agent = options.agent;
    }

    var req = client.request(uri);

    var shadow = null;                                                                      // A copy of the streamed request payload when redirects are enabled

    // Register handlers

    var isFinished = false;
    var finish = function (err, res) {

        if (!callback || err) {
            if (res) {
                res.destroy();
            }

            req.abort();
        }

        if (!isFinished) {
            isFinished = true;

            req.removeAllListeners();
            req.on('error', Hoek.ignore);
            clearTimeout(timeoutId);

            if (callback) {
                return callback(err, res);
            }
        }
    };

    req.once('error', function (err) {

        err.trace = _trace;
        return finish(Boom.badGateway('Client request error', err));
    });

    req.once('response', function (res) {

        // Pass-through response

        if (redirects === false ||
            [301, 302, 307, 308].indexOf(res.statusCode) === -1) {

            return finish(null, res);
        }

        // Redirection

        var redirectMethod = (res.statusCode === 301 || res.statusCode === 302 ? 'GET' : uri.method);
        var location = res.headers.location;

        res.destroy();

        if (redirects === 0) {
            return finish(Boom.badGateway('Maximum redirections reached', _trace));
        }

        if (!location) {
            return finish(Boom.badGateway('Received redirection without location', _trace));
        }

        if (!location.match(/^https?:/i)) {
            location = Url.resolve(uri.href, location);
        }

        var redirectOptions = {
            headers: options.headers,
            payload: shadow || options.payload,         // shadow must be ready at this point if set
            redirects: --redirects
        };

        return exports.request(redirectMethod, location, redirectOptions, finish, _trace);
    });

    if (options.timeout) {
        timeoutId = setTimeout(function () {

            return finish(Boom.gatewayTimeout('Client request timeout'));
        }, options.timeout);
    }

    // Write payload

    if (uri.method !== 'GET' &&
        uri.method !== 'HEAD' &&
        options.payload !== null &&
        options.payload !== undefined) {            // Value can be falsey

        if (options.payload instanceof Stream) {
            var stream = options.payload;

            if (redirects) {
                var collector = new internals.Tap();
                collector.once('finish', function () {

                    shadow = collector.collect();
                });

                stream = options.payload.pipe(collector);
            }

            stream.pipe(req);
            return;
        }

        req.write(options.payload);
    }

    // Finalize request

    req.end();
};

// Tap

internals.Tap = function () {

    Stream.Transform.call(this);
    this.buffers = [];
};

Hoek.inherits(internals.Tap, Stream.Transform);

internals.Tap.prototype._transform = function (chunk, encoding, next) {

    this.buffers.push(chunk);
    next(null, chunk);
};

internals.Tap.prototype.collect = function () {

    return exports.toReadableStream(this.buffers);
};

// read()

exports.read = function (res /*, [options], callback */) {

    var options = (arguments.length === 3 ? arguments[1] : {});
    var callback = (arguments.length === 3 ? arguments[2] : arguments[1]);

    // Set stream timeout

    var clientTimeout = options.timeout;
    var clientTimeoutId = null;

    if (clientTimeout &&
        clientTimeout > 0) {

        clientTimeoutId = setTimeout(function () {

            finish(Boom.clientTimeout());
        }, clientTimeout);
    }

    // Finish once

    var finish = function (err, buffer) {

        var sendJSON = function () {
            var contentType = ((res.headers && res.headers['content-type']) || '').toLowerCase();

            if (contentType !== 'application/json') {
                return callback(Boom.internal('JSON not returned'), null);
            }

            try {

                return callback(err, JSON.parse(buffer));
            } catch (parseError) {

                return callback(Boom.internal('Invalid JSON returned'), null);
            }
        };

        clearTimeout(clientTimeoutId);
        reader.removeAllListeners();
        res.removeListener('error', onError);
        res.removeListener('close', onClose);
        res.on('error', Hoek.ignore);

        if (!err && options.parseJSON === true) {
            return sendJSON();
        }

        return callback(err, buffer);
    };

    finish = Hoek.once(finish);

    // Hander errors

    var onError = function (err) {

        return finish(Boom.internal('Payload stream error', err));
    };

    var onClose = function () {

        return finish(Boom.internal('Payload stream closed prematurely'));
    };

    res.once('error', onError);
    res.once('close', onClose);

    // Read payload

    var reader = new internals.Recorder({ maxBytes: options.maxBytes });

    reader.once('error', function (err) {

        res.destroy();
        return finish(err);
    });

    reader.once('finish', function () {

        return finish(null, reader.collect());
    });

    res.pipe(reader);
};

// Recorder

internals.Recorder = function (options) {

    Stream.Writable.call(this);

    this.settings = Hoek.clone(options || {});
    this.buffers = [];
    this.length = 0;
};

Hoek.inherits(internals.Recorder, Stream.Writable);

internals.Recorder.prototype._write = function (chunk, encoding, next) {

    if (this.settings.maxBytes &&
        this.length + chunk.length > this.settings.maxBytes) {

        return this.emit('error', Boom.badRequest('Payload content length greater than maximum allowed: ' + this.settings.maxBytes));
    }

    this.length += chunk.length;
    this.buffers.push(chunk);
    next();
};

internals.Recorder.prototype.collect = function () {

    var buffer = (this.buffers.length === 0 ? new Buffer(0) : (this.buffers.length === 1 ? this.buffers[0] : Buffer.concat(this.buffers, this.length)));
    return buffer;
};

// toReadableStream()

exports.toReadableStream = function (payload, encoding) {

    return new internals.Payload(payload, encoding);
};

internals.Payload = function (payload, encoding) {

    Stream.Readable.call(this);
    this._data = [].concat(payload || '');
    this._encoding = encoding || 'utf8';
};

Hoek.inherits(internals.Payload, Stream.Readable);

internals.Payload.prototype._read = function (size) {

    for (var i = 0, il = this._data.length; i < il; ++i) {
        this.push(this._data[i], this._encoding);
    }

    this.push(null);
};

// parseCacheControl()

exports.parseCacheControl = function (field) {

    /*
        Cache-Control   = 1#cache-directive
        cache-directive = token [ "=" ( token / quoted-string ) ]
        token           = [^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+
        quoted-string   = "(?:[^"\\]|\\.)*"
    */

    //                             1: directive                                        =   2: token                                              3: quoted-string
    var regex = /(?:^|(?:\s*\,\s*))([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)(?:\=(?:([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)|(?:\"((?:[^"\\]|\\.)*)\")))?/g;

    var header = {};
    var err = field.replace(regex, function ($0, $1, $2, $3) {

        var value = $2 || $3;
        header[$1] = value ? value.toLowerCase() : true;
        return '';
    });

    if (header['max-age']) {
        try {
            var maxAge = parseInt(header['max-age'], 10);
            if (isNaN(maxAge)) {
                return null;
            }

            header['max-age'] = maxAge;
        }
        catch (err) { }
    }

    return (err ? null : header);
};

// Shortcuts

internals.shortcut = function (method, uri /*, options, callback */) {

    var options = (typeof arguments[2] === 'function' ? {} : arguments[2]);
    var callback = (typeof arguments[2] === 'function' ? arguments[2] : arguments[3]);

    exports.request(method, uri, options, function (err, res) {

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

        exports.read(res, options, function (err, payload) {

            if (options.parseJSON !== true) {
                payload = payload.toString();
            }

            return callback(err, res, payload);
        });
    });
};

exports.get = function (uri, options, callback) { internals.shortcut('GET', uri, options, callback); };
exports.post = function (uri, options, callback) { internals.shortcut('POST', uri, options, callback); };
exports.put = function (uri, options, callback) { internals.shortcut('PUT', uri, options, callback); };
exports.delete = function (uri, options, callback) { internals.shortcut('DELETE', uri, options, callback); };
<commit_msg>Add toString() on buffer parsing<commit_after>
// Load modules

var Url = require('url');
var Http = require('http');
var Https = require('https');
var Stream = require('stream');
var Zlib = require('zlib');
var Hoek = require('hoek');
var Boom = require('boom');

// Declare internals

var internals = {};

exports.request = function (method, url, options, callback, _trace) {

    options = options || {};

    Hoek.assert(options.payload === null || options.payload === undefined || typeof options.payload === 'string' || options.payload instanceof Stream || Buffer.isBuffer(options.payload), 'options.payload must be a string, a Buffer, or a Stream');

    // Setup request

    var uri = Url.parse(url);
    var timeoutId;
    uri.method = method.toUpperCase();
    uri.headers = options.headers;

    if (options.rejectUnauthorized !== undefined && uri.protocol === 'https:') {
        uri.rejectUnauthorized = options.rejectUnauthorized;
    }

    var redirects = (options.hasOwnProperty('redirects') ? options.redirects : false);      // Needed to allow 0 as valid value when passed recursively

    _trace = (_trace || []);
    _trace.push({ method: uri.method, url: url });

    var client = (uri.protocol === 'https:' ? Https : Http);
    if (options.agent) {
        uri.agent = options.agent;
    }

    var req = client.request(uri);

    var shadow = null;                                                                      // A copy of the streamed request payload when redirects are enabled

    // Register handlers

    var isFinished = false;
    var finish = function (err, res) {

        if (!callback || err) {
            if (res) {
                res.destroy();
            }

            req.abort();
        }

        if (!isFinished) {
            isFinished = true;

            req.removeAllListeners();
            req.on('error', Hoek.ignore);
            clearTimeout(timeoutId);

            if (callback) {
                return callback(err, res);
            }
        }
    };

    req.once('error', function (err) {

        err.trace = _trace;
        return finish(Boom.badGateway('Client request error', err));
    });

    req.once('response', function (res) {

        // Pass-through response

        if (redirects === false ||
            [301, 302, 307, 308].indexOf(res.statusCode) === -1) {

            return finish(null, res);
        }

        // Redirection

        var redirectMethod = (res.statusCode === 301 || res.statusCode === 302 ? 'GET' : uri.method);
        var location = res.headers.location;

        res.destroy();

        if (redirects === 0) {
            return finish(Boom.badGateway('Maximum redirections reached', _trace));
        }

        if (!location) {
            return finish(Boom.badGateway('Received redirection without location', _trace));
        }

        if (!location.match(/^https?:/i)) {
            location = Url.resolve(uri.href, location);
        }

        var redirectOptions = {
            headers: options.headers,
            payload: shadow || options.payload,         // shadow must be ready at this point if set
            redirects: --redirects
        };

        return exports.request(redirectMethod, location, redirectOptions, finish, _trace);
    });

    if (options.timeout) {
        timeoutId = setTimeout(function () {

            return finish(Boom.gatewayTimeout('Client request timeout'));
        }, options.timeout);
    }

    // Write payload

    if (uri.method !== 'GET' &&
        uri.method !== 'HEAD' &&
        options.payload !== null &&
        options.payload !== undefined) {            // Value can be falsey

        if (options.payload instanceof Stream) {
            var stream = options.payload;

            if (redirects) {
                var collector = new internals.Tap();
                collector.once('finish', function () {

                    shadow = collector.collect();
                });

                stream = options.payload.pipe(collector);
            }

            stream.pipe(req);
            return;
        }

        req.write(options.payload);
    }

    // Finalize request

    req.end();
};

// Tap

internals.Tap = function () {

    Stream.Transform.call(this);
    this.buffers = [];
};

Hoek.inherits(internals.Tap, Stream.Transform);

internals.Tap.prototype._transform = function (chunk, encoding, next) {

    this.buffers.push(chunk);
    next(null, chunk);
};

internals.Tap.prototype.collect = function () {

    return exports.toReadableStream(this.buffers);
};

// read()

exports.read = function (res /*, [options], callback */) {

    var options = (arguments.length === 3 ? arguments[1] : {});
    var callback = (arguments.length === 3 ? arguments[2] : arguments[1]);

    // Set stream timeout

    var clientTimeout = options.timeout;
    var clientTimeoutId = null;

    if (clientTimeout &&
        clientTimeout > 0) {

        clientTimeoutId = setTimeout(function () {

            finish(Boom.clientTimeout());
        }, clientTimeout);
    }

    // Finish once

    var finish = function (err, buffer) {

        var sendJSON = function () {
            var contentType = ((res.headers && res.headers['content-type']) || '').toLowerCase();

            if (contentType !== 'application/json') {
                return callback(Boom.internal('JSON not returned'), null);
            }

            try {

                return callback(err, JSON.parse(buffer.toString()));
            } catch (parseError) {

                return callback(Boom.internal('Invalid JSON returned'), null);
            }
        };

        clearTimeout(clientTimeoutId);
        reader.removeAllListeners();
        res.removeListener('error', onError);
        res.removeListener('close', onClose);
        res.on('error', Hoek.ignore);

        if (!err && options.parseJSON === true) {
            return sendJSON();
        }

        return callback(err, buffer);
    };

    finish = Hoek.once(finish);

    // Hander errors

    var onError = function (err) {

        return finish(Boom.internal('Payload stream error', err));
    };

    var onClose = function () {

        return finish(Boom.internal('Payload stream closed prematurely'));
    };

    res.once('error', onError);
    res.once('close', onClose);

    // Read payload

    var reader = new internals.Recorder({ maxBytes: options.maxBytes });

    reader.once('error', function (err) {

        res.destroy();
        return finish(err);
    });

    reader.once('finish', function () {

        return finish(null, reader.collect());
    });

    res.pipe(reader);
};

// Recorder

internals.Recorder = function (options) {

    Stream.Writable.call(this);

    this.settings = Hoek.clone(options || {});
    this.buffers = [];
    this.length = 0;
};

Hoek.inherits(internals.Recorder, Stream.Writable);

internals.Recorder.prototype._write = function (chunk, encoding, next) {

    if (this.settings.maxBytes &&
        this.length + chunk.length > this.settings.maxBytes) {

        return this.emit('error', Boom.badRequest('Payload content length greater than maximum allowed: ' + this.settings.maxBytes));
    }

    this.length += chunk.length;
    this.buffers.push(chunk);
    next();
};

internals.Recorder.prototype.collect = function () {

    var buffer = (this.buffers.length === 0 ? new Buffer(0) : (this.buffers.length === 1 ? this.buffers[0] : Buffer.concat(this.buffers, this.length)));
    return buffer;
};

// toReadableStream()

exports.toReadableStream = function (payload, encoding) {

    return new internals.Payload(payload, encoding);
};

internals.Payload = function (payload, encoding) {

    Stream.Readable.call(this);
    this._data = [].concat(payload || '');
    this._encoding = encoding || 'utf8';
};

Hoek.inherits(internals.Payload, Stream.Readable);

internals.Payload.prototype._read = function (size) {

    for (var i = 0, il = this._data.length; i < il; ++i) {
        this.push(this._data[i], this._encoding);
    }

    this.push(null);
};

// parseCacheControl()

exports.parseCacheControl = function (field) {

    /*
        Cache-Control   = 1#cache-directive
        cache-directive = token [ "=" ( token / quoted-string ) ]
        token           = [^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+
        quoted-string   = "(?:[^"\\]|\\.)*"
    */

    //                             1: directive                                        =   2: token                                              3: quoted-string
    var regex = /(?:^|(?:\s*\,\s*))([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)(?:\=(?:([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)|(?:\"((?:[^"\\]|\\.)*)\")))?/g;

    var header = {};
    var err = field.replace(regex, function ($0, $1, $2, $3) {

        var value = $2 || $3;
        header[$1] = value ? value.toLowerCase() : true;
        return '';
    });

    if (header['max-age']) {
        try {
            var maxAge = parseInt(header['max-age'], 10);
            if (isNaN(maxAge)) {
                return null;
            }

            header['max-age'] = maxAge;
        }
        catch (err) { }
    }

    return (err ? null : header);
};

// Shortcuts

internals.shortcut = function (method, uri /*, options, callback */) {

    var options = (typeof arguments[2] === 'function' ? {} : arguments[2]);
    var callback = (typeof arguments[2] === 'function' ? arguments[2] : arguments[3]);

    exports.request(method, uri, options, function (err, res) {

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

        exports.read(res, options, function (err, payload) {

            if (options.parseJSON !== true) {
                payload = payload.toString();
            }

            return callback(err, res, payload);
        });
    });
};

exports.get = function (uri, options, callback) { internals.shortcut('GET', uri, options, callback); };
exports.post = function (uri, options, callback) { internals.shortcut('POST', uri, options, callback); };
exports.put = function (uri, options, callback) { internals.shortcut('PUT', uri, options, callback); };
exports.delete = function (uri, options, callback) { internals.shortcut('DELETE', uri, options, callback); };
<|endoftext|>


<commit_before>'use strict';

// Load modules
const request = require('request');
const Hoek = require('hoek');
const AWS  = require('aws-sdk');

// Declare internals
const internals = {};

internals.getStoragePathForKey = function (key) {

    // Remove leading/trailing slashes
    if (key.id) {
        key.id = encodeURIComponent(key.id.replace(/(^\/|\/$)/g, ''));
    }

    if (key.segment) {
        key.segment = encodeURIComponent(key.segment.replace(/(^\/|\/$)/g, ''));
    }

    return key.id === '' ?
        key.segment :
        key.segment + '/' + key.id;
};

internals.parseBody = function (res, body) {

    if (res.headers['content-type'] === 'text/plain' && Buffer.isBuffer(body)) {
        body = body.toString();
    }

    if (res.headers['content-type'] === 'application/json') {
        /* eslint-disable brace-style */
        try {
            body = JSON.parse(body);
        } catch (e) {}
        /* eslint-enable brace-style */
    }

    return body;
};

internals.testBucketAccess = function (client, bucket, callback) {

    const putParams = {
        Bucket : bucket,
        Key    : internals.getStoragePathForKey({ segment: 'catbox-s3', id: 'accesstest' }),
        ACL    : 'public-read',
        Body   : 'ok'
    };

    const getParams = {
        Bucket : bucket,
        Key    : internals.getStoragePathForKey({ segment: 'catbox-s3', id: 'accesstest' })
    };

    client.putObject(putParams, (err) => {

        Hoek.assert(!err, `Error writing to bucket ${bucket}`);

        client.getObject(getParams, (err, data) => {

            Hoek.assert(!err && data.Body.toString('utf8') === 'ok', `Error reading from bucket ${bucket}`);
            callback();
        });
    });
};

exports = module.exports = internals.Connection = function S3Cache (options) {

    Hoek.assert(this.constructor === internals.Connection, 'S3 cache client must be instantiated using new');
    Hoek.assert(options && options.bucket, 'Invalid Amazon S3 bucket value');
    Hoek.assert(options && options.accessKeyId, 'Invalid Amazon S3 accessKeyId value');
    Hoek.assert(options && options.secretAccessKey, 'Invalid Amazon S3 secretAccessKey value');

    this.settings = Hoek.clone(options || {});
    this.client = null;
};

internals.Connection.prototype.start = function (callback) {

    const self = this;

    const clientOptions = {
        accessKeyId     : this.settings.accessKeyId,
        secretAccessKey : this.settings.secretAccessKey
    };

    if (this.settings.region) {
        clientOptions.region = this.settings.region;
    }

    this.client = new AWS.S3(clientOptions);

    internals.testBucketAccess(this.client, this.settings.bucket, (err, data) => {

        if (err) {
            self.isConnected = false;
            return callback(err);
        }

        self.isConnected = true;
        callback();
    });
};

internals.Connection.prototype.stop = function () {

    if (this.client) {
        this.client = null;
        this.isConnected = false;
    }
};

internals.Connection.prototype.isReady = function () {

    return this.isConnected;
};

internals.Connection.prototype.validateSegmentName = function (name) {

    if (!name) {
        return new Error('Empty string');
    }

    if (name.indexOf('\0') !== -1) {
        return new Error('Includes null character');
    }

    if (name.length < 3 || name.length > 63) {
        return new Error('Must be between 3 and 63 characters');
    }

    return null;
};

internals.Connection.prototype.get = function (key, callback) {

    if (!this.isConnected) {
        return callback(new Error('Connection not started'));
    }

    const baseUrl = this.settings.region ?
        'http://s3-' + this.settings.region + '.amazonaws.com/' :
        'http://s3.amazonaws.com/';

    const requestOptions = {
        url      : baseUrl + this.settings.bucket + '/' + internals.getStoragePathForKey(key),
        encoding : null
    };

    request.get(requestOptions, (err, res, body) => {

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

        if (res.statusCode !== 200) {
            return callback(null, null);
        }

        const now    = new Date().getTime();
        const stored = new Date(res.headers['x-amz-meta-catbox-stored']);
        let ttl      = Number(res.headers['x-amz-meta-catbox-ttl']) || 0;

        // Calculate remaining ttl
        ttl = (stored.getTime() + ttl) - now;

        // Cache item has expired
        if (ttl <= 0) {
            return callback(null, null);
        }

        const result = {
            item   : internals.parseBody(res, body),
            stored : stored,
            ttl    : ttl
        };

        callback(null, result);
    });
};

internals.Connection.prototype.set = function (key, value, ttl, callback) {

    let type = 'application/octet-stream';

    if (!this.isConnected) {
        return callback(new Error('Connection not started'));
    }

    if (['Object', 'Array'].indexOf(value.constructor.name) === 0) {
        /* eslint-disable brace-style */
        try {
            value = JSON.stringify(value);
            type = 'application/json';
        } catch (e) {
            return callback(new Error('Could not convert object to JSON'));
        }
        /* eslint-enable brace-style */
    }

    if (['String', 'Number', 'Boolean'].indexOf(value.constructor.name) === 0) {
        type = 'text/plain';
    }

    const now = new Date();
    const params = {
        Bucket      : this.settings.bucket,
        Key         : internals.getStoragePathForKey(key),
        ACL         : 'public-read',
        Expires     : new Date(now.getTime() + ttl),
        ContentType : type,
        Body        : value
    };

    const req = this.client.putObject(params);
    req.on('build', () => {

        req.httpRequest.headers['x-amz-meta-catbox-stored'] = now;
        req.httpRequest.headers['x-amz-meta-catbox-ttl']    = ttl;
    });
    req.send((err) => {

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

internals.Connection.prototype.drop = function (key, callback) {

    if (!this.isConnected) {
        return callback(new Error('Connection not started'));
    }

    const params = {
        Bucket : this.settings.bucket,
        Key    : internals.getStoragePathForKey(key)
    };

    this.client.deleteObject(params, (err) => {

        if (err) {
            return callback(err);
        }
        callback();
    });
};
<commit_msg>Fix check for variable type<commit_after>'use strict';

// Load modules
const request = require('request');
const Hoek = require('hoek');
const AWS  = require('aws-sdk');

// Declare internals
const internals = {};

internals.getStoragePathForKey = function (key) {

    // Remove leading/trailing slashes
    if (key.id) {
        key.id = encodeURIComponent(key.id.replace(/(^\/|\/$)/g, ''));
    }

    if (key.segment) {
        key.segment = encodeURIComponent(key.segment.replace(/(^\/|\/$)/g, ''));
    }

    return key.id === '' ?
        key.segment :
        key.segment + '/' + key.id;
};

internals.parseBody = function (res, body) {

    if (res.headers['content-type'] === 'text/plain' && Buffer.isBuffer(body)) {
        body = body.toString();
    }

    if (res.headers['content-type'] === 'application/json') {
        /* eslint-disable brace-style */
        try {
            body = JSON.parse(body);
        } catch (e) {}
        /* eslint-enable brace-style */
    }

    return body;
};

internals.testBucketAccess = function (client, bucket, callback) {

    const putParams = {
        Bucket : bucket,
        Key    : internals.getStoragePathForKey({ segment: 'catbox-s3', id: 'accesstest' }),
        ACL    : 'public-read',
        Body   : 'ok'
    };

    const getParams = {
        Bucket : bucket,
        Key    : internals.getStoragePathForKey({ segment: 'catbox-s3', id: 'accesstest' })
    };

    client.putObject(putParams, (err) => {

        Hoek.assert(!err, `Error writing to bucket ${bucket}`);

        client.getObject(getParams, (err, data) => {

            Hoek.assert(!err && data.Body.toString('utf8') === 'ok', `Error reading from bucket ${bucket}`);
            callback();
        });
    });
};

exports = module.exports = internals.Connection = function S3Cache (options) {

    Hoek.assert(this.constructor === internals.Connection, 'S3 cache client must be instantiated using new');
    Hoek.assert(options && options.bucket, 'Invalid Amazon S3 bucket value');
    Hoek.assert(options && options.accessKeyId, 'Invalid Amazon S3 accessKeyId value');
    Hoek.assert(options && options.secretAccessKey, 'Invalid Amazon S3 secretAccessKey value');

    this.settings = Hoek.clone(options || {});
    this.client = null;
};

internals.Connection.prototype.start = function (callback) {

    const self = this;

    const clientOptions = {
        accessKeyId     : this.settings.accessKeyId,
        secretAccessKey : this.settings.secretAccessKey
    };

    if (this.settings.region) {
        clientOptions.region = this.settings.region;
    }

    this.client = new AWS.S3(clientOptions);

    internals.testBucketAccess(this.client, this.settings.bucket, (err, data) => {

        if (err) {
            self.isConnected = false;
            return callback(err);
        }

        self.isConnected = true;
        callback();
    });
};

internals.Connection.prototype.stop = function () {

    if (this.client) {
        this.client = null;
        this.isConnected = false;
    }
};

internals.Connection.prototype.isReady = function () {

    return this.isConnected;
};

internals.Connection.prototype.validateSegmentName = function (name) {

    if (!name) {
        return new Error('Empty string');
    }

    if (name.indexOf('\0') !== -1) {
        return new Error('Includes null character');
    }

    if (name.length < 3 || name.length > 63) {
        return new Error('Must be between 3 and 63 characters');
    }

    return null;
};

internals.Connection.prototype.get = function (key, callback) {

    if (!this.isConnected) {
        return callback(new Error('Connection not started'));
    }

    const baseUrl = this.settings.region ?
        'http://s3-' + this.settings.region + '.amazonaws.com/' :
        'http://s3.amazonaws.com/';

    const requestOptions = {
        url      : baseUrl + this.settings.bucket + '/' + internals.getStoragePathForKey(key),
        encoding : null
    };

    request.get(requestOptions, (err, res, body) => {

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

        if (res.statusCode !== 200) {
            return callback(null, null);
        }

        const now    = new Date().getTime();
        const stored = new Date(res.headers['x-amz-meta-catbox-stored']);
        let ttl      = Number(res.headers['x-amz-meta-catbox-ttl']) || 0;

        // Calculate remaining ttl
        ttl = (stored.getTime() + ttl) - now;

        // Cache item has expired
        if (ttl <= 0) {
            return callback(null, null);
        }

        const result = {
            item   : internals.parseBody(res, body),
            stored : stored,
            ttl    : ttl
        };

        callback(null, result);
    });
};

internals.Connection.prototype.set = function (key, value, ttl, callback) {

    let type = 'application/octet-stream';

    if (!this.isConnected) {
        return callback(new Error('Connection not started'));
    }

    if (['Object', 'Array'].indexOf(value.constructor.name) > -1) {
        /* eslint-disable brace-style */
        try {
            value = JSON.stringify(value);
            type = 'application/json';
        } catch (e) {
            return callback(new Error('Could not convert object to JSON'));
        }
        /* eslint-enable brace-style */
    }

    if (['String', 'Number', 'Boolean'].indexOf(value.constructor.name) > -1) {
        type = 'text/plain';
    }

    const now = new Date();
    const params = {
        Bucket      : this.settings.bucket,
        Key         : internals.getStoragePathForKey(key),
        ACL         : 'public-read',
        Expires     : new Date(now.getTime() + ttl),
        ContentType : type,
        Body        : value
    };

    const req = this.client.putObject(params);
    req.on('build', () => {

        req.httpRequest.headers['x-amz-meta-catbox-stored'] = now;
        req.httpRequest.headers['x-amz-meta-catbox-ttl']    = ttl;
    });
    req.send((err) => {

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

internals.Connection.prototype.drop = function (key, callback) {

    if (!this.isConnected) {
        return callback(new Error('Connection not started'));
    }

    const params = {
        Bucket : this.settings.bucket,
        Key    : internals.getStoragePathForKey(key)
    };

    this.client.deleteObject(params, (err) => {

        if (err) {
            return callback(err);
        }
        callback();
    });
};
<|endoftext|>


<commit_before>import { Color, pt, Rectangle } from "lively.graphics";
import { arr, promise } from "lively.lang";
import { connect, disconnect } from "lively.bindings";
import { Window, morph, show } from "../index.js";
import { GridLayout } from "../layout.js";
import { JavaScriptEditorPlugin } from "./js/editor-plugin.js";
import config from "../config.js";

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// commands
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

function commandsForBrowser(browser) {
  var pList = browser.get("packageList"),
      mList = browser.get("moduleList"),
      editor = browser.get("sourceEditor"),
      world = browser.world();

  return [
    {name: "focus list with selection", exec: () => focusList(mList.selection ? mList : pList)},
    {name: "focus package list", exec: () => focusList(pList)},
    {name: "focus module list", exec: () => focusList(mList)},
    {name: "focus source editor", exec: () => { editor.focus(); editor.show(); return true; }},

    {name: "reload module", exec: async () => {
      var m = browser.selectedModule;
      if (!m) return browser.world().inform("No module selected", {requester: browser});
      try {
        await (await browser.systemInterface()).interactivelyReloadModule(null, m.name);
      } catch(err) {
        browser.world().inform(`Error while reloading ${m.name}:\n${err.stack || err}`, {requester: browser});
        return true;
      }
      show(`Reloaded ${m.name}`);
      browser.selectModuleNamed(m.nameInPackage);
      return true;
    }},

    {name: "load or add module", exec: async (browser) => {
      var p = browser.selectedPackage;
      try {
        var mods = await (await browser.systemInterface()).interactivelyAddModule(null, p ? p.address : null, browser.world());
      } catch(e) {
        e === "Canceled" ?
          show(e) :
          browser.world().inform(`Error while trying to load modules:\n${e.stack || e}`, {requester: browser});
        return;
      }

      mods.forEach(({name, error}) =>
        error ? show(`Error while loading module ${name}: ${error.stack || error}`) :
                show(`Module ${name} loaded`))
      await browser.updateModuleList(p);
      mods.length && browser.selectModuleNamed(mods[0].name);
      return true;
    }},

    {name: "run all tests in module",
     exec: (browser) => {
       var m = browser.selectedModule;
        if (!m) return browser.world().inform("No module selected", {requester: browser});
       return runTestsInModule(browser, m.name, null);
     }},

    {name: "run tests at point",
     exec: async (browser) => {
       var m = browser.selectedModule;
        if (!m) return browser.world().inform("No module selected", {requester: browser});

        var {parse, query: {nodesAt}} = await System.import("lively.ast");
        var ed = browser.get("sourceEditor");
        var source = ed.textString;
        var parsed = parse(source);
        var nodes = nodesAt(ed.document.positionToIndex(ed.cursorPosition), parsed)
          .filter(n => n.type === "CallExpression" && n.callee.name && n.callee.name.match(/describe|it/) && n.arguments[0].type === "Literal")
            .map(n => ({
              type: n.callee.name.match(/describe/) ? "suite" : "test",
              title: n.arguments[0].value,
          }))

        if (!nodes.length)
          return browser.world().inform("No test at " + JSON.stringify(ed.cursorPosition), {requester: browser});

       var spec = {fullTitle: arr.pluck(nodes, "title").join(" "), type: arr.last(nodes).type, file: m.name}
       return runTestsInModule(browser, m.name, spec);
     }}
  ]

  function focusList(list) {
    list.scrollSelectionIntoView();
    list.update();
    list.show();
    list.focus();
    return list
  }

  function runTestsInModule(browser, moduleName, spec) {
    var runner = browser.get("test runner window");
    if (!runner)
      runner = world.execCommand("open test runner");
    if (runner.minimized)
      runner.toggleMinimize();
    return spec ?
      runner.targetMorph[spec.type === "suite" ? "runSuite" : "runTest"](spec.fullTitle):
      runner.targetMorph.runTestFile(moduleName);
  }

}

// -=-=-=-=-=-
// Browser UI
// -=-=-=-=-=-

export class Browser extends Window {

  static async browse(packageName, moduleName, textPosition = {row: 0, column: 0}, browserOrProps = {}) {
    var browser = browserOrProps instanceof Browser ? browserOrProps : new this(browserOrProps);
    if (!browser.world())
      browser.openInWorldNearHand();
    await browser.whenRendered();
    if (packageName) await browser.selectPackageNamed(packageName);
    if (packageName && moduleName) await browser.selectModuleNamed(moduleName);
    if (textPosition) {
      var text = browser.get("sourceEditor");
      text.cursorPosition = textPosition;
      text.centerRow(textPosition.row);
    }
    return browser;
  }

  constructor(props = {}) {
    super({
      name: "browser",
      extent: pt(700,600),
      ...props,
      targetMorph: this.build()
    });
    this.state = {packageUpdateInProgress: null, moduleUpdateInProgress: null};
    this.onLoad();
  }

  get isBrowser() { return true; }

  focus() {
    this.get("sourceEditor").focus();
  }

  whenPackageUpdated() { return this.state.packageUpdateInProgress || Promise.resolve(); }
  whenModuleUpdated() { return this.state.moduleUpdateInProgress || Promise.resolve(); }

  build() {
    var style = {borderWidth: 1, borderColor: Color.gray, fontSize: 14, fontFamily: "Helvetica Neue, Arial, sans-serif"},
        textStyle = {borderWidth: 1, borderColor: Color.gray, type: "text", ...config.codeEditor.defaultStyle, plugins: [new JavaScriptEditorPlugin(config.codeEditor.defaultTheme)]},
        container = morph({
          ...style,
          layout: new GridLayout({
            grid: [["packageList", "moduleList"],
                   ["sourceEditor", "sourceEditor"]]}),
          submorphs: [
            {name: "packageList", type: "list", ...style},
            {name: "moduleList", type: "list", ...style},
            {name: "sourceEditor", ...textStyle, doSave() { this.owner.owner/*FIXME*/.save(); }}
          ]
        });
    // FIXME? how to specify that directly??
    container.layout.grid.row(0).adjustProportion(-1/5);
    container.get("sourceEditor").__defineGetter__("evalEnvironment", function () {
      var browser = this.getWindow();
      if (!browser.selectedModule) throw new Error("Browser has no module selected");
      return {
        targetModule: browser.selectedModule.name,
        context: this.doitContext || this.owner.doitContext || this
      }
    });
    return container;
  }

  get keybindings() {
    return [
      {keys: "Alt-Up", command: "focus list with selection"},
      {keys: "F1", command: "focus package list"},
      {keys: "F2", command: "focus module list"},
      {keys: "F3|Alt-Down", command: "focus source editor"},
      {keys: "Alt-R", command: "reload module"},
      {keys: "Alt-L", command: "load or add module"},
      {keys: "Ctrl-C Ctrl-T", command: "run all tests in module"},
      {keys: "Ctrl-C T", command: "run tests at point"},
    ].concat(super.keybindings);
  }

  get commands() { return commandsForBrowser(this); }

  reset() {
    connect(this.get("packageList"), "selection", this, "onPackageSelected");
    connect(this.get("moduleList"), 'selection', this, 'onModuleSelected');
    this.get("packageList").items = [];
    this.get("moduleList").items = [];
    this.get("sourceEditor").textString = ""
  }

  async systemInterface() {
    return (await System.import("lively-system-interface")).localInterface;
  }

  get selectedModule() {
    return this.get("moduleList").selection;
  }

  get selectedPackage() {
    return this.get("packageList").selection;
  }

  async packageResources(p) {
    // await this.packageResources(this.selectedPackage)
    return (await (await this.systemInterface()).resourcesOfPackage(p.address))
      .filter(({name}) => name.endsWith(".js") || name.endsWith(".json"));
  }

  async onLoad() {
    this.reset();
    this.get("packageList").items = (await (await this.systemInterface()).getPackages()).map(p => ({isListItem: true, string: p.name, value: p}));
  }

  async selectPackageNamed(pName) {
    var p = this.get("packageList").selection = this.get("packageList").values.find(({address, name}) => address === pName || name === pName);
    await this.whenPackageUpdated();
    return p;
  }

  async selectModuleNamed(mName) {
    var m = this.get("moduleList").selection = this.get("moduleList").values.find(({nameInPackage, name}) => mName === name || mName === nameInPackage);
    await this.whenModuleUpdated();
    return m;
  }

  async searchForModuleAndSelect(moduleURI) {
    // moduleURI = System.decanonicalize("lively.vm")
    // var x= await (that.getWindow().searchForModuleAndSelect(System.decanonicalize("lively.vm")));

    var {selectedModule, selectedPackage} = this;
    if (selectedModule && selectedModule.name === moduleURI)
      return selectedModule;

    var system = await this.systemInterface(),
        mods = await system.getModules(),
        m = mods.find(({name}) => name === moduleURI),
        p = m && await system.getPackageForModule(m.name);

    if (!p) return null;
    await this.selectPackageNamed(p.address);
    await this.selectModuleNamed(m.name);
    return this.selectedModule;
  }

  async onPackageSelected(p) {
    if (!this.state.packageUpdateInProgress) {
      var deferred = promise.deferred();
      this.state.packageUpdateInProgress = deferred.promise;
    }

    try {
      if (!p) {
        this.get("moduleList").items = [];
        this.get("sourceEditor").textString = "";
        this.title = "browser";
        return;
      }

      this.title = "browser – " + p.name;

      this.get("packageList").scrollSelectionIntoView();
      this.get("moduleList").selection = null;

      await this.updateModuleList(p);
    } finally {
      this.state.packageUpdateInProgress = null;
      deferred && deferred.resolve(p);
    }
  }

  async onModuleSelected(m) {

    var pack = this.get("packageList").selection;

    if (!m) {
      this.get("sourceEditor").textString = "";
      this.title = "browser – " + pack && pack.name || "";
      return;
    }

    if (!this.state.moduleUpdateInProgress) {
      var deferred = promise.deferred()
      this.state.moduleUpdateInProgress = deferred.promise;
    }

    try {
      var system = await this.systemInterface();

      if (!m.isLoaded && m.name.endsWith("js")) {
        var err;
        try {
          await System.import(m.name);
        } catch(e) { err = e; }

        if (err) this.world().logError(err);

        var p = await system.getPackage(pack.address),
            isLoadedNow = p.modules.map(ea => ea.name).includes(m.name);

        if (isLoadedNow) {
          Object.assign(pack, p);
          m.isLoaded = true;
          // await this.selectPackageNamed(pack.address);
          await this.updateModuleList();
          this.state.moduleUpdateInProgress = null;
          await this.selectModuleNamed(m.name);
          m = this.selectedModule;
          if (deferred)
            this.state.moduleUpdateInProgress = deferred.promise;
          return;
        }
      }

      this.get("moduleList").scrollSelectionIntoView();
      this.title = "browser – " + pack.name + "/" + m.nameInPackage;
      var source = await system.moduleRead(m.name);
      this.get("sourceEditor").textString = source;
      this.get("sourceEditor").cursorPosition = {row: 0, column: 0}
    } finally {
      this.state.moduleUpdateInProgress = null;
      deferred && deferred.resolve(m);
    }
  }

  async updateModuleList(p = this.selectedPackage) {
    if (!p) return;
    var mods = await this.packageResources(p);

    this.get("moduleList").items = mods
      .sort((a, b) => {
        if (a.isLoaded && !b.isLoaded) return -1;
        if (!a.isLoaded && b.isLoaded) return 1;
        if (a.nameInPackage.toLowerCase() < b.nameInPackage.toLowerCase()) return -1;
        if (a.nameInPackage.toLowerCase() == b.nameInPackage.toLowerCase()) return 0;
        return 1
      })
      .map(m => ({string: m.nameInPackage + (m.isLoaded ? "" : " [not loaded]"), value: m, isListItem: true}));

    await this.get("moduleList").whenRendered();
  }

  async save() {
    var module = this.get("moduleList").selection;
    if (!module) return show("Cannot save, no module selected");

    var content = this.get("sourceEditor").textString,
        system = await this.systemInterface();

    try {
      if (module.isLoaded) { // is loaded in runtime
        await system.interactivelyChangeModule(
          null, module.name, content,
          {targetModule: module.name, doEval: true});
      } else await system.moduleWrite(module.name, content);
    } catch(err) { return this.world().logError(err); }

    this.world().setStatusMessage("saved " + module.nameInPackage, Color.green);
  }

}<commit_msg>browser: detach save() call from owner chain<commit_after>import { Color, pt, Rectangle } from "lively.graphics";
import { arr, promise } from "lively.lang";
import { connect, disconnect } from "lively.bindings";
import { Window, morph, show } from "../index.js";
import { GridLayout } from "../layout.js";
import { JavaScriptEditorPlugin } from "./js/editor-plugin.js";
import config from "../config.js";

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// commands
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

function commandsForBrowser(browser) {
  var pList = browser.get("packageList"),
      mList = browser.get("moduleList"),
      editor = browser.get("sourceEditor"),
      world = browser.world();

  return [
    {name: "focus list with selection", exec: () => focusList(mList.selection ? mList : pList)},
    {name: "focus package list", exec: () => focusList(pList)},
    {name: "focus module list", exec: () => focusList(mList)},
    {name: "focus source editor", exec: () => { editor.focus(); editor.show(); return true; }},

    {name: "reload module", exec: async () => {
      var m = browser.selectedModule;
      if (!m) return browser.world().inform("No module selected", {requester: browser});
      try {
        await (await browser.systemInterface()).interactivelyReloadModule(null, m.name);
      } catch(err) {
        browser.world().inform(`Error while reloading ${m.name}:\n${err.stack || err}`, {requester: browser});
        return true;
      }
      show(`Reloaded ${m.name}`);
      browser.selectModuleNamed(m.nameInPackage);
      return true;
    }},

    {name: "load or add module", exec: async (browser) => {
      var p = browser.selectedPackage;
      try {
        var mods = await (await browser.systemInterface()).interactivelyAddModule(null, p ? p.address : null, browser.world());
      } catch(e) {
        e === "Canceled" ?
          show(e) :
          browser.world().inform(`Error while trying to load modules:\n${e.stack || e}`, {requester: browser});
        return;
      }

      mods.forEach(({name, error}) =>
        error ? show(`Error while loading module ${name}: ${error.stack || error}`) :
                show(`Module ${name} loaded`))
      await browser.updateModuleList(p);
      mods.length && browser.selectModuleNamed(mods[0].name);
      return true;
    }},

    {name: "run all tests in module",
     exec: (browser) => {
       var m = browser.selectedModule;
        if (!m) return browser.world().inform("No module selected", {requester: browser});
       return runTestsInModule(browser, m.name, null);
     }},

    {name: "run tests at point",
     exec: async (browser) => {
       var m = browser.selectedModule;
        if (!m) return browser.world().inform("No module selected", {requester: browser});

        var {parse, query: {nodesAt}} = await System.import("lively.ast");
        var ed = browser.get("sourceEditor");
        var source = ed.textString;
        var parsed = parse(source);
        var nodes = nodesAt(ed.document.positionToIndex(ed.cursorPosition), parsed)
          .filter(n => n.type === "CallExpression" && n.callee.name && n.callee.name.match(/describe|it/) && n.arguments[0].type === "Literal")
            .map(n => ({
              type: n.callee.name.match(/describe/) ? "suite" : "test",
              title: n.arguments[0].value,
          }))

        if (!nodes.length)
          return browser.world().inform("No test at " + JSON.stringify(ed.cursorPosition), {requester: browser});

       var spec = {fullTitle: arr.pluck(nodes, "title").join(" "), type: arr.last(nodes).type, file: m.name}
       return runTestsInModule(browser, m.name, spec);
     }}
  ]

  function focusList(list) {
    list.scrollSelectionIntoView();
    list.update();
    list.show();
    list.focus();
    return list
  }

  function runTestsInModule(browser, moduleName, spec) {
    var runner = browser.get("test runner window");
    if (!runner)
      runner = world.execCommand("open test runner");
    if (runner.minimized)
      runner.toggleMinimize();
    return spec ?
      runner.targetMorph[spec.type === "suite" ? "runSuite" : "runTest"](spec.fullTitle):
      runner.targetMorph.runTestFile(moduleName);
  }

}

// -=-=-=-=-=-
// Browser UI
// -=-=-=-=-=-

export class Browser extends Window {

  static async browse(packageName, moduleName, textPosition = {row: 0, column: 0}, browserOrProps = {}) {
    var browser = browserOrProps instanceof Browser ? browserOrProps : new this(browserOrProps);
    if (!browser.world())
      browser.openInWorldNearHand();
    await browser.whenRendered();
    if (packageName) await browser.selectPackageNamed(packageName);
    if (packageName && moduleName) await browser.selectModuleNamed(moduleName);
    if (textPosition) {
      var text = browser.get("sourceEditor");
      text.cursorPosition = textPosition;
      text.centerRow(textPosition.row);
    }
    return browser;
  }

  constructor(props = {}) {
    super({
      name: "browser",
      extent: pt(700,600),
      ...props,
      targetMorph: this.build()
    });
    this.state = {packageUpdateInProgress: null, moduleUpdateInProgress: null};
    this.onLoad();
  }

  get isBrowser() { return true; }

  focus() {
    this.get("sourceEditor").focus();
  }

  whenPackageUpdated() { return this.state.packageUpdateInProgress || Promise.resolve(); }
  whenModuleUpdated() { return this.state.moduleUpdateInProgress || Promise.resolve(); }

  build() {
    var style = {borderWidth: 1, borderColor: Color.gray, fontSize: 14, fontFamily: "Helvetica Neue, Arial, sans-serif"},
        textStyle = {borderWidth: 1, borderColor: Color.gray, type: "text", ...config.codeEditor.defaultStyle, plugins: [new JavaScriptEditorPlugin(config.codeEditor.defaultTheme)]},
        container = morph({
          ...style,
          layout: new GridLayout({
            grid: [["packageList", "moduleList"],
                   ["sourceEditor", "sourceEditor"]]}),
          submorphs: [
            {name: "packageList", type: "list", ...style},
            {name: "moduleList", type: "list", ...style},
            {name: "sourceEditor", ...textStyle, doSave: () => { this.save(); }}
          ]
        });
    // FIXME? how to specify that directly??
    container.layout.grid.row(0).adjustProportion(-1/5);
    container.get("sourceEditor").__defineGetter__("evalEnvironment", function () {
      var browser = this.getWindow();
      if (!browser.selectedModule) throw new Error("Browser has no module selected");
      return {
        targetModule: browser.selectedModule.name,
        context: this.doitContext || this.owner.doitContext || this
      }
    });
    return container;
  }

  get keybindings() {
    return [
      {keys: "Alt-Up", command: "focus list with selection"},
      {keys: "F1", command: "focus package list"},
      {keys: "F2", command: "focus module list"},
      {keys: "F3|Alt-Down", command: "focus source editor"},
      {keys: "Alt-R", command: "reload module"},
      {keys: "Alt-L", command: "load or add module"},
      {keys: "Ctrl-C Ctrl-T", command: "run all tests in module"},
      {keys: "Ctrl-C T", command: "run tests at point"},
    ].concat(super.keybindings);
  }

  get commands() { return commandsForBrowser(this); }

  reset() {
    connect(this.get("packageList"), "selection", this, "onPackageSelected");
    connect(this.get("moduleList"), 'selection', this, 'onModuleSelected');
    this.get("packageList").items = [];
    this.get("moduleList").items = [];
    this.get("sourceEditor").textString = ""
  }

  async systemInterface() {
    return (await System.import("lively-system-interface")).localInterface;
  }

  get selectedModule() {
    return this.get("moduleList").selection;
  }

  get selectedPackage() {
    return this.get("packageList").selection;
  }

  async packageResources(p) {
    // await this.packageResources(this.selectedPackage)
    return (await (await this.systemInterface()).resourcesOfPackage(p.address))
      .filter(({name}) => name.endsWith(".js") || name.endsWith(".json"));
  }

  async onLoad() {
    this.reset();
    this.get("packageList").items = (await (await this.systemInterface()).getPackages()).map(p => ({isListItem: true, string: p.name, value: p}));
  }

  async selectPackageNamed(pName) {
    var p = this.get("packageList").selection = this.get("packageList").values.find(({address, name}) => address === pName || name === pName);
    await this.whenPackageUpdated();
    return p;
  }

  async selectModuleNamed(mName) {
    var m = this.get("moduleList").selection = this.get("moduleList").values.find(({nameInPackage, name}) => mName === name || mName === nameInPackage);
    await this.whenModuleUpdated();
    return m;
  }

  async searchForModuleAndSelect(moduleURI) {
    // moduleURI = System.decanonicalize("lively.vm")
    // var x= await (that.getWindow().searchForModuleAndSelect(System.decanonicalize("lively.vm")));

    var {selectedModule, selectedPackage} = this;
    if (selectedModule && selectedModule.name === moduleURI)
      return selectedModule;

    var system = await this.systemInterface(),
        mods = await system.getModules(),
        m = mods.find(({name}) => name === moduleURI),
        p = m && await system.getPackageForModule(m.name);

    if (!p) return null;
    await this.selectPackageNamed(p.address);
    await this.selectModuleNamed(m.name);
    return this.selectedModule;
  }

  async onPackageSelected(p) {
    if (!this.state.packageUpdateInProgress) {
      var deferred = promise.deferred();
      this.state.packageUpdateInProgress = deferred.promise;
    }

    try {
      if (!p) {
        this.get("moduleList").items = [];
        this.get("sourceEditor").textString = "";
        this.title = "browser";
        return;
      }

      this.title = "browser – " + p.name;

      this.get("packageList").scrollSelectionIntoView();
      this.get("moduleList").selection = null;

      await this.updateModuleList(p);
    } finally {
      this.state.packageUpdateInProgress = null;
      deferred && deferred.resolve(p);
    }
  }

  async onModuleSelected(m) {

    var pack = this.get("packageList").selection;

    if (!m) {
      this.get("sourceEditor").textString = "";
      this.title = "browser – " + pack && pack.name || "";
      return;
    }

    if (!this.state.moduleUpdateInProgress) {
      var deferred = promise.deferred()
      this.state.moduleUpdateInProgress = deferred.promise;
    }

    try {
      var system = await this.systemInterface();

      if (!m.isLoaded && m.name.endsWith("js")) {
        var err;
        try {
          await System.import(m.name);
        } catch(e) { err = e; }

        if (err) this.world().logError(err);

        var p = await system.getPackage(pack.address),
            isLoadedNow = p.modules.map(ea => ea.name).includes(m.name);

        if (isLoadedNow) {
          Object.assign(pack, p);
          m.isLoaded = true;
          // await this.selectPackageNamed(pack.address);
          await this.updateModuleList();
          this.state.moduleUpdateInProgress = null;
          await this.selectModuleNamed(m.name);
          m = this.selectedModule;
          if (deferred)
            this.state.moduleUpdateInProgress = deferred.promise;
          return;
        }
      }

      this.get("moduleList").scrollSelectionIntoView();
      this.title = "browser – " + pack.name + "/" + m.nameInPackage;
      var source = await system.moduleRead(m.name);
      this.get("sourceEditor").textString = source;
      this.get("sourceEditor").cursorPosition = {row: 0, column: 0}
    } finally {
      this.state.moduleUpdateInProgress = null;
      deferred && deferred.resolve(m);
    }
  }

  async updateModuleList(p = this.selectedPackage) {
    if (!p) return;
    var mods = await this.packageResources(p);

    this.get("moduleList").items = mods
      .sort((a, b) => {
        if (a.isLoaded && !b.isLoaded) return -1;
        if (!a.isLoaded && b.isLoaded) return 1;
        if (a.nameInPackage.toLowerCase() < b.nameInPackage.toLowerCase()) return -1;
        if (a.nameInPackage.toLowerCase() == b.nameInPackage.toLowerCase()) return 0;
        return 1
      })
      .map(m => ({string: m.nameInPackage + (m.isLoaded ? "" : " [not loaded]"), value: m, isListItem: true}));

    await this.get("moduleList").whenRendered();
  }

  async save() {
    var module = this.get("moduleList").selection;
    if (!module) return show("Cannot save, no module selected");

    var content = this.get("sourceEditor").textString,
        system = await this.systemInterface();

    try {
      if (module.isLoaded) { // is loaded in runtime
        await system.interactivelyChangeModule(
          null, module.name, content,
          {targetModule: module.name, doEval: true});
      } else await system.moduleWrite(module.name, content);
    } catch(err) { return this.world().logError(err); }

    this.world().setStatusMessage("saved " + module.nameInPackage, Color.green);
  }

}<|endoftext|>


<commit_before>'use strict'
var fs = require('fs')
var decodeAudio = require('audio-decode')
var request = require('request')
var load = require('./load')

/**
 * Load audio
 */
module.exports = function (path, options) {
  var opts = Object.assign({ decode: decode, fetch: fetch }, options)
  return load(path, opts)
}

// A fix to audio-decode module
function decode (buffer) {
  if (buffer instanceof ArrayBuffer) buffer = Buffer(buffer)
  return decodeAudio(buffer)
}

function isPath (url) { return /^[\\\/\.]/.test(url) }
function fetch (url, type) {
  return isPath(url) ? readFile(url) : sendRequest(url, type)
}

function readFile (url) {
  return new Promise(function (done, reject) {
    fs.readFile(url, function (err, data) {
      err ? reject(err) : done(data.buffer)
    })
  })
}

function sendRequest (url, type) {
  var encoding = type === 'arraybuffer' ? null : 'utf8'
  return new Promise(function (done, reject) {
    request({ url: url, encoding: encoding }, function (err, resp, body) {
      if (err) reject(err)
      else if (resp.statusCode !== 200) reject('Status code: ' + resp.statusCode)
      else done(body)
    })
  })
}
<commit_msg>Remove audio-decode wrapper.<commit_after>'use strict'
var fs = require('fs')
var decode = require('audio-decode')
var request = require('request')
var load = require('./load')

/**
 * Load audio
 */
module.exports = function (path, options) {
  var opts = Object.assign({ decode: decode, fetch: fetch }, options)
  return load(path, opts)
}

function isPath (url) { return /^[\\\/\.]/.test(url) }
function fetch (url, type) {
  return isPath(url) ? readFile(url) : sendRequest(url, type)
}

function readFile (url) {
  return new Promise(function (done, reject) {
    fs.readFile(url, function (err, data) {
      err ? reject(err) : done(data.buffer)
    })
  })
}

function sendRequest (url, type) {
  var encoding = type === 'arraybuffer' ? null : 'utf8'
  return new Promise(function (done, reject) {
    request({ url: url, encoding: encoding }, function (err, resp, body) {
      if (err) reject(err)
      else if (resp.statusCode !== 200) reject('Status code: ' + resp.statusCode)
      else done(body)
    })
  })
}
<|endoftext|>


<commit_before>exports = module.exports = function http(id) {
  var map = {
    'server': './server'
  };

  var mid = map[id];
  if (mid) {
    return require(mid);
  }
};

exports.used = function(container) {
  // Register specs so objects can be auto-wired by interface.
  container.add('server');
}
<commit_msg>Simplify manifest.<commit_after>exports = module.exports = {
  'server': require('./server')
};<|endoftext|>


<commit_before>
var fs = require('fs')
  , path = require('path')

  , _ = require('lodash')
  , async = require('async')
  , mkdirp = require('mkdirp')
  , EventEmitter2 = require('eventemitter2').EventEmitter2
  , Loader = require('strider-extension-loader')

  , core = require('strider-runner-core')

  , JobData = require('./jobdata')

// timeout for callbacks. Helps to kill misbehaving plugins, etc
function t(time, done) {
  if (arguments.length === 1) {
    done = time
    time = 2000
  }
  var error = new Error('Callback took too long (max: ' + time + ')')
  var waiting = true
  function handler() {
    if (!waiting) return
    clearTimeout(timeout)
    waiting = false
    done.apply(this, arguments)
  }
  var timeout = setTimeout(function () {
    if (!waiting) return
    waiting = false
    done(error)
  }, time)
  return handler
}

module.exports = Runner

/*
 * Options:
 *    pty: use 'pty'
 *    logger:
 *    io: the means of communication with the job worker
 *    pluginDir: the directory in which to look for plugins
 *    dataDir: the directory in which to clone/test/etc
 */
function Runner(emitter, config) {
  console.log('RUNNER INIT')
  var dotStrider = path.join(process.env.HOME || '/', '.strider')
  this.config = _.extend({
    pty: false,
    io: new EventEmitter2({
      wildcard: true
    }),
    logger: console,
    processJob: core.process,
    pluginDir: path.join(__dirname, '../node_modules'),
    dataDir: process.env.STRIDER_CLONE_DEST || dotStrider
  }, config)
  this.emitter = emitter
  this.log = this.config.logger.log
  this.queue = async.queue(this.processJob.bind(this), 1)
  this.io = this.config.io
  this.hooks = []
  this.jobdata = new JobData(this.io)
  this.attach()
}

function initDataDir(base, job) {
  // XXX this doesn't support "pull only"; it will always clone a new repo.
  var name = job.project.name.replace('/', '.') + '-' + job._id
    , baseDir = path.join(base, name)
    , dataDir = path.join(baseDir, 'code')
  if (!fs.existsSync(dataDir)) {
    mkdirp.sync(dataDir)
  }
  return [baseDir, dataDir]
}

Runner.prototype = {
  id: 'simple-runner',

  // public API
  loadExtensions: function (dirs, done) {
    var self = this
      , loader = new Loader()
    loader.collectExtensions(dirs, function (err) {
      if (err) return done(err)
      loader.initWorkerExtensions({}, function (err, extensions) {
        if (err) return done(err)
        self.extensions = extensions
        done(null)
      })
    })
  },

  // Determine which jobs are zombies. This is called when strider
  // starts up.
  //
  // jobs: unfinished jobs that belong to this runner
  // done(err, zombies)
  findZombies: function (jobs, done) {
    done(null, jobs)
  },

  // private API
  attach: function () {
    var self = this
    this.emitter.on('job.new', this.queueJob.bind(this))
    this.emitter.on('job.cancel', this.cancelJob.bind(this))
    function proxy(id) {
      // proxy up job.status.* updates as browser events
      var job = self.jobdata.get(id)
        , project = job && job.data.project.name
      if (!job) return
      self.emitter.emit('browser.update', project, this.event, [].slice.call(arguments))
    }
    this.io.on('job.status.*', proxy)
    this.io.on('job.status.*.*', proxy)
    this.io.on('job.status.*.*.*', proxy)
    this.io.on('job.cancelled', function (id) {
      self.emitter.emit('job.cancelled', id, self.jobdata.pop(id))
      // our running job was cancelled, so we need to start the next one
      async.setImmediate(self.queue.process)
    })
    // proxy up plugin events
    this.io.on('plugin.*', function () {
      self.emitter.emit.apply(self.emitter, [this.event].concat([].slice.call(arguments)))
    })
  },

  queueJob: function (job, config) {
    if (config.runner.id !== this.id) return
    var now = new Date()
    this.jobdata.add(job)
    this.log('[runner:' + this.id + '] Got new job')
    this.emitter.emit('browser.update', job.project.name, 'job.status.queued', [job._id, now])
    this.queue.push({job: job, config: config})
  },

  cancelJob: function (id) {
    for (var i=0; i<this.queue.tasks.length; i++) {
      if (this.queue.tasks[i].data.job._id === id) {
        this.queue.tasks.splice(i, 1)
        this.emitter.emit('job.cancelled', id, this.jobdata.pop(id))
        return
      }
    }
    // if it wasn't found in the queue, fire to the worker in case it is in process
    this.io.emit('job.cancel', id)
  },

  // initialize the provider and job plugins for a job
  // dirs = [baseDir, dataDir]
  plugins: function (user, config, job, dirs, done) {
    var self = this
      , extensions = self.extensions
    var tasks = [function (next) {
      var plugin = self.extensions.provider[job.project.provider.id]
        , msg
      if (!plugin) {
        msg = 'Provider plugin "' + job.project.provider.id + '" not found in this environment!'
        return next(new Error(msg))
      }
      console.log('Initializing provider', job.project.provider.id)
      var finished = t(function (err, provider) {
        if (provider) provider.id = job.project.provider.id
        next(err, provider)
      })
      if (!plugin.hosted) {
        return plugin.init(dirs[1], job.project.provider.config, job, finished)
      }
      plugin.init(dirs[1], user.account(job.project.provider), job.project.provider.config, job, finished)
    }]
    // XXX: do we want/need more things in the context? See
    // extension-loader readme for what gets passed in to the
    // individual phase functions as "Phase context"
    var context = {
      baseDir: dirs[0],
      dataDir: dirs[1]
    }
    var bad = config.plugins.some(function (plugin) {
      if (!plugin.enabled) {
        console.warn('I got a disabled plugin', plugin)
        return
      }
      if (!self.extensions.job[plugin.id]) {
        console.warn('Plugin not found', plugin.id)
        done(new Error('Plugin required but not found: ' + plugin.id))
        return true
      }
      tasks.push(function (next) {
        var fn = self.extensions.job[plugin.id].init
          , cb = t(function (err, obj) {
              if (obj) obj.id = plugin.id
              next(err, obj)
            })
        if (fn.length === 3) fn(plugin.config, context, cb)
        else if (fn.length === 4) fn(plugin.config, job, context, cb)
        else fn(user.jobplugins[plugin.id], plugin.config, job, context, cb)
      })
    })
    if (bad) return
    async.parallel(tasks, function (err, results) {
      if (err) return done(err)
      done(null, {
        provider: results.shift(),
        jobplugins: results
      })
    })
  },

  processJob: function (data, next) {
    var job = data.job
      , config = data.config
      , dirs = initDataDir(this.config.dataDir, job)
      , now = new Date()
      , self = this
    this.jobdata.get(job._id).started = now
    this.emitter.emit('browser.update', job.project.name, 'job.status.started', [job._id, now])
    this.plugins(job.project.creator, config, job, dirs, function (err, workers) {
      if (err) {
        var jobdata = self.jobdata.pop(job._id)
        jobdata.errored = true
        jobdata.error = {
          message: err.message,
          stack: err.stack
        }
        // self.emitter.emit('browser.update', job.project.name, 'job.status.errored', [job._id, jobdata.error])
        delete jobdata.data
        jobdata.finished = new Date()
        self.emitter.emit('job.done', jobdata)
        next(null)
        return
      }
      self.config.processJob(job, workers.provider, workers.jobplugins, {
        baseDir: dirs[0],
        dataDir: dirs[1],
        io: self.config.io,
        log: console.log,
        error: console.error,
        logger: console
      }, function (err) {
        var jobdata = self.jobdata.pop(job._id)
        if (err) {
          jobdata.errored = true
          jobdata.error = {
            message: err.message,
            stack: err.stack
          }
          self.emitter.emit('browser.update', job.project.name, 'job.status.errored', [job._id, jobdata.error])
        }
        delete jobdata.data
        jobdata.finished = new Date()
        self.emitter.emit('job.done', jobdata)
        next(null)
      })
    })
  }
};

<commit_msg>bugfix<commit_after>
var fs = require('fs')
  , path = require('path')

  , _ = require('lodash')
  , async = require('async')
  , mkdirp = require('mkdirp')
  , EventEmitter2 = require('eventemitter2').EventEmitter2
  , Loader = require('strider-extension-loader')

  , core = require('strider-runner-core')

  , JobData = require('./jobdata')

// timeout for callbacks. Helps to kill misbehaving plugins, etc
function t(time, done) {
  if (arguments.length === 1) {
    done = time
    time = 2000
  }
  var error = new Error('Callback took too long (max: ' + time + ')')
  var waiting = true
  function handler() {
    if (!waiting) return
    clearTimeout(timeout)
    waiting = false
    done.apply(this, arguments)
  }
  var timeout = setTimeout(function () {
    if (!waiting) return
    waiting = false
    done(error)
  }, time)
  return handler
}

module.exports = Runner

/*
 * Options:
 *    pty: use 'pty'
 *    logger:
 *    io: the means of communication with the job worker
 *    pluginDir: the directory in which to look for plugins
 *    dataDir: the directory in which to clone/test/etc
 */
function Runner(emitter, config) {
  console.log('RUNNER INIT')
  var dotStrider = path.join(process.env.HOME || '/', '.strider')
  this.config = _.extend({
    pty: false,
    io: new EventEmitter2({
      wildcard: true
    }),
    logger: console,
    processJob: core.process,
    pluginDir: path.join(__dirname, '../node_modules'),
    dataDir: process.env.STRIDER_CLONE_DEST || dotStrider
  }, config)
  this.emitter = emitter
  this.log = this.config.logger.log
  this.queue = async.queue(this.processJob.bind(this), 1)
  this.io = this.config.io
  this.hooks = []
  this.jobdata = new JobData(this.io)
  this.attach()
}

function initDataDir(base, job) {
  // XXX this doesn't support "pull only"; it will always clone a new repo.
  var name = job.project.name.replace('/', '.') + '-' + job._id
    , baseDir = path.join(base, name)
    , dataDir = path.join(baseDir, 'code')
  if (!fs.existsSync(dataDir)) {
    mkdirp.sync(dataDir)
  }
  return [baseDir, dataDir]
}

Runner.prototype = {
  id: 'simple-runner',

  // public API
  loadExtensions: function (dirs, done) {
    var self = this
      , loader = new Loader()
    loader.collectExtensions(dirs, function (err) {
      if (err) return done(err)
      loader.initWorkerExtensions({}, function (err, extensions) {
        if (err) return done(err)
        self.extensions = extensions
        done(null)
      })
    })
  },

  // Determine which jobs are zombies. This is called when strider
  // starts up.
  //
  // jobs: unfinished jobs that belong to this runner
  // done(err, zombies)
  findZombies: function (jobs, done) {
    done(null, jobs)
  },

  // private API
  attach: function () {
    var self = this
    this.emitter.on('job.new', this.queueJob.bind(this))
    this.emitter.on('job.cancel', this.cancelJob.bind(this))
    function proxy(id) {
      // proxy up job.status.* updates as browser events
      var job = self.jobdata.get(id)
        , project = job && job.data.project.name
      if (!job) return
      self.emitter.emit('browser.update', project, this.event, [].slice.call(arguments))
    }
    this.io.on('job.status.*', proxy)
    this.io.on('job.status.*.*', proxy)
    this.io.on('job.status.*.*.*', proxy)
    this.io.on('job.cancelled', function (id) {
      self.emitter.emit('job.cancelled', id, self.jobdata.pop(id))
      // our running job was cancelled, so we need to start the next one
      async.setImmediate(self.queue.process)
    })
    // proxy up plugin events
    this.io.on('plugin.*', function () {
      self.emitter.emit.apply(self.emitter, [this.event].concat([].slice.call(arguments)))
    })
  },

  queueJob: function (job, config) {
    if (config.runner.id !== this.id) return
    var now = new Date()
    this.jobdata.add(job)
    this.log('[runner:' + this.id + '] Got new job')
    this.emitter.emit('browser.update', job.project.name, 'job.status.queued', [job._id, now])
    this.queue.push({job: job, config: config})
  },

  cancelJob: function (id) {
    for (var i=0; i<this.queue.tasks.length; i++) {
      if (this.queue.tasks[i].data.job._id === id) {
        this.queue.tasks.splice(i, 1)
        this.emitter.emit('job.cancelled', id, this.jobdata.pop(id))
        return
      }
    }
    // if it wasn't found in the queue, fire to the worker in case it is in process
    this.io.emit('job.cancel', id)
  },

  // initialize the provider and job plugins for a job
  // dirs = [baseDir, dataDir]
  plugins: function (user, config, job, dirs, done) {
    var self = this
      , extensions = self.extensions
    var tasks = [function (next) {
      var plugin = self.extensions.provider[job.project.provider.id]
        , msg
      if (!plugin) {
        msg = 'Provider plugin "' + job.project.provider.id + '" not found in this environment!'
        return next(new Error(msg))
      }
      console.log('Initializing provider', job.project.provider.id)
      var finished = t(function (err, provider) {
        if (provider) provider.id = job.project.provider.id
        next(err, provider)
      })
      if (!plugin.hosted) {
        return plugin.init(dirs[1], job.project.provider.config, job, finished)
      }
      plugin.init(dirs[1], user.account(job.project.provider), job.project.provider.config, job, finished)
    }]
    // XXX: do we want/need more things in the context? See
    // extension-loader readme for what gets passed in to the
    // individual phase functions as "Phase context"
    var context = {
      baseDir: dirs[0],
      dataDir: dirs[1]
    }
    var bad = config.plugins.some(function (plugin) {
      if (!plugin.enabled) {
        console.warn('I got a disabled plugin', plugin)
        return
      }
      if (!self.extensions.job[plugin.id]) {
        console.warn('Plugin not found', plugin.id)
        done(new Error('Plugin required but not found: ' + plugin.id))
        return true
      }
      tasks.push(function (next) {
        var fn = self.extensions.job[plugin.id].init
          , cb = t(function (err, obj) {
              if (obj) obj.id = plugin.id
              next(err, obj)
            })
        if (fn.length === 3) fn(plugin.config, context, cb)
        else if (fn.length === 4) fn(plugin.config, job, context, cb)
        else fn(user.jobplugins && user.jobplugins[plugin.id], plugin.config, job, context, cb)
      })
    })
    if (bad) return
    async.parallel(tasks, function (err, results) {
      if (err) return done(err)
      done(null, {
        provider: results.shift(),
        jobplugins: results
      })
    })
  },

  processJob: function (data, next) {
    var job = data.job
      , config = data.config
      , dirs = initDataDir(this.config.dataDir, job)
      , now = new Date()
      , self = this
    this.jobdata.get(job._id).started = now
    this.emitter.emit('browser.update', job.project.name, 'job.status.started', [job._id, now])
    this.plugins(job.project.creator, config, job, dirs, function (err, workers) {
      if (err) {
        var jobdata = self.jobdata.pop(job._id)
        jobdata.errored = true
        jobdata.error = {
          message: err.message,
          stack: err.stack
        }
        // self.emitter.emit('browser.update', job.project.name, 'job.status.errored', [job._id, jobdata.error])
        delete jobdata.data
        jobdata.finished = new Date()
        self.emitter.emit('job.done', jobdata)
        next(null)
        return
      }
      self.config.processJob(job, workers.provider, workers.jobplugins, {
        baseDir: dirs[0],
        dataDir: dirs[1],
        io: self.config.io,
        log: console.log,
        error: console.error,
        logger: console
      }, function (err) {
        var jobdata = self.jobdata.pop(job._id)
        if (err) {
          jobdata.errored = true
          jobdata.error = {
            message: err.message,
            stack: err.stack
          }
          self.emitter.emit('browser.update', job.project.name, 'job.status.errored', [job._id, jobdata.error])
        }
        delete jobdata.data
        jobdata.finished = new Date()
        self.emitter.emit('job.done', jobdata)
        next(null)
      })
    })
  }
};

<|endoftext|>


<commit_before>'use strict';

var temp = require('temp'),
      inherits = require('util').inherits,
      fs       = require('fs'),
      config = require('../config'),
      spawn = require('child_process').spawn,
      _          = require('lodash'),
      chdir = require('chdir'),
      unzip = require('unzip');

function Zip2Git(options){
  options = _.defaults(options || {}, _.omit(config, 'env', 'remote'));
}

function isNullOrEmpty(value){
  return (value === null || value === undefined) ||
    (_.isString(value) ? value.length === 0 : true);
}

/*
*   updateRepo completes the following workflow:
*        1) changes directory to the provided dir
*        2) git add --all
*        3) git commit -am <commit message> <--this can be the options.commitMsg or the default
*        4) git push
*/
function updateRepo(dir, remote, options, cb){
    var subDir = remote[remote.length-1]==='/' ? remote.split('/')[remote.split('/').length-2] : remote.split('/')[remote.split('/').length-1];
    subDir = dir + '/' + subDir;
        if(isNullOrEmpty(remote)){
          return cb(new Error('You must provide both directory and a string URI to a git repository'));
        }
        var gitAdd = spawn('git',['add', '.']);
        gitAdd.on('error', cb);
        gitAdd.stdout.on('data', function(data) {console.log('gitAdd.stdout: ' + data);});
        gitAdd.stderr.on('data', function(data) {console.log('gitAdd.stderr: ' + data);});
        gitAdd.on('close', function(code){
            if(code !== 0) return cb(new Error('gitAdd process exited with code '+code));

            // git add successfully complete, now commit
            console.log('gitAdd complete, now commiting');
            var commitMsg = options.commitMsg ? options.commitMsg : 'Zippadee Doo Da Zip2Git';
            var gitCommit = spawn('git',['commit','-am '+commitMsg]);
            gitCommit.on('error', cb);
            gitCommit.stdout.on('data', function(data) {console.log('gitCommit.stdout: ' + data);});
            gitCommit.stderr.on('data', function(data) {console.log('gitCommit.stderr: ' + data);});
            gitCommit.on('close', function(code){
                if(code !== 0) return cb(new Error('gitCommit process exited with code '+code));
                //git commit successfully complete, now push
                console.log('gitCommit complete, now pushing');
                var gitPush= spawn('git',['push']);
                gitPush.on('error', cb);
                gitPush.stdout.on('data', function(data) {console.log('gitPush.stdout: ' + data);});
                gitPush.stderr.on('data', function(data) {console.log('gitPush.stderr: ' + data);});
                gitPush.on('close', function(code){
                    if(code !== 0) return cb(new Error('gitPush process exited with code '+code));
                    //git push complete successfully, now return callback
                    console.log('gitPush complete, now calling cb');
                    return cb();
                })

            });
        });
}

/*
*   Zip2Git.commit options:
*       commitMsg [String]- A custom message to include as the git commit message
            -Defaults to 'Zippadee Doo Da Zip2Git'
*/
Zip2Git.prototype.commit = function(archive, remote, options, cb){
    if(isNullOrEmpty(archive) || isNullOrEmpty(remote)){
        return cb(new Error('You must provide both an archive filepath or string and a string URI to a git repository'));
    }
    if(typeof(options)==='function' && !cb) {
        cb=options;
        options = {};
    }
    temp.track();
    temp.mkdir('git-temp', function(e, dir){
        if(e) return cb(e);
        chdir(dir, function(){
            console.log('************ temp dir =', dir);
            var clone = spawn('git',['clone',remote,'./']);
            clone.stderr.on('data', function (data) { console.log('clone.stderr: ' + data); });
            clone.stdout.on('data', function (data) { console.log('clone.stdout: ' + data); });
            clone.on('error', function(err){console.log('err', err); cb('error')});
            clone.on('close', function(code){
                //clone complete
                if(code !== 0)
                    return cb(new Error('Git clone process exited with code '+code));
                console.log('clone complete');
                var unzipper = unzip.Extract({path: process.cwd()});
                unzipper.on('error', cb);
                unzipper.on('close', function(code){
                    //unzip complete, now call updateRepo to do git operations
                    updateRepo(dir, remote, options, cb);
                });

                if(_.isString(archive)){
                    fs.createReadStream(archive).pipe(unzipper);
                }
                else archive.pipe(unzipper);
            });
        });
    });
};

module.exports = Zip2Git;
<commit_msg> third<commit_after>'use strict';

var temp = require('temp'),
      inherits = require('util').inherits,
      fs       = require('fs'),
      config = require('../config'),
      spawn = require('child_process').spawn,
      _          = require('lodash'),
      chdir = require('chdir'),
      unzip = require('unzip');

function Zip2Git(options){
  options = _.defaults(options || {}, _.omit(config, 'env', 'remote'));
}

function isNullOrEmpty(value){
  return (value === null || value === undefined) ||
    (_.isString(value) ? value.length === 0 : true);
}

/*
*   updateRepo completes the following workflow:
*        1) changes directory to the provided dir
*        2) git add --all
*        3) git commit -am <commit message> <--this can be the options.commitMsg or the default
*        4) git push
*/
function updateRepo(dir, remote, options, cb){
    var subDir = remote[remote.length-1]==='/' ? remote.split('/')[remote.split('/').length-2] : remote.split('/')[remote.split('/').length-1];
    subDir = dir + '/' + subDir;
        if(isNullOrEmpty(remote)){
          return cb(new Error('You must provide both directory and a string URI to a git repository'));
        }
        var gitAdd = spawn('git',['add', '.']);
        gitAdd.on('error', cb);
        gitAdd.stdout.on('data', function(data) {console.log('gitAdd.stdout: ' + data);});
        gitAdd.stderr.on('data', function(data) {console.log('gitAdd.stderr: ' + data);});
        gitAdd.on('close', function(code){
            if(code !== 0) return cb(new Error('gitAdd process exited with code '+code));

            // git add successfully complete, now commit
            console.log('gitAdd complete, now commiting');
            var commitMsg = options.commitMsg ? options.commitMsg : 'Zippadee Doo Da Zip2Git';
            var gitCommit = spawn('git',['commit','-am '+commitMsg]);
            gitCommit.on('error', cb);
            gitCommit.stdout.on('data', function(data) {console.log('gitCommit.stdout: ' + data);});
            gitCommit.stderr.on('data', function(data) {console.log('gitCommit.stderr: ' + data);});
            gitCommit.on('close', function(code){
                if(code !== 0) return cb(new Error('gitCommit process exited with code '+code));
                //git commit successfully complete, now push
                var gitBranch = (options.branch || 'master');
                console.log('gitCommit complete, now pushing');
                var gitPush= spawn('git',['push','origin',gitBranch]);
                gitPush.on('error', cb);
                gitPush.stdout.on('data', function(data) {console.log('gitPush.stdout: ' + data);});
                gitPush.stderr.on('data', function(data) {console.log('gitPush.stderr: ' + data);});
                gitPush.on('close', function(code){
                    if(code !== 0) return cb(new Error('gitPush process exited with code '+code));
                    //git push complete successfully, now return callback
                    console.log('gitPush complete, now calling cb');
                    return cb();
                })

            });
        });
}

/*
*   Zip2Git.commit options:
*       commitMsg [String]- A custom message to include as the git commit message
            -Defaults to 'Zippadee Doo Da Zip2Git'
*/
Zip2Git.prototype.commit = function(archive, remote, options, cb){
    if(isNullOrEmpty(archive) || isNullOrEmpty(remote)){
        return cb(new Error('You must provide both an archive filepath or string and a string URI to a git repository'));
    }
    if(typeof(options)==='function' && !cb) {
        cb=options;
        options = {};
    }
    temp.track();
    temp.mkdir('git-temp', function(e, dir){
        if(e) return cb(e);
        chdir(dir, function(){
            console.log('************ temp dir =', dir);
            var clone = spawn('git',['clone',remote,'./']);
            clone.stderr.on('data', function (data) { console.log('clone.stderr: ' + data); });
            clone.stdout.on('data', function (data) { console.log('clone.stdout: ' + data); });
            clone.on('error', function(err){console.log('err', err); cb('error')});
            clone.on('close', function(code){
                //clone complete
                if(code !== 0)
                    return cb(new Error('Git clone process exited with code '+code));
                console.log('clone complete');
                var unzipper = unzip.Extract({path: process.cwd()});
                unzipper.on('error', cb);
                unzipper.on('close', function(code){
                    //unzip complete, now call updateRepo to do git operations
                    updateRepo(dir, remote, options, cb);
                });

                if(_.isString(archive)){
                    fs.createReadStream(archive).pipe(unzipper);
                }
                else archive.pipe(unzipper);
            });
        });
    });
};

module.exports = Zip2Git;
<|endoftext|>


<commit_before>'use strict';
/**
 * mongoose-context-ref plugin
 * --------------------------------------------------------------------------*/

module.exports = function(schema, options) {
  options || (options = {});
  options.required || (options.required = false);
  if(options.virtuals === undefined) options.virtuals = true;
  if(options.refUpdate === undefined) options.refUpdate = true;

  var ObjectId = schema.constructor.ObjectId;

  // add the context paths
  schema.add({
    context_id:   { type: ObjectId, required: options.required },
    context_type: { type: String, required: options.required }
  });

  // add the validator
  var validator = typeof options.context_types === 'function'?
                                        options.context_types:
                                                 dftValidator;

  schema.path('context_type').validate(validator, 'Invalid context type.');

  // add virtual properties, if the context_types are specified and the user
  // didn't disable this feature
  if(Array.isArray(options.context_types) && options.virtuals) {
    addVirtuals(schema, options.context_types);
  }

  // add the updating hooks
  if(options.refUpdate) {
    schema.pre('save', function(next) {
      if(!this.isNew || !this.context_id) return next();
      else refUpdate('save', this, next);
    });

    schema.pre('remove', function(next) {
      if(!this.context_id) return next();
      else refUpdate('remove', this, next);
    });
  }

  // return the schema, for chaining
  return schema;

  // utility functions --
  function dftValidator(value) {
    /* jshint validthis: true */
    var models = options.context_types || getModelNames(this);

    return !!value && (models.indexOf(value) !== -1);
  }
};

function refUpdate(method, doc, cb) {
  var path = doc.constructor.modelName.toLowerCase() + 's',
      action = method === 'save' ? '$push' : '$pull';

  var update = {};
  update[action] = {};
  update[action][path] = doc._id;

  doc.constructor.db.model(doc.context_type)
    .findByIdAndUpdate(doc.context_id, update, function(err, pdoc) {
      if(err) cb(err);
      else if(!pdoc && method === 'save') {
        err = new Error(doc.context_type + ' not found'); err.status = 400;
        cb(err);
      }
      else cb(null, pdoc);
    });
}

function addVirtuals(schema, context_types) {
  context_types.forEach(function(context_type) {
    var path = context_type.toLowerCase();

    if(!schema.path(path)) { // don't overwrite existing paths
      schema.virtual(path)
            .get(function() {
              if(this.context_type.toLowerCase() === path)
                return this.context_id;
              else return undefined;
            })
            .set(function(id) {
              this.context_type = (path.charAt(0).toUpperCase() + path.slice(1));
              this.context_id = id;
            });
    }
  });
}

function getModelNames(doc) {
  var model_names = Object.keys(doc.constructor.db.base.models);
  if(model_names.length) return model_names;
  else return doc.constructor.db.modelNames();
}
<commit_msg>Add context change updates' support.<commit_after>'use strict';
/**
 * mongoose-context-ref plugin
 * --------------------------------------------------------------------------*/

module.exports = function(schema, options) {
  options || (options = {});
  options.required || (options.required = false);
  if(options.virtuals === undefined) options.virtuals = true;
  if(options.refUpdate === undefined) options.refUpdate = true;

  var ObjectId = schema.constructor.ObjectId;

  // add the context paths
  schema.add({
    context_id:   { type: ObjectId, required: options.required },
    context_type: { type: String, required: options.required }
  });

  // add the validator
  var validator = typeof options.context_types === 'function'?
                                        options.context_types:
                                                 dftValidator;

  schema.path('context_type').validate(validator, 'Invalid context type.');

  // add virtual properties, if the context_types are specified and the user
  // didn't disable this feature
  if(Array.isArray(options.context_types) && options.virtuals) {
    addVirtuals(schema, options.context_types);
  }

  // add the updating hooks
  if(options.refUpdate) {
    schema.pre('save', function(next) {
      if(!this.isNew || !this.context_id) return next();
      else refUpdate('save', this, next);
    });

    // make sure the context is cached
    schema.post('init', cacheContext);
    schema.post('save', cacheContext);
    schema.pre('save', function(next) {
      if((!this.isModified('context_id') && !this.isModified('context_type'))
         || this.isNew || !this.context_id) return next();

      var _this = this;
      var new_context = {
        context_type: this.context_type,
        context_id:   this.context_id
      };

      this.context_type = this._original.context_type;
      this.context_id   = this._original.context_id;

      return refUpdate('remove', this, function(err) {
        if(err) return next(err);

        _this.context_type = new_context.context_type;
        _this.context_id   = new_context.context_id;
        refUpdate('save', _this, next);
      });
    });

    schema.pre('remove', function(next) {
      if(!this.context_id) return next();
      else refUpdate('remove', this, next);
    });
  }

  // return the schema, for chaining
  return schema;

  // utility functions --
  function dftValidator(value) {
    /* jshint validthis: true */
    var models = options.context_types || getModelNames(this);

    return !!value && (models.indexOf(value) !== -1);
  }
};

function refUpdate(method, doc, cb) {
  var path = doc.constructor.modelName.toLowerCase() + 's',
      action = method === 'save' ? '$push' : '$pull';

  var update = {};
  update[action] = {};
  update[action][path] = doc._id;

  doc.constructor.db.model(doc.context_type)
    .findByIdAndUpdate(doc.context_id, update, function(err, pdoc) {
      if(err) cb(err);
      else if(!pdoc && method === 'save') {
        err = new Error(doc.context_type + ' not found'); err.status = 400;
        cb(err);
      }
      else cb(null, pdoc);
    });
}

function addVirtuals(schema, context_types) {
  context_types.forEach(function(context_type) {
    var path = context_type.toLowerCase();

    if(!schema.path(path)) { // don't overwrite existing paths
      schema.virtual(path)
            .get(function() {
              if(this.context_type.toLowerCase() === path)
                return this.context_id;
              else return undefined;
            })
            .set(function(id) {
              this.context_type = (path.charAt(0).toUpperCase() + path.slice(1));
              this.context_id = id;
            });
    }
  });
}

function getModelNames(doc) {
  var model_names = Object.keys(doc.constructor.db.base.models);
  if(model_names.length) return model_names;
  else return doc.constructor.db.modelNames();
}

function cacheContext(doc) {
  doc._original = doc._original || { // only cache if not redundant
    context_type: doc.context_type,
    context_id:   doc.context_id
  };
}
<|endoftext|>