<commit_before>/*
 * Copyright 2016 Red Hat Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

var log = require("./log.js").logger();
var util = require('util');

var MAX_OUTSTANDING = process.env.MAX_OUTSTANDING_QDR_REQUESTS || 1000;

var Router = function (connection, router, agent) {
    if (router) {
        this.target = agent;
        this.connection = router.connection;
        this.counter = router.counter;
        this.handlers = router.handlers;
        this.requests = router.requests;
        this.address = router.address;
        this.sender = router.sender;
        this.tracking = router.tracking;
    } else {
        this.target = '$management';
        this.sender = connection.open_sender();
        this.sender.on('sendable', this._send_pending_requests.bind(this));
        this.connection = connection;
        this.counter = 0;
        this.handlers = {};
        this.requests = [];
        connection.open_receiver({source:{dynamic:true}});
        connection.on('message', this.incoming.bind(this));
        connection.on('connection_close', this.closed.bind(this));
        this.tracking = {
            sent: 0,
            recv: 0,
            outstanding: 0,
            unexpected_responses: 0
        };
        this.last_status = undefined;
        var self = this;
        var interval = process.env.STATUS_LOG_INTERVAL || 300000;
        this.timer = setInterval(function () {
            self.health_check();
        }, interval);
    }
    this.connection.on('receiver_open', this.ready.bind(this));
    this.connection.on('disconnected', this.disconnected.bind(this));
    this.connection.on('sender_error', this.on_sender_error.bind(this));
};

Router.prototype.get_id = function () {
    if (this.name) {
        return this.name + ':' + this.connection.container_id;
    } else {
        return this.connection.container_id;
    }
};

Router.prototype.log_info = function () {
    log.info('[%s] qdr handlers: %d, requests pending: %d, requests sent: %d, responses received: %d, unexpected response: %d, ready: %s',
             this.get_id(), Object.keys(this.handlers).length, this.requests.length,
             this.tracking.sent, this.tracking.recv, this.tracking.unexpected_responses, (this.address !== undefined));
};

Router.prototype.health_check = function () {
    var current = this._get_status();
    var last = this.last_status;
    if (this.last_status === undefined) {
        this.last_status = current;
    } else {
        var ready_state;
        if (current.ready) {
            if (last.ready) {
                ready_state = 'remains active';
            } else {
                ready_state = 'transitioned to active';
            }
        } else {
            if (last.ready) {
                ready_state = 'transitioned to inactive';
            } else {
                ready_state = 'remains inactive';
            }
        }

        var messages = [];
        var still_outstanding = current.outstanding.filter(function (id) { return last.outstanding.indexOf(id) >=0; });
        if (still_outstanding.length > 0) {
            messages.push(util.format('still waiting for responses for %j', still_outstanding));
        }

        if (current.unsent > last.unsent && current.sent === last.sent) {
            messages.push('sending of requests appears to be blocked');
        }

        if (current.unexpected_responses > last.unexpected_responses) {
            messages.push(util.format('%d unexpected responses since last status', current.unexpected_responses - last.unexpected_responses));
        }

        if (messages.length) {
            log.warn('[%s] %s (%s)', this.get_id(), messages.join(', '), ready_state);
        } else if (current.sent !== last.sent || current.recv != last.recv) {
            log.info('[%s] %s sent %d requests and received %d responses since last status; %d outstanding requests issued', this.get_id(),
                     ready_state, current.sent - last.sent, current.recv - last.recv, current.outstanding);
        } else {
            log.info('[%s] %s no requests/responses since last status', this.get_id(), ready_state);
        }
        last = current;
    }
};

Router.prototype._get_status = function () {
    return  {
        sent: this.tracking.sent,
        recv: this.tracking.recv,
        unexpected_responses: this.tracking.unexpected_responses,
        unsent: this.requests.length,
        outstanding: Object.keys(this.handlers),
        ready: (this.address !== undefined)
    };
};

Router.prototype.closed = function (context) {
    if (context.connection.error) {
        log.error('[%s] ERROR: router closed connection with %s', this.get_id(), context.connection.error.description);
    }
    log.info('[%s] router closed ', this.get_id(), this.target);
    this.address = undefined;
    this._abort_requests('closed');
};

Router.prototype._abort_requests = function (error) {
    log.info('[%s] aborting pending requests: %s', this.get_id(), error);
    for (var h in this.handlers) {
        this.handlers[h](error);
        delete this.handlers[h];
    }
    while (this.requests.length > 0) { this.requests.shift(); };
}

Router.prototype.on_sender_error = function (context) {
    log.info('[%s] sender error %s', this.get_id(), error);
};

Router.prototype.disconnected = function (context) {
    this.address = undefined;
    this._abort_requests('disconnected');
}

Router.prototype.ready = function (context) {
    log.info('[%s] router ready', this.get_id());
    this.address = context.receiver.source.address;
    this._send_pending_requests();
};

function create_record(names, values) {
    var record = {};
    for (var j = 0; j < names.length; j++) {
        record[names[j]] = values[j];
    }
    return record;
}

function extract_records(body) {
    return body.results ? body.results.map(create_record.bind(null, body.attributeNames)) : body;
}

function as_handler(resolve, reject) {
    return function (context) {
        var message = context.message === undefined ? context : context.message;
        if (message.application_properties) {
            if (message.application_properties.statusCode >= 200 && message.application_properties.statusCode < 300) {
                if (message.body) resolve(extract_records(message.body));
                else resolve({code:message.application_properties.statusCode, description:message.application_properties.statusDescription});
            } else {
                reject({code:message.application_properties.statusCode, description:message.application_properties.statusDescription});
            }
        } else {
            reject(message.toString());
        }
    };
}

Router.prototype._sendable = function () {
    return this.sender.sendable() && this.tracking.outstanding < MAX_OUTSTANDING;
}

Router.prototype._send_pending_requests = function () {
    if (this.address === undefined) return false;

    var i = 0;
    while (i < this.requests.length && this._sendable()) {
        this._send_request(this.requests[i++]);
    }
    this.requests.splice(0, i);
    return this.requests.length === 0 && this._sendable();
}

Router.prototype._send_request = function (request) {
    request.reply_to = this.address;
    this.sender.send(request);
    this.tracking.sent++;
    this.tracking.outstanding++;
    log.debug('sent: %j', request);
}

Router.prototype.request = function (operation, properties, body) {
    var id = this.target + this.counter.toString();
    this.counter++;
    var req = {correlation_id:id, to:this.target};
    req.application_properties = properties || {};
    req.application_properties.operation = operation;
    req.body = body;

    if (this._send_pending_requests()) {
        this._send_request(req);
    } else {
        this.requests.push(req);
    }
    var handlers = this.handlers;
    return new Promise(function (resolve, reject) {
        handlers[id] = as_handler(resolve, reject);
    });
}

Router.prototype.query = function (type, options) {
    return this.request('QUERY', {entityType:type}, options || {attributeNames:[]});
};

Router.prototype.create_entity = function (type, name, attributes) {
    return this.request('CREATE', {'type':type, 'name':name}, attributes || {});
};

Router.prototype.delete_entity = function (type, name) {
    return this.request('DELETE', {'type':type, 'name':name}, {});
};

Router.prototype.get_mgmt_nodes = function () {
    return this.request('GET-MGMT-NODES', {}, {});
};

Router.prototype._get_all_routers = function () {
    var self = this;
    return this.get_mgmt_nodes().then(
        function (agents) {
            return agents.map(function (agent) { return new Router(undefined, self, agent); });
        }
    );
};

Router.prototype.get_all_routers = function (current) {
    if (current === undefined || current.length === 0) {
        return this._get_all_routers();
    } else {
        var self = this;
        return this.get_mgmt_nodes().then(
            function (results) {
                var agents = {};
                results.forEach(function (name) { agents[name] = true; });
                var routers = [];
                for (var i = 0; i < current.length; i++) {
                    if (agents[current[i].target] !== undefined) {
                        delete agents[current[i].target];
                        routers.push(current[i]);
                    }
                }
                return routers.concat( Object.keys(agents).map(function (agent) { return new Router(undefined, self, agent); }) );
            }
        );
    }
};

Router.prototype.incoming = function (context) {
    log.debug('recv: %j', context.message);
    this.tracking.recv++
    this.tracking.outstanding--;
    var message = context.message;
    var handler = this.handlers[message.correlation_id];
    if (handler) {
        delete this.handlers[message.correlation_id];
        handler(context);
    } else {
        this.tracking.unexpected_responses++;
        log.warn('WARNING: unexpected response: ' + message.correlation_id + ' [' + JSON.stringify(message) + ']');
    }
    this._send_pending_requests();
};

Router.prototype.close = function () {
    if (this.connection) this.connection.close();
    if (this.timer) clearInterval(this.timer);
}

function add_resource_type (name, typename, plural) {
    var resource_type = typename || name;
    Router.prototype['create_' + name] = function (o) {
        return this.create_entity(resource_type, o.name, o);
    };
    Router.prototype['delete_' + name] = function (o) {
        return this.delete_entity(resource_type, o.name);
    };
    var plural_name = plural || name + 's';
    Router.prototype['get_' + plural_name] = function (options) {
        return this.query(resource_type, options);
    };

}

function add_queryable_type (name, typename) {
    var resource_type = typename || name;
    Router.prototype['get_' + name] = function (options) {
        return this.query(resource_type, options);
    };
}

add_resource_type('connector');
add_resource_type('listener');
add_resource_type('address', 'org.apache.qpid.dispatch.router.config.address', 'addresses');
add_resource_type('link_route', 'org.apache.qpid.dispatch.router.config.linkRoute');

add_queryable_type('connections', 'org.apache.qpid.dispatch.connection');
add_queryable_type('links', 'org.apache.qpid.dispatch.router.link');
add_queryable_type('address_stats', 'org.apache.qpid.dispatch.router.address');

var amqp = require('rhea').create_container();
module.exports.Router = Router;
module.exports.connect = function (options) {
    return new Router(amqp.connect(options));
}
<commit_msg>lower default to routers default internal capacity<commit_after>/*
 * Copyright 2016 Red Hat Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

var log = require("./log.js").logger();
var util = require('util');

var MAX_OUTSTANDING = process.env.MAX_OUTSTANDING_QDR_REQUESTS || 250;

var Router = function (connection, router, agent) {
    if (router) {
        this.target = agent;
        this.connection = router.connection;
        this.counter = router.counter;
        this.handlers = router.handlers;
        this.requests = router.requests;
        this.address = router.address;
        this.sender = router.sender;
        this.tracking = router.tracking;
    } else {
        this.target = '$management';
        this.sender = connection.open_sender();
        this.sender.on('sendable', this._send_pending_requests.bind(this));
        this.connection = connection;
        this.counter = 0;
        this.handlers = {};
        this.requests = [];
        connection.open_receiver({source:{dynamic:true}});
        connection.on('message', this.incoming.bind(this));
        connection.on('connection_close', this.closed.bind(this));
        this.tracking = {
            sent: 0,
            recv: 0,
            outstanding: 0,
            unexpected_responses: 0
        };
        this.last_status = undefined;
        var self = this;
        var interval = process.env.STATUS_LOG_INTERVAL || 300000;
        this.timer = setInterval(function () {
            self.health_check();
        }, interval);
    }
    this.connection.on('receiver_open', this.ready.bind(this));
    this.connection.on('disconnected', this.disconnected.bind(this));
    this.connection.on('sender_error', this.on_sender_error.bind(this));
};

Router.prototype.get_id = function () {
    if (this.name) {
        return this.name + ':' + this.connection.container_id;
    } else {
        return this.connection.container_id;
    }
};

Router.prototype.log_info = function () {
    log.info('[%s] qdr handlers: %d, requests pending: %d, requests sent: %d, responses received: %d, unexpected response: %d, ready: %s',
             this.get_id(), Object.keys(this.handlers).length, this.requests.length,
             this.tracking.sent, this.tracking.recv, this.tracking.unexpected_responses, (this.address !== undefined));
};

Router.prototype.health_check = function () {
    var current = this._get_status();
    var last = this.last_status;
    if (this.last_status === undefined) {
        this.last_status = current;
    } else {
        var ready_state;
        if (current.ready) {
            if (last.ready) {
                ready_state = 'remains active';
            } else {
                ready_state = 'transitioned to active';
            }
        } else {
            if (last.ready) {
                ready_state = 'transitioned to inactive';
            } else {
                ready_state = 'remains inactive';
            }
        }

        var messages = [];
        var still_outstanding = current.outstanding.filter(function (id) { return last.outstanding.indexOf(id) >=0; });
        if (still_outstanding.length > 0) {
            messages.push(util.format('still waiting for responses for %j', still_outstanding));
        }

        if (current.unsent > last.unsent && current.sent === last.sent) {
            messages.push('sending of requests appears to be blocked');
        }

        if (current.unexpected_responses > last.unexpected_responses) {
            messages.push(util.format('%d unexpected responses since last status', current.unexpected_responses - last.unexpected_responses));
        }

        if (messages.length) {
            log.warn('[%s] %s (%s)', this.get_id(), messages.join(', '), ready_state);
        } else if (current.sent !== last.sent || current.recv != last.recv) {
            log.info('[%s] %s sent %d requests and received %d responses since last status; %d outstanding requests issued', this.get_id(),
                     ready_state, current.sent - last.sent, current.recv - last.recv, current.outstanding);
        } else {
            log.info('[%s] %s no requests/responses since last status', this.get_id(), ready_state);
        }
        last = current;
    }
};

Router.prototype._get_status = function () {
    return  {
        sent: this.tracking.sent,
        recv: this.tracking.recv,
        unexpected_responses: this.tracking.unexpected_responses,
        unsent: this.requests.length,
        outstanding: Object.keys(this.handlers),
        ready: (this.address !== undefined)
    };
};

Router.prototype.closed = function (context) {
    if (context.connection.error) {
        log.error('[%s] ERROR: router closed connection with %s', this.get_id(), context.connection.error.description);
    }
    log.info('[%s] router closed ', this.get_id(), this.target);
    this.address = undefined;
    this._abort_requests('closed');
};

Router.prototype._abort_requests = function (error) {
    log.info('[%s] aborting pending requests: %s', this.get_id(), error);
    for (var h in this.handlers) {
        this.handlers[h](error);
        delete this.handlers[h];
    }
    while (this.requests.length > 0) { this.requests.shift(); };
}

Router.prototype.on_sender_error = function (context) {
    log.info('[%s] sender error %s', this.get_id(), error);
};

Router.prototype.disconnected = function (context) {
    this.address = undefined;
    this._abort_requests('disconnected');
}

Router.prototype.ready = function (context) {
    log.info('[%s] router ready', this.get_id());
    this.address = context.receiver.source.address;
    this._send_pending_requests();
};

function create_record(names, values) {
    var record = {};
    for (var j = 0; j < names.length; j++) {
        record[names[j]] = values[j];
    }
    return record;
}

function extract_records(body) {
    return body.results ? body.results.map(create_record.bind(null, body.attributeNames)) : body;
}

function as_handler(resolve, reject) {
    return function (context) {
        var message = context.message === undefined ? context : context.message;
        if (message.application_properties) {
            if (message.application_properties.statusCode >= 200 && message.application_properties.statusCode < 300) {
                if (message.body) resolve(extract_records(message.body));
                else resolve({code:message.application_properties.statusCode, description:message.application_properties.statusDescription});
            } else {
                reject({code:message.application_properties.statusCode, description:message.application_properties.statusDescription});
            }
        } else {
            reject(message.toString());
        }
    };
}

Router.prototype._sendable = function () {
    return this.sender.sendable() && this.tracking.outstanding < MAX_OUTSTANDING;
}

Router.prototype._send_pending_requests = function () {
    if (this.address === undefined) return false;

    var i = 0;
    while (i < this.requests.length && this._sendable()) {
        this._send_request(this.requests[i++]);
    }
    this.requests.splice(0, i);
    return this.requests.length === 0 && this._sendable();
}

Router.prototype._send_request = function (request) {
    request.reply_to = this.address;
    this.sender.send(request);
    this.tracking.sent++;
    this.tracking.outstanding++;
    log.debug('sent: %j', request);
}

Router.prototype.request = function (operation, properties, body) {
    var id = this.target + this.counter.toString();
    this.counter++;
    var req = {correlation_id:id, to:this.target};
    req.application_properties = properties || {};
    req.application_properties.operation = operation;
    req.body = body;

    if (this._send_pending_requests()) {
        this._send_request(req);
    } else {
        this.requests.push(req);
    }
    var handlers = this.handlers;
    return new Promise(function (resolve, reject) {
        handlers[id] = as_handler(resolve, reject);
    });
}

Router.prototype.query = function (type, options) {
    return this.request('QUERY', {entityType:type}, options || {attributeNames:[]});
};

Router.prototype.create_entity = function (type, name, attributes) {
    return this.request('CREATE', {'type':type, 'name':name}, attributes || {});
};

Router.prototype.delete_entity = function (type, name) {
    return this.request('DELETE', {'type':type, 'name':name}, {});
};

Router.prototype.get_mgmt_nodes = function () {
    return this.request('GET-MGMT-NODES', {}, {});
};

Router.prototype._get_all_routers = function () {
    var self = this;
    return this.get_mgmt_nodes().then(
        function (agents) {
            return agents.map(function (agent) { return new Router(undefined, self, agent); });
        }
    );
};

Router.prototype.get_all_routers = function (current) {
    if (current === undefined || current.length === 0) {
        return this._get_all_routers();
    } else {
        var self = this;
        return this.get_mgmt_nodes().then(
            function (results) {
                var agents = {};
                results.forEach(function (name) { agents[name] = true; });
                var routers = [];
                for (var i = 0; i < current.length; i++) {
                    if (agents[current[i].target] !== undefined) {
                        delete agents[current[i].target];
                        routers.push(current[i]);
                    }
                }
                return routers.concat( Object.keys(agents).map(function (agent) { return new Router(undefined, self, agent); }) );
            }
        );
    }
};

Router.prototype.incoming = function (context) {
    log.debug('recv: %j', context.message);
    this.tracking.recv++
    this.tracking.outstanding--;
    var message = context.message;
    var handler = this.handlers[message.correlation_id];
    if (handler) {
        delete this.handlers[message.correlation_id];
        handler(context);
    } else {
        this.tracking.unexpected_responses++;
        log.warn('WARNING: unexpected response: ' + message.correlation_id + ' [' + JSON.stringify(message) + ']');
    }
    this._send_pending_requests();
};

Router.prototype.close = function () {
    if (this.connection) this.connection.close();
    if (this.timer) clearInterval(this.timer);
}

function add_resource_type (name, typename, plural) {
    var resource_type = typename || name;
    Router.prototype['create_' + name] = function (o) {
        return this.create_entity(resource_type, o.name, o);
    };
    Router.prototype['delete_' + name] = function (o) {
        return this.delete_entity(resource_type, o.name);
    };
    var plural_name = plural || name + 's';
    Router.prototype['get_' + plural_name] = function (options) {
        return this.query(resource_type, options);
    };

}

function add_queryable_type (name, typename) {
    var resource_type = typename || name;
    Router.prototype['get_' + name] = function (options) {
        return this.query(resource_type, options);
    };
}

add_resource_type('connector');
add_resource_type('listener');
add_resource_type('address', 'org.apache.qpid.dispatch.router.config.address', 'addresses');
add_resource_type('link_route', 'org.apache.qpid.dispatch.router.config.linkRoute');

add_queryable_type('connections', 'org.apache.qpid.dispatch.connection');
add_queryable_type('links', 'org.apache.qpid.dispatch.router.link');
add_queryable_type('address_stats', 'org.apache.qpid.dispatch.router.address');

var amqp = require('rhea').create_container();
module.exports.Router = Router;
module.exports.connect = function (options) {
    return new Router(amqp.connect(options));
}
<|endoftext|>


<commit_before>/*! angular-deckgrid (v0.5.0) - Copyright: 2013 - 2014, André König (andre.koenig@posteo.de) - MIT */
/*
 * angular-deckgrid
 *
 * Copyright(c) 2013-2014 André König <andre.koenig@posteo.de>
 * MIT Licensed
 *
 */

/**
 * @author André König (andre.koenig@posteo.de)
 *
 */

angular.module('akoenig.deckgrid', []);

angular.module('akoenig.deckgrid').directive('deckgrid', [

    'DeckgridDescriptor',

    function initialize (DeckgridDescriptor) {

        'use strict';

        return DeckgridDescriptor.create();
    }
]);
/*
 * angular-deckgrid
 *
 * Copyright(c) 2013-2014 André König <andre.koenig@posteo.de>
 * MIT Licensed
 *
 */

/**
 * @author André König (andre.koenig@posteo.de)
 *
 */

angular.module('akoenig.deckgrid').factory('DeckgridDescriptor', [

    'Deckgrid',
    '$templateCache',
    '$window',
    '$q',

    function initialize (Deckgrid, $templateCache, $window, $q, $timeout) {

        'use strict';

        /**
         * This is a wrapper around the AngularJS
         * directive description object.
         *
         */
        function Descriptor () {
            this.restrict = 'AE';

            this.template = '
' +
                                '

' +
                            '
';

            this.scope = {
                'model': '=source'
            };

            //
            // Will be created in the linking function.
            //
            this.$$deckgrid = null;

            this.transclude = true;
            this.link = this.$$link.bind(this);

            //
            // Will be incremented if using inline templates.
            //
            this.$$templateKeyIndex = 0;

        }

        /**
         * @private
         *
         * Cleanup method. Will be called when the
         * deckgrid directive should be destroyed.
         *
         */
        Descriptor.prototype.$$destroy = function $$destroy () {
            this.$$deckgrid.destroy();
        };

        /**
         * @private
         *
         * The deckgrid link method. Will instantiate the deckgrid.
         *
         */
        Descriptor.prototype.$$link = function $$link (scope, elem, attrs, nullController, transclude) {
            var templateKey = 'deckgrid/innerHtmlTemplate' + (++this.$$templateKeyIndex) + '.html',
                self = this,
                styleReady = $q.defer(),
                domWatch;

            scope.$on('$destroy', this.$$destroy.bind(this));

            if (attrs.cardtemplate === undefined) {
                if (attrs.cardtemplatestring === undefined) {
                    // use the provided inner html as template
                    transclude(scope, function onTransclude (innerHTML) {
                        var extractedInnerHTML = [],
                            i = 0,
                            len = innerHTML.length,
                            outerHTML;

                        for (i; i < len; i = i + 1) {
                            outerHTML = innerHTML[i].outerHTML;

                            if (outerHTML !== undefined) {
                                extractedInnerHTML.push(outerHTML);
                            }
                        }

                        $templateCache.put(templateKey, extractedInnerHTML.join());
                    });
                } else {
                    // use the provided template string
                    //
                    // note: the attr is accessed via the elem object, as the attrs content
                    // is already compiled and thus lacks the {{...}} expressions
                    $templateCache.put(templateKey, elem.attr('cardtemplatestring'));
                }

                scope.cardTemplate = templateKey;
            } else {
                // use the provided template file
                scope.cardTemplate = attrs.cardtemplate;
            }

            scope.mother = scope.$parent;

            // Wait for style to be ready on deckgrid element
           domWatch = scope.$watch(function() {
                return $window.getComputedStyle(elem[0], ':before').content;
            }, function(content) {
                if (content !== '') {
                    styleReady.resolve();
                    // Clear the watcher once element is initialized
                    domWatch();
                }
            });
            styleReady.promise.then(function() {
                console.log('before: ' + $window.getComputedStyle(elem[0], null).width);

                $timeout(function() {
                    console.log('after' + $window.getComputedStyle(elem[0], null).width);
                    self.$$deckgrid = Deckgrid.create(scope, elem[0]);
                });
            });
        };

        return {
            create : function create () {
                return new Descriptor();
            }
        };
    }
]);

/*
 * angular-deckgrid
 *
 * Copyright(c) 2013-2014 André König <andre.koenig@posteo.de>
 * MIT Licensed
 *
 */

/**
 * @author André König (andre.koenig@posteo.de)
 *
 */

angular.module('akoenig.deckgrid').factory('Deckgrid', [

    '$window',
    '$log',

    function initialize ($window, $log) {

        'use strict';

        /**
         * The deckgrid directive.
         *
         */
        function Deckgrid (scope, element) {
            var self = this,
                watcher,
                mql;

            this.$$elem = element;
            this.$$watchers = [];

            this.$$scope = scope;
            this.$$scope.columns = [];

            //
            // The layout configuration will be parsed from
            // the pseudo "before element." There you have to save all
            // the column configurations.
            //
            this.$$scope.layout = this.$$getLayout();

            this.$$createColumns();

            //
            // Register model change.
            //
            watcher = this.$$scope.$watchCollection('model', this.$$onModelChange.bind(this));

            this.$$watchers.push(watcher);

            //
            // Register media query change events.
            //
            angular.forEach(self.$$getMediaQueries(), function onIteration (rule) {
                var handler = self.$$onMediaQueryChange.bind(self);

                function onDestroy () {
                    rule.removeListener(handler);
                }

                rule.addListener(handler);

                self.$$watchers.push(onDestroy);
            });

            mql = $window.matchMedia('(orientation: portrait)');
            mql.addListener(self.$$onMediaQueryChange.bind(self));

        }

        /**
         * @private
         *
         * Extracts the media queries out of the stylesheets.
         *
         * This method will fetch the media queries out of the stylesheets that are
         * responsible for styling the angular-deckgrid.
         *
         * @return {array} An array with all respective styles.
         *
         */
        Deckgrid.prototype.$$getMediaQueries = function $$getMediaQueries () {
            var stylesheets = [],
                mediaQueries = [];

            stylesheets = Array.prototype.concat.call(
                Array.prototype.slice.call(document.querySelectorAll('style[type=\'text/css\']')),
                Array.prototype.slice.call(document.querySelectorAll('link[rel=\'stylesheet\']'))
            );

            function extractRules (stylesheet) {
                try {
                    return (stylesheet.sheet.cssRules || []);
                } catch (e) {
                    return [];
                }
            }

            function hasDeckgridStyles (rule) {
                var regexe   = /\[(\w*-)?deckgrid\]::?before/g,
                    i        = 0,
                    selector = '';

                if (!rule.media || angular.isUndefined(rule.cssRules)) {
                    return false;
                }

                i = rule.cssRules.length - 1;

                for (i; i >= 0; i = i - 1) {
                    selector = rule.cssRules[i].selectorText;

                    if (angular.isDefined(selector) && selector.match(regexe)) {
                        return true;
                    }
                }

                return false;
            }

            angular.forEach(stylesheets, function onIteration (stylesheet) {
                var rules = extractRules(stylesheet);

                angular.forEach(rules, function inRuleIteration (rule) {
                    if (hasDeckgridStyles(rule)) {
                        mediaQueries.push($window.matchMedia(rule.media.mediaText));
                    }
                });
            });

            return mediaQueries;
        };

        /**
         * @private
         *
         * Creates the column segmentation. With other words:
         * This method creates the internal data structure from the
         * passed "source" attribute. Every card within this "source"
         * model will be passed into this internal column structure by
         * reference. So if you modify the data within your controller
         * this directive will reflect these changes immediately.
         *
         * NOTE that calling this method will trigger a complete template "redraw".
         *
         */
        Deckgrid.prototype.$$createColumns = function $$createColumns () {
            var self = this;

            if (!this.$$scope.layout) {
                return $log.error('angular-deckgrid: No CSS configuration found (see ' +
                                   'https://github.com/akoenig/angular-deckgrid#the-grid-configuration)');
            }

            this.$$scope.columns = [];

            angular.forEach(this.$$scope.model, function onIteration (card, index) {
                var column = (index % self.$$scope.layout.columns) | 0;

                if (!self.$$scope.columns[column]) {
                    self.$$scope.columns[column] = [];
                }

                card.$index = index;
                self.$$scope.columns[column].push(card);
            });
        };

        /**
         * @private
         *
         * Parses the configuration out of the configured CSS styles.
         *
         * Example:
         *
         *     .deckgrid::before {
         *         content: '3 .column.size-1-3';
         *     }
         *
         * Will result in a three column grid where each column will have the
         * classes: "column size-1-3".
         *
         * You are responsible for defining the respective styles within your CSS.
         *
         */
        Deckgrid.prototype.$$getLayout = function $$getLayout () {
            var content = $window.getComputedStyle(this.$$elem, ':before').content,
                layout;

            if (content) {
                content = content.replace(/'/g, '');  // before e.g. '3 .column.size-1of3'
                content = content.replace(/"/g, '');  // before e.g. "3 .column.size-1of3"
                content = content.split(' ');

                if (2 === content.length) {
                    layout = {};
                    layout.columns = (content[0] | 0);
                    layout.classList = content[1].replace(/\./g, ' ').trim();
                }
            }

            return layout;
        };

        /**
         * @private
         *
         * Event that will be triggered if a CSS media query changed.
         *
         */
        Deckgrid.prototype.$$onMediaQueryChange = function $$onMediaQueryChange () {
            var self = this,
                layout = this.$$getLayout();

            //
            // Okay, the layout has changed.
            // Creating a new column structure is not avoidable.
            //
            if (layout.columns !== this.$$scope.layout.columns) {
                self.$$scope.layout = layout;

                self.$$scope.$apply(function onApply () {
                    self.$$createColumns();
                });
            }
        };

        /**
         * @private
         *
         * Event that will be triggered when the source model has changed.
         *
         */
        Deckgrid.prototype.$$onModelChange = function $$onModelChange (newModel, oldModel) {
            var self = this;

            newModel = newModel || [];
            oldModel = oldModel || [];

            if (!angular.equals(oldModel, newModel)) {
                self.$$createColumns();
            }
        };

        /**
         * Destroys the directive. Takes care of cleaning all
         * watchers and event handlers.
         *
         */
        Deckgrid.prototype.destroy = function destroy () {
            var i = this.$$watchers.length - 1;

            for (i; i >= 0; i = i - 1) {
                this.$$watchers[i]();
            }
        };

        return {
            create : function create (scope, element) {
                return new Deckgrid(scope, element);
            }
        };
    }
]);
<commit_msg>specifies  string for dependency<commit_after>/*! angular-deckgrid (v0.5.0) - Copyright: 2013 - 2014, André König (andre.koenig@posteo.de) - MIT */
/*
 * angular-deckgrid
 *
 * Copyright(c) 2013-2014 André König <andre.koenig@posteo.de>
 * MIT Licensed
 *
 */

/**
 * @author André König (andre.koenig@posteo.de)
 *
 */

angular.module('akoenig.deckgrid', []);

angular.module('akoenig.deckgrid').directive('deckgrid', [

    'DeckgridDescriptor',

    function initialize (DeckgridDescriptor) {

        'use strict';

        return DeckgridDescriptor.create();
    }
]);
/*
 * angular-deckgrid
 *
 * Copyright(c) 2013-2014 André König <andre.koenig@posteo.de>
 * MIT Licensed
 *
 */

/**
 * @author André König (andre.koenig@posteo.de)
 *
 */

angular.module('akoenig.deckgrid').factory('DeckgridDescriptor', [

    'Deckgrid',
    '$templateCache',
    '$window',
    '$q',
    '$timeout',

    function initialize (Deckgrid, $templateCache, $window, $q, $timeout) {

        'use strict';

        /**
         * This is a wrapper around the AngularJS
         * directive description object.
         *
         */
        function Descriptor () {
            this.restrict = 'AE';

            this.template = '
' +
                                '

' +
                            '
';

            this.scope = {
                'model': '=source'
            };

            //
            // Will be created in the linking function.
            //
            this.$$deckgrid = null;

            this.transclude = true;
            this.link = this.$$link.bind(this);

            //
            // Will be incremented if using inline templates.
            //
            this.$$templateKeyIndex = 0;

        }

        /**
         * @private
         *
         * Cleanup method. Will be called when the
         * deckgrid directive should be destroyed.
         *
         */
        Descriptor.prototype.$$destroy = function $$destroy () {
            this.$$deckgrid.destroy();
        };

        /**
         * @private
         *
         * The deckgrid link method. Will instantiate the deckgrid.
         *
         */
        Descriptor.prototype.$$link = function $$link (scope, elem, attrs, nullController, transclude) {
            var templateKey = 'deckgrid/innerHtmlTemplate' + (++this.$$templateKeyIndex) + '.html',
                self = this,
                styleReady = $q.defer(),
                domWatch;

            scope.$on('$destroy', this.$$destroy.bind(this));

            if (attrs.cardtemplate === undefined) {
                if (attrs.cardtemplatestring === undefined) {
                    // use the provided inner html as template
                    transclude(scope, function onTransclude (innerHTML) {
                        var extractedInnerHTML = [],
                            i = 0,
                            len = innerHTML.length,
                            outerHTML;

                        for (i; i < len; i = i + 1) {
                            outerHTML = innerHTML[i].outerHTML;

                            if (outerHTML !== undefined) {
                                extractedInnerHTML.push(outerHTML);
                            }
                        }

                        $templateCache.put(templateKey, extractedInnerHTML.join());
                    });
                } else {
                    // use the provided template string
                    //
                    // note: the attr is accessed via the elem object, as the attrs content
                    // is already compiled and thus lacks the {{...}} expressions
                    $templateCache.put(templateKey, elem.attr('cardtemplatestring'));
                }

                scope.cardTemplate = templateKey;
            } else {
                // use the provided template file
                scope.cardTemplate = attrs.cardtemplate;
            }

            scope.mother = scope.$parent;

            // Wait for style to be ready on deckgrid element
           domWatch = scope.$watch(function() {
                return $window.getComputedStyle(elem[0], ':before').content;
            }, function(content) {
                if (content !== '') {
                    styleReady.resolve();
                    // Clear the watcher once element is initialized
                    domWatch();
                }
            });
            styleReady.promise.then(function() {
                console.log('before: ' + $window.getComputedStyle(elem[0], null).width);

                $timeout(function() {
                    console.log('after' + $window.getComputedStyle(elem[0], null).width);
                    self.$$deckgrid = Deckgrid.create(scope, elem[0]);
                });
            });
        };

        return {
            create : function create () {
                return new Descriptor();
            }
        };
    }
]);

/*
 * angular-deckgrid
 *
 * Copyright(c) 2013-2014 André König <andre.koenig@posteo.de>
 * MIT Licensed
 *
 */

/**
 * @author André König (andre.koenig@posteo.de)
 *
 */

angular.module('akoenig.deckgrid').factory('Deckgrid', [

    '$window',
    '$log',

    function initialize ($window, $log) {

        'use strict';

        /**
         * The deckgrid directive.
         *
         */
        function Deckgrid (scope, element) {
            var self = this,
                watcher,
                mql;

            this.$$elem = element;
            this.$$watchers = [];

            this.$$scope = scope;
            this.$$scope.columns = [];

            //
            // The layout configuration will be parsed from
            // the pseudo "before element." There you have to save all
            // the column configurations.
            //
            this.$$scope.layout = this.$$getLayout();

            this.$$createColumns();

            //
            // Register model change.
            //
            watcher = this.$$scope.$watchCollection('model', this.$$onModelChange.bind(this));

            this.$$watchers.push(watcher);

            //
            // Register media query change events.
            //
            angular.forEach(self.$$getMediaQueries(), function onIteration (rule) {
                var handler = self.$$onMediaQueryChange.bind(self);

                function onDestroy () {
                    rule.removeListener(handler);
                }

                rule.addListener(handler);

                self.$$watchers.push(onDestroy);
            });

            mql = $window.matchMedia('(orientation: portrait)');
            mql.addListener(self.$$onMediaQueryChange.bind(self));

        }

        /**
         * @private
         *
         * Extracts the media queries out of the stylesheets.
         *
         * This method will fetch the media queries out of the stylesheets that are
         * responsible for styling the angular-deckgrid.
         *
         * @return {array} An array with all respective styles.
         *
         */
        Deckgrid.prototype.$$getMediaQueries = function $$getMediaQueries () {
            var stylesheets = [],
                mediaQueries = [];

            stylesheets = Array.prototype.concat.call(
                Array.prototype.slice.call(document.querySelectorAll('style[type=\'text/css\']')),
                Array.prototype.slice.call(document.querySelectorAll('link[rel=\'stylesheet\']'))
            );

            function extractRules (stylesheet) {
                try {
                    return (stylesheet.sheet.cssRules || []);
                } catch (e) {
                    return [];
                }
            }

            function hasDeckgridStyles (rule) {
                var regexe   = /\[(\w*-)?deckgrid\]::?before/g,
                    i        = 0,
                    selector = '';

                if (!rule.media || angular.isUndefined(rule.cssRules)) {
                    return false;
                }

                i = rule.cssRules.length - 1;

                for (i; i >= 0; i = i - 1) {
                    selector = rule.cssRules[i].selectorText;

                    if (angular.isDefined(selector) && selector.match(regexe)) {
                        return true;
                    }
                }

                return false;
            }

            angular.forEach(stylesheets, function onIteration (stylesheet) {
                var rules = extractRules(stylesheet);

                angular.forEach(rules, function inRuleIteration (rule) {
                    if (hasDeckgridStyles(rule)) {
                        mediaQueries.push($window.matchMedia(rule.media.mediaText));
                    }
                });
            });

            return mediaQueries;
        };

        /**
         * @private
         *
         * Creates the column segmentation. With other words:
         * This method creates the internal data structure from the
         * passed "source" attribute. Every card within this "source"
         * model will be passed into this internal column structure by
         * reference. So if you modify the data within your controller
         * this directive will reflect these changes immediately.
         *
         * NOTE that calling this method will trigger a complete template "redraw".
         *
         */
        Deckgrid.prototype.$$createColumns = function $$createColumns () {
            var self = this;

            if (!this.$$scope.layout) {
                return $log.error('angular-deckgrid: No CSS configuration found (see ' +
                                   'https://github.com/akoenig/angular-deckgrid#the-grid-configuration)');
            }

            this.$$scope.columns = [];

            angular.forEach(this.$$scope.model, function onIteration (card, index) {
                var column = (index % self.$$scope.layout.columns) | 0;

                if (!self.$$scope.columns[column]) {
                    self.$$scope.columns[column] = [];
                }

                card.$index = index;
                self.$$scope.columns[column].push(card);
            });
        };

        /**
         * @private
         *
         * Parses the configuration out of the configured CSS styles.
         *
         * Example:
         *
         *     .deckgrid::before {
         *         content: '3 .column.size-1-3';
         *     }
         *
         * Will result in a three column grid where each column will have the
         * classes: "column size-1-3".
         *
         * You are responsible for defining the respective styles within your CSS.
         *
         */
        Deckgrid.prototype.$$getLayout = function $$getLayout () {
            var content = $window.getComputedStyle(this.$$elem, ':before').content,
                layout;

            if (content) {
                content = content.replace(/'/g, '');  // before e.g. '3 .column.size-1of3'
                content = content.replace(/"/g, '');  // before e.g. "3 .column.size-1of3"
                content = content.split(' ');

                if (2 === content.length) {
                    layout = {};
                    layout.columns = (content[0] | 0);
                    layout.classList = content[1].replace(/\./g, ' ').trim();
                }
            }

            return layout;
        };

        /**
         * @private
         *
         * Event that will be triggered if a CSS media query changed.
         *
         */
        Deckgrid.prototype.$$onMediaQueryChange = function $$onMediaQueryChange () {
            var self = this,
                layout = this.$$getLayout();

            //
            // Okay, the layout has changed.
            // Creating a new column structure is not avoidable.
            //
            if (layout.columns !== this.$$scope.layout.columns) {
                self.$$scope.layout = layout;

                self.$$scope.$apply(function onApply () {
                    self.$$createColumns();
                });
            }
        };

        /**
         * @private
         *
         * Event that will be triggered when the source model has changed.
         *
         */
        Deckgrid.prototype.$$onModelChange = function $$onModelChange (newModel, oldModel) {
            var self = this;

            newModel = newModel || [];
            oldModel = oldModel || [];

            if (!angular.equals(oldModel, newModel)) {
                self.$$createColumns();
            }
        };

        /**
         * Destroys the directive. Takes care of cleaning all
         * watchers and event handlers.
         *
         */
        Deckgrid.prototype.destroy = function destroy () {
            var i = this.$$watchers.length - 1;

            for (i; i >= 0; i = i - 1) {
                this.$$watchers[i]();
            }
        };

        return {
            create : function create (scope, element) {
                return new Deckgrid(scope, element);
            }
        };
    }
]);
<|endoftext|>


<commit_before>require('moonstone');

/**
* Contains the declaration for the {@link module:moonstone/Drawers~Drawers} kind.
* @module moonstone/Drawers
*/

var
	kind = require('enyo/kind'),
	dispatcher = require('enyo/dispatcher'),
	util = require('enyo/utils'),
	Animator = require('enyo/Animator'),
	Control = require('enyo/Control'),
	Drawer = require('enyo/Drawer'),
	dom = require('enyo/dom');

var
	Spotlight = require('spotlight');

var
	options = require('../options'),
	Icon = require('../Icon'),
	Item = require('../Item'),
	MoonHistory = require('../History'),
	HistorySupport = MoonHistory.HistorySupport;

var
	MoonDrawer = require('./Drawer');

/**
* {@link module:moonstone/Drawers~Drawers} is a container kind designed to hold a set of {@link module:moonstone/Drawer~Drawer}
* objects and client content. The [drawers]{@link module:moonstone/Drawers~Drawers#drawers} property accepts
* an array of Drawer controls. The associated handles are positioned in their own small
* drawer, centered at the top of the "dresser" (the region containing the array of Drawer
* controls and the activator nub).
*
* When a handle is selected, it opens the corresponding Drawer object's main drawer or
* control drawer, depending on how the Drawer object is configured. The control's child
* components may be of any kind.
*
* ```
* {
*	kind: 'moon.Drawers',
*	drawers: [
*		{
*			name: 'musicDrawer',
*			kind: 'moon.Drawer',
*			handle: {kind: 'moon.DrawerHandle', content: 'Handle'},
*			components: [
*				{content: 'Drawer Content'}
*			],
*			controlDrawerComponents: [
*				{content: 'Controls'}
*			]
*		}
*	],
*	components: [
*		{content: 'Content Area'}
*	]
* }
* ```
*
* @class Drawers
* @extends module:enyo/Control~Control
* @ui
* @public
*/
module.exports = kind(
	/** @lends module:moonstone/Drawers~Drawers.prototype */ {

	/**
	* @private
	*/
	name: 'moon.Drawers',

	/**
	* @private
	*/
	kind: Control,

	/**
	* @private
	*/
	mixins : [HistorySupport],

	/**
	* @private
	*/
	classes: 'moon-drawers enyo-fit',

	/**
	* @private
	* @lends module:moonstone/Drawers~Drawers.prototype
	*/
	published: {

		/**
		* Populate with an array of {@link module:moonstone/Drawer~Drawer} components.
		*
		* @type {Object[]}
		* @default null
		* @public
		*/
		drawers: null,

		/**
		* When using a font-based icon, the name of the icon to be used.
		* The following icon names are valid:
		*
		* 'drawer'
		* 'arrowlargeup'
		* 'arrowlargedown'
		* 'arrowlargeleft'
		* 'arrowlargeright'
		* 'arrowsmallup'
		* 'arrowsmalldown'
		* 'arrowsmallleft'
		* 'arrowsmallright'
		* 'closex'
		* 'check'
		* 'search'
		*
		* @type {String}
		* @default ''
		* @public
		*/
		icon: '',

		/**
		* URL specifying path to icon image.
		*
		* @type {String|module:enyo/resolution#selectSrc~src}
		* @default ''
		* @public
		*/
		src: ''
	},

	/**
	* Default icon displayed on drawer handle when open
	*
	* @type {String}
	* @default 'arrowsmalldown'
	* @private
	*/
	iconClosed: 'arrowsmalldown',

	/**
	* Default icon displayed on drawer handle when closed
	*
	* @type {String}
	* @default 'arrowsmallup'
	* @private
	*/
	iconOpen: 'arrowsmallup',

	/**
	* Cached value of the top position of the client area. Used for non-accelerated animation.
	*
	* @type {Number}
	* @default 0
	* @private
	*/
	clientTop: 0,

	/**
	* @private
	*/
	handlers: {
		//* Handlers to update the activator when the state of the contained drawers changes
		onActivate: 'drawerActivated',
		onDeactivate: 'drawerDeactivated',
		onSpotlightDown: 'spotDown',
		onSpotlightUp: 'spotUp'
	},

	/**
	* @private
	*/
	components: [
		{name: 'activator', kind: Control, classes: 'moon-drawers-activator', spotlight: true, ontap: 'activatorTapped', components: [
			{name: 'activatorIcon', kind: Icon, classes: 'moon-drawers-activator-icon', small: false}
		]},
		{name: 'handleContainer', kind: Drawer, classes: 'moon-drawers-handle-container', resizeContainer: false, open: false, spotlightDisabled: true, components: [
			{name: 'handles', kind: Control, classes: 'moon-neutral moon-drawers-handles'}
		]},
		{name: 'drawers', kind: Control, classes: 'moon-drawers-drawer-container'},
		{name: 'client', kind: Control, classes: 'moon-drawers-client'}
	],

	/**
	* @private
	*/
	eventsToCapture: {
		ontap: 'captureTapSelect',
		onSpotlightFocus: 'captureSpotFocus',
		onSpotlightSelect: 'captureTapSelect'
	},

	/**
	* @private
	*/
	create: function () {
		Control.prototype.create.apply(this, arguments);
		this.drawers = this.$.drawers.createComponents(this.drawers, {kind: MoonDrawer, owner: this.owner});
		if (!options.accelerate) this.createComponent({name: 'animator', kind: Animator, onStep: 'animationStep'});
		this.setupHandles();
		this.updateActivator();
	},

	/**
	* @private
	*/
	rendered: function () {
		Control.prototype.rendered.apply(this, arguments);
		this.updateDrawerHeight();
	},

	/**
	* @private
	*/
	destroy: function () {
		var i,
			c$ = this.drawers;
		dispatcher.release(this.$.handleContainer);
		for (i = 0; i < c$.length; i++) {
			dispatcher.release(c$[i]);
		}
		Control.prototype.destroy.apply(this, arguments);
	},

	/**
	* @private
	*/
	srcChanged: function () {
		this.updateActivator();
	},

	/**
	* @private
	*/
	iconChanged: function () {
		this.updateActivator();
	},

	/**
	* @private
	*/
	setupHandles: function () {
		var i, len, handle,
			def = {kind: Item, owner: this},
			tap = this.bindSafely(this.handleTapped);

		for (i = 0, len = this.drawers.length; i < len; i++) {
			handle = this.$.handles.createComponent(this.drawers[i].handle || {}, def);
			handle.addClass('moon-drawers-handle');
			handle.tap = tap;
		}
	},

	/**
	* Updates the icon and styling of the activator for the state of the drawer
	*
	* @param {Boolean} open Drawer state
	* @private
	*/
	updateActivator: function (open) {
		var icon, src;

		if (open) {
			src = '';
			icon = this.iconOpen;
		} else {
			src = this.src;
			icon = (src && !icon) ? '' : (this.icon || this.iconClosed);
		}

		this.$.activator.addRemoveClass('open', open);
		this.$.activatorIcon.set('src', src);
		this.$.activatorIcon.set('icon', icon);
	},

	/**
	* Inform each drawer of the total available height
	*
	* @private
	*/
	updateDrawerHeight: function () {
		var dh = document.body.getBoundingClientRect().height,
			drawer, i;
		for (i = this.drawers.length - 1; i >= 0; --i) {
			drawer = this.drawers[i];
			if (drawer instanceof MoonDrawer) drawer.set('fullHeight', dh);
		}
	},

	/**
	* @private
	*/
	activatorTapped: function () {
		var handles;
		if (this.isDrawerOpen()) {
			this.closeDrawers();
		} else {
			handles = this.$.handles.getControls();
			if (handles.length == 1) {
				this.openDrawer(handles[0]);
				this.updateActivator(true);
			} else if (this.$.handleContainer.getOpen()) {
				this.closeHandleContainer();
			} else {
				this.openHandleContainer();
			}
		}
	},

	/**
	* @private
	*/
	openHandleContainer: function () {
		this.$.handleContainer.spotlightDisabled = false;
		this.$.handleContainer.setOpen(true);
		this.updateActivator(true);
		dispatcher.capture(this.$.handleContainer, this.eventsToCapture, this);
		if (this.allowBackKey) {
			this.pushBackHistory();
		}
	},

	/**
	* @private
	*/
	closeHandleContainer: function () {
		dispatcher.release(this.$.handleContainer);
		this.$.handleContainer.spotlightDisabled = true;
		this.$.handleContainer.setOpen(false);
		this.updateActivator(this.isDrawerOpen());
	},

	/**
	* @private
	*/
	handleTapped: function (sender, ev) {
		this.openDrawer(ev.originator);
		return true;
	},

	/**
	* @private
	*/
	openDrawer: function (handle) {
		var drawer,
			handles = this.$.handles.getControls();
		for (var index = 0; index < handles.length; ++index) {
			if (handle.isDescendantOf(handles[index])) {
				drawer = this.$.drawers.getControls()[index];
				drawer.show();
				drawer.toggleDrawer();
				this.closeHandleContainer();
				dispatcher.capture(drawer, this.eventsToCapture, this);
				return;
			}
		}
	},

	/**
	* @private
	*/
	isDrawerOpen: function () {
		var drawers = this.$.drawers.getControls();
		for (var index = 0; index < drawers.length; ++index) {
			if (drawers[index].get('open') || drawers[index].get('controlsOpen')) {
				return true;
			}
		}
		return false;
	},

	/**
	* @private
	*/
	closeDrawers: function () {
		var drawers = this.$.drawers.getControls();
		for (var index = 0; index < drawers.length; ++index) {
			var drawer = drawers[index];
			if (drawer.get('open') || drawer.get('controlsOpen')) {
				dispatcher.release(drawer);
				drawer.set('open', false);
				drawer.set('controlsOpen', false);
			}
		}
		this.$.client.spotlightDisabled = false;
		this.updateActivator(false);
	},

	/**
	* @private
	*/
	captureSpotFocus: function (sender, ev) {
		// Only close drawers on 5-way focus in the client (not pointer focus)
		if (ev.dir && ev.dispatchTarget.isDescendantOf(this.$.client)) {
			this.closeDrawers();
			this.closeHandleContainer();
		}
	},

	/**
	* @private
	*/
	captureTapSelect: function (sender, ev) {
		// Any tap or select in the client area closes the dresser/drawer
		if (ev.dispatchTarget.isDescendantOf(this.$.client)) {
			this.closeDrawers();
			this.closeHandleContainer();
		}
	},

	/**
	* Animates the position of the client area when a drawer is opened or closed
	*
	* @private
	*/
	animate: function (height) {
		var top;
		if (options.accelerate) {
			dom.transformValue(this.$.client, 'translateY', height + 'px');
		} else {
			this.$.animator.play({
				startValue: this.clientTop,
				endValue: height
			});
		}
	},

	/**
	* When `!options.accelerate`, this handles {@link enyo/Animator#event:onStep} to update the
	* position of the client area
	*
	* @private
	*/
	animationStep: function (sender, event) {
		// cache the last top value to be reused by animate() as the startValue
		this.clientTop = sender.value;
		this.$.client.setBounds({top: this.clientTop + 'px'});
	},

	/**
	* @private
	*/
	drawerActivated: function (sender, ev) {
		var drawer = ev.originator instanceof MoonDrawer ? ev.originator : null;
		if (drawer) {
			this.updateActivator(true);
			// show/hide() is expensive. Use spotlightDisabled feature instead
			this.$.client.spotlightDisabled = drawer.open;
			this.animate(ev.height);
		}
	},

	/**
	* @private
	*/
	drawerDeactivated: function (sender, ev) {
		var drawer = ev.originator instanceof MoonDrawer ? ev.originator : null;
		if (drawer) {
			if (!drawer.open && !drawer.controlsOpen) this.updateActivator(false);
			// show/hide() is expensive. Use spotlightDisabled feature instead
			this.$.client.spotlightDisabled = drawer.open;
			this.animate(ev.height);
		}
	},

	/**
	* @private
	*/
	handleResize: function () {
		Control.prototype.handleResize.apply(this, arguments);
		this.updateDrawerHeight();
		this.updateActivator(false);
	},

	/**
	* @private
	*/
	backKeyHandler: function () {
		this.closeHandleContainer();
		return true;
	}
});

module.exports.Drawer = MoonDrawer;<commit_msg>ENYO-898: General clean-up.<commit_after>require('moonstone');

/**
* Contains the declaration for the {@link module:moonstone/Drawers~Drawers} kind.
* @module moonstone/Drawers
*/

var
	kind = require('enyo/kind'),
	dispatcher = require('enyo/dispatcher'),
	Animator = require('enyo/Animator'),
	Control = require('enyo/Control'),
	Drawer = require('enyo/Drawer'),
	dom = require('enyo/dom');

var
	options = require('../options'),
	Icon = require('../Icon'),
	Item = require('../Item'),
	MoonHistory = require('../History'),
	HistorySupport = MoonHistory.HistorySupport;

var
	MoonDrawer = require('./Drawer');

/**
* {@link module:moonstone/Drawers~Drawers} is a container kind designed to hold a set of {@link module:moonstone/Drawer~Drawer}
* objects and client content. The [drawers]{@link module:moonstone/Drawers~Drawers#drawers} property accepts
* an array of Drawer controls. The associated handles are positioned in their own small
* drawer, centered at the top of the "dresser" (the region containing the array of Drawer
* controls and the activator nub).
*
* When a handle is selected, it opens the corresponding Drawer object's main drawer or
* control drawer, depending on how the Drawer object is configured. The control's child
* components may be of any kind.
*
* ```
* {
*	kind: 'moon.Drawers',
*	drawers: [
*		{
*			name: 'musicDrawer',
*			kind: 'moon.Drawer',
*			handle: {kind: 'moon.DrawerHandle', content: 'Handle'},
*			components: [
*				{content: 'Drawer Content'}
*			],
*			controlDrawerComponents: [
*				{content: 'Controls'}
*			]
*		}
*	],
*	components: [
*		{content: 'Content Area'}
*	]
* }
* ```
*
* @class Drawers
* @extends module:enyo/Control~Control
* @ui
* @public
*/
module.exports = kind(
	/** @lends module:moonstone/Drawers~Drawers.prototype */ {

	/**
	* @private
	*/
	name: 'moon.Drawers',

	/**
	* @private
	*/
	kind: Control,

	/**
	* @private
	*/
	mixins : [HistorySupport],

	/**
	* @private
	*/
	classes: 'moon-drawers enyo-fit',

	/**
	* @private
	* @lends module:moonstone/Drawers~Drawers.prototype
	*/
	published: {

		/**
		* Populate with an array of {@link module:moonstone/Drawer~Drawer} components.
		*
		* @type {Object[]}
		* @default null
		* @public
		*/
		drawers: null,

		/**
		* When using a font-based icon, the name of the icon to be used.
		* The following icon names are valid:
		*
		* 'drawer'
		* 'arrowlargeup'
		* 'arrowlargedown'
		* 'arrowlargeleft'
		* 'arrowlargeright'
		* 'arrowsmallup'
		* 'arrowsmalldown'
		* 'arrowsmallleft'
		* 'arrowsmallright'
		* 'closex'
		* 'check'
		* 'search'
		*
		* @type {String}
		* @default ''
		* @public
		*/
		icon: '',

		/**
		* URL specifying path to icon image.
		*
		* @type {String|module:enyo/resolution#selectSrc~src}
		* @default ''
		* @public
		*/
		src: ''
	},

	/**
	* Default icon displayed on drawer handle when open
	*
	* @type {String}
	* @default 'arrowsmalldown'
	* @private
	*/
	iconClosed: 'arrowsmalldown',

	/**
	* Default icon displayed on drawer handle when closed
	*
	* @type {String}
	* @default 'arrowsmallup'
	* @private
	*/
	iconOpen: 'arrowsmallup',

	/**
	* Cached value of the top position of the client area. Used for non-accelerated animation.
	*
	* @type {Number}
	* @default 0
	* @private
	*/
	clientTop: 0,

	/**
	* @private
	*/
	handlers: {
		//* Handlers to update the activator when the state of the contained drawers changes
		onActivate: 'drawerActivated',
		onDeactivate: 'drawerDeactivated',
		onSpotlightDown: 'spotDown',
		onSpotlightUp: 'spotUp'
	},

	/**
	* @private
	*/
	components: [
		{name: 'activator', kind: Control, classes: 'moon-drawers-activator', spotlight: true, ontap: 'activatorTapped', components: [
			{name: 'activatorIcon', kind: Icon, classes: 'moon-drawers-activator-icon', small: false}
		]},
		{name: 'handleContainer', kind: Drawer, classes: 'moon-drawers-handle-container', resizeContainer: false, open: false, spotlightDisabled: true, components: [
			{name: 'handles', kind: Control, classes: 'moon-neutral moon-drawers-handles'}
		]},
		{name: 'drawers', kind: Control, classes: 'moon-drawers-drawer-container'},
		{name: 'client', kind: Control, classes: 'moon-drawers-client'}
	],

	/**
	* @private
	*/
	eventsToCapture: {
		ontap: 'captureTapSelect',
		onSpotlightFocus: 'captureSpotFocus',
		onSpotlightSelect: 'captureTapSelect'
	},

	/**
	* @private
	*/
	create: function () {
		Control.prototype.create.apply(this, arguments);
		this.drawers = this.$.drawers.createComponents(this.drawers, {kind: MoonDrawer, owner: this.owner});
		if (!options.accelerate) this.createComponent({name: 'animator', kind: Animator, onStep: 'animationStep'});
		this.setupHandles();
		this.updateActivator();
	},

	/**
	* @private
	*/
	rendered: function () {
		Control.prototype.rendered.apply(this, arguments);
		this.updateDrawerHeight();
	},

	/**
	* @private
	*/
	destroy: function () {
		var i,
			c$ = this.drawers;
		dispatcher.release(this.$.handleContainer);
		for (i = 0; i < c$.length; i++) {
			dispatcher.release(c$[i]);
		}
		Control.prototype.destroy.apply(this, arguments);
	},

	/**
	* @private
	*/
	srcChanged: function () {
		this.updateActivator();
	},

	/**
	* @private
	*/
	iconChanged: function () {
		this.updateActivator();
	},

	/**
	* @private
	*/
	setupHandles: function () {
		var i, len, handle,
			def = {kind: Item, owner: this},
			tap = this.bindSafely(this.handleTapped);

		for (i = 0, len = this.drawers.length; i < len; i++) {
			handle = this.$.handles.createComponent(this.drawers[i].handle || {}, def);
			handle.addClass('moon-drawers-handle');
			handle.tap = tap;
		}
	},

	/**
	* Updates the icon and styling of the activator for the state of the drawer
	*
	* @param {Boolean} open Drawer state
	* @private
	*/
	updateActivator: function (open) {
		var icon, src;

		if (open) {
			src = '';
			icon = this.iconOpen;
		} else {
			src = this.src;
			icon = (src && !icon) ? '' : (this.icon || this.iconClosed);
		}

		this.$.activator.addRemoveClass('open', open);
		this.$.activatorIcon.set('src', src);
		this.$.activatorIcon.set('icon', icon);
	},

	/**
	* Inform each drawer of the total available height
	*
	* @private
	*/
	updateDrawerHeight: function () {
		var dh = document.body.getBoundingClientRect().height,
			drawer, i;
		for (i = this.drawers.length - 1; i >= 0; --i) {
			drawer = this.drawers[i];
			if (drawer instanceof MoonDrawer) drawer.set('fullHeight', dh);
		}
	},

	/**
	* @private
	*/
	activatorTapped: function () {
		var handles;
		if (this.isDrawerOpen()) {
			this.closeDrawers();
		} else {
			handles = this.$.handles.getControls();
			if (handles.length == 1) {
				this.openDrawer(handles[0]);
				this.updateActivator(true);
			} else if (this.$.handleContainer.getOpen()) {
				this.closeHandleContainer();
			} else {
				this.openHandleContainer();
			}
		}
	},

	/**
	* @private
	*/
	openHandleContainer: function () {
		this.$.handleContainer.spotlightDisabled = false;
		this.$.handleContainer.setOpen(true);
		this.updateActivator(true);
		dispatcher.capture(this.$.handleContainer, this.eventsToCapture, this);
		if (this.allowBackKey) {
			this.pushBackHistory();
		}
	},

	/**
	* @private
	*/
	closeHandleContainer: function () {
		dispatcher.release(this.$.handleContainer);
		this.$.handleContainer.spotlightDisabled = true;
		this.$.handleContainer.setOpen(false);
		this.updateActivator(this.isDrawerOpen());
	},

	/**
	* @private
	*/
	handleTapped: function (sender, ev) {
		this.openDrawer(ev.originator);
		return true;
	},

	/**
	* @private
	*/
	openDrawer: function (handle) {
		var drawer,
			handles = this.$.handles.getControls();
		for (var index = 0; index < handles.length; ++index) {
			if (handle.isDescendantOf(handles[index])) {
				drawer = this.$.drawers.getControls()[index];
				drawer.show();
				drawer.toggleDrawer();
				this.closeHandleContainer();
				dispatcher.capture(drawer, this.eventsToCapture, this);
				return;
			}
		}
	},

	/**
	* @private
	*/
	isDrawerOpen: function () {
		var drawers = this.$.drawers.getControls();
		for (var index = 0; index < drawers.length; ++index) {
			if (drawers[index].get('open') || drawers[index].get('controlsOpen')) {
				return true;
			}
		}
		return false;
	},

	/**
	* @private
	*/
	closeDrawers: function () {
		var drawers = this.$.drawers.getControls();
		for (var index = 0; index < drawers.length; ++index) {
			var drawer = drawers[index];
			if (drawer.get('open') || drawer.get('controlsOpen')) {
				dispatcher.release(drawer);
				drawer.set('open', false);
				drawer.set('controlsOpen', false);
			}
		}
		this.$.client.spotlightDisabled = false;
		this.updateActivator(false);
	},

	/**
	* @private
	*/
	captureSpotFocus: function (sender, ev) {
		// Only close drawers on 5-way focus in the client (not pointer focus)
		if (ev.dir && ev.dispatchTarget.isDescendantOf(this.$.client)) {
			this.closeDrawers();
			this.closeHandleContainer();
		}
	},

	/**
	* @private
	*/
	captureTapSelect: function (sender, ev) {
		// Any tap or select in the client area closes the dresser/drawer
		if (ev.dispatchTarget.isDescendantOf(this.$.client)) {
			this.closeDrawers();
			this.closeHandleContainer();
		}
	},

	/**
	* Animates the position of the client area when a drawer is opened or closed
	*
	* @private
	*/
	animate: function (height) {
		if (options.accelerate) {
			dom.transformValue(this.$.client, 'translateY', height + 'px');
		} else {
			this.$.animator.play({
				startValue: this.clientTop,
				endValue: height
			});
		}
	},

	/**
	* When `!options.accelerate`, this handles {@link enyo/Animator#event:onStep} to update the
	* position of the client area
	*
	* @private
	*/
	animationStep: function (sender, event) {
		// cache the last top value to be reused by animate() as the startValue
		this.clientTop = sender.value;
		this.$.client.setBounds({top: this.clientTop + 'px'});
	},

	/**
	* @private
	*/
	drawerActivated: function (sender, ev) {
		var drawer = ev.originator instanceof MoonDrawer ? ev.originator : null;
		if (drawer) {
			this.updateActivator(true);
			// show/hide() is expensive. Use spotlightDisabled feature instead
			this.$.client.spotlightDisabled = drawer.open;
			this.animate(ev.height);
		}
	},

	/**
	* @private
	*/
	drawerDeactivated: function (sender, ev) {
		var drawer = ev.originator instanceof MoonDrawer ? ev.originator : null;
		if (drawer) {
			if (!drawer.open && !drawer.controlsOpen) this.updateActivator(false);
			// show/hide() is expensive. Use spotlightDisabled feature instead
			this.$.client.spotlightDisabled = drawer.open;
			this.animate(ev.height);
		}
	},

	/**
	* @private
	*/
	handleResize: function () {
		Control.prototype.handleResize.apply(this, arguments);
		this.updateDrawerHeight();
		this.updateActivator(false);
	},

	/**
	* @private
	*/
	backKeyHandler: function () {
		this.closeHandleContainer();
		return true;
	}
});

module.exports.Drawer = MoonDrawer;<|endoftext|>


<commit_before>const { Knorm, KnormError } = require('knorm');

class KnormTimestamps {
  constructor(config = {}) {
    const { createdAt, updatedAt } = config;

    this.fields = {};
    this.fields[(createdAt && createdAt.name) || 'createdAt'] = {
      type: 'dateTime',
      column: (createdAt && createdAt.column) || 'created_at',
      default: () => new Date(),
      updated: false
    };
    this.updatedAtFieldName = (updatedAt && updatedAt.name) || 'updatedAt';
    this.fields[this.updatedAtFieldName] = {
      type: 'dateTime',
      column: (updatedAt && updatedAt.column) || 'updated_at',
      default: () => new Date()
    };
  }

  updateModel(knorm) {
    knorm.Model = class ModelWithTimestamps extends knorm.Model {};
    knorm.Model.fields = this.fields;
  }

  updateQuery(knorm) {
    const { updatedAtFieldName } = this;
    class QueryWithTimestamps extends knorm.Query {
      async update(data, options) {
        if (data && updatedAtFieldName) {
          data[updatedAtFieldName] = new Date();
        }
        return super.update(data, options);
      }
    }
    knorm.Model.Query = knorm.Query = QueryWithTimestamps;
  }

  init(knorm) {
    if (!knorm) {
      throw new KnormError('KnormTimestamps: no Knorm instance provided');
    }

    if (!(knorm instanceof Knorm)) {
      throw new KnormError('KnormTimestamps: invalid Knorm instance provided');
    }

    this.updateModel(knorm);
    this.updateQuery(knorm);
  }
}

module.exports = KnormTimestamps;
<commit_msg>fix: fix default values for `createdAt` and `updatedAt`<commit_after>const { Knorm, KnormError } = require('knorm');

class KnormTimestamps {
  constructor(config = {}) {
    const { createdAt = {}, updatedAt = {} } = config;

    this.fields = {};
    this.fields[createdAt.name || 'createdAt'] = {
      type: 'dateTime',
      column: createdAt.column || 'created_at',
      default: () => new Date(),
      updated: false
    };
    this.updatedAtFieldName = updatedAt.name || 'updatedAt';
    this.fields[this.updatedAtFieldName] = {
      type: 'dateTime',
      column: updatedAt.column || 'updated_at',
      default: () => new Date()
    };
  }

  updateModel(knorm) {
    knorm.Model = class ModelWithTimestamps extends knorm.Model {};
    knorm.Model.fields = this.fields;
  }

  updateQuery(knorm) {
    const { updatedAtFieldName } = this;
    class QueryWithTimestamps extends knorm.Query {
      async update(data, options) {
        if (data) {
          data[updatedAtFieldName] = new Date();
        }
        return super.update(data, options);
      }
    }
    knorm.Model.Query = knorm.Query = QueryWithTimestamps;
  }

  init(knorm) {
    if (!knorm) {
      throw new KnormError('KnormTimestamps: no Knorm instance provided');
    }

    if (!(knorm instanceof Knorm)) {
      throw new KnormError('KnormTimestamps: invalid Knorm instance provided');
    }

    this.updateModel(knorm);
    this.updateQuery(knorm);
  }
}

module.exports = KnormTimestamps;
<|endoftext|>


<commit_before>/* jshint node: true, esversion: 6 */
'use strict';

var async = require('async');
var DOMParser = require('xmldom').DOMParser;
var extract = require('extract-zip');
var find = require('findit2');
var fs = require('fs-extra');
var path = require('path');

var TransformationResult = require('./transformation-result');

var noop = function() {};

/**
 * A transformer for Android Archive artifacts
 */
class AarTransformer {
  /**
   * Constructs a new AAR transformer
   *
   * Uses constructor injection for an optional appc-logger instance
   *
   * @param {Object} logger Instance of an appc-logger (optional)
   */
  constructor(logger) {
    if (logger) {
      this.logger = logger;
    } else {
      this.logger = {
        debug: noop,
        trace: noop
      };
    }
  }

  /**
   * Transforms an Android Archive file using the given options.
   *
   * @param {Object} object Options object
   * @param {Function} callback Callback function
   */
  transform(options, callback) {
    this.reset();

    this.logger.debug('AAR transform options: %s', JSON.stringify(options, null, 2));

    async.series([
      async.apply(this.validateAndApplyOptions.bind(this), options),
      (next) => {
        this.logger.trace('Start processing: %s', this.aarBasename + '.aar');
        next();
      },
      this.extractAndroidArchive.bind(this),
      (next) => {
        async.parallel([
          this.identifyPackageName.bind(this),
          this.registerAndCopyLibraries.bind(this),
          this.copyAssets.bind(this),
          this.copySharedLibraries.bind(this)
        ], next);
      },
      (next) => {
        this.logger.trace('Done processing: %s', this.aarBasename + '.aar');
        next();
      },
    ], (err) => {
      callback(err, this.result);
    });
  }

  /**
   * Resets this transformer for a new transform task.
   */
  reset() {
    this.result = new TransformationResult();
    this.aarPathAndFilename = null;
    this.outputPath = null;
    this.assetsDestinationPath = null;
    this.libraryDestinationPath = null;
    this.sharedLibraryDestinationPath = null;
  }

  /**
   * Validates and applies the values of the given options objects to the
   * transformers own properties.
   *
   * @param {Object} options Options object
   * @param {function} next Callback function
   */
  validateAndApplyOptions(options, next) {
    var requiredOptions = ['aarPathAndFilename', 'outputPath'];
    requiredOptions.forEach(function(optionName) {
      if (!options[optionName]) {
        return next(new Error(requiredOptions.join(', ') + ' are required to start the AAR transform.'));
      }
    });

    this.aarPathAndFilename = options.aarPathAndFilename;
    if (!fs.existsSync(this.aarPathAndFilename)) {
      return next(new Error('The file ' + this.aarFile + ' does not exists.'));
    }
    this.aarBasename = path.basename(this.aarPathAndFilename, '.aar');
    this.outputPath = path.join(options.outputPath, this.aarBasename);
    this.assetsDestinationPath = options.assetsDestinationPath ? options.assetsDestinationPath : null;
    this.libraryDestinationPath = options.libraryDestinationPath ? options.libraryDestinationPath : null;
    this.sharedLibraryDestinationPath = options.sharedLibraryDestinationPath ? options.sharedLibraryDestinationPath : null;
    next();
  }

  /**
   * Extracts the android archive file.
   *
   * @param {Function} next Callback function
   */
  extractAndroidArchive(next) {
    this.logger.debug('Extracting: %s => %s', this.aarPathAndFilename, this.outputPath);
    fs.emptyDirSync(this.outputPath);
    extract(this.aarPathAndFilename, {dir: this.outputPath}, next);
  }

  /**
   * Extracts the package name from the module manifest and stores it in the
   * transformation result.
   *
   * @param {Function} next Callback function
   */
  identifyPackageName(next) {
    var manifestFile = path.join(this.outputPath, 'AndroidManifest.xml');
    var contents = fs.readFileSync(manifestFile).toString();
    var doc = new DOMParser().parseFromString(contents, 'text/xml').documentElement;
    this.result.packageName = doc.getAttribute('package');
    this.logger.trace('Package name is: %s', this.result.packageName);
    next();
  }

  /**
   * Registers all found .jar files in the result, optionally copying them to a
   * new destination.
   *
   * @param {Function} next Callback function
   */
  registerAndCopyLibraries(next) {
    var mainJarPathAndFilename = path.join(this.outputPath, 'classes.jar');
    this.logger.trace('Adding main JAR to list: %s', mainJarPathAndFilename);
    this.result.addJar(mainJarPathAndFilename);
    if (this.libraryDestinationPath !== null) {
      var dest = path.join(this.libraryDestinationPath, this.aarBasename + '.jar');
      fs.copySync(mainJarPathAndFilename, dest);
    }

    var libraryPath = path.join(this.outputPath, 'libs');
    if (!fs.existsSync(libraryPath)) {
      return next();
    }
    find(libraryPath)
      .on('file', (file, stat) => {
        if (path.extname(file) !== '.jar') {
          return;
        }

        this.logger.trace('Adding additional JAR to list: %s', file);
        this.result.addJar(file);

        if (this.libraryDestinationPath === null) {
          return next();
        }
        var dest = path.join(this.libraryDestinationPath, path.relative(libraryPath, file));
        fs.ensureDirSync(path.dirname(dest));
        this.logger.debug('Copying: %s => %s', file, dest);
        fs.copySync(file, dest, {logger: this.logger});
      })
      .on('end', next);
  }

  /**
   * Copies any assets found in the archive to a new destination.
   *
   * This will only be done when assetsDestinationPath is specified.
   *
   * @param {Function} next Callback function
   */
  copyAssets(next) {
    if (this.assetsDestinationPath === null) {
      return next();
    }
    var src = path.join(this.outputPath, 'assets');
    if (!fs.existsSync(src)) {
      return next();
    }
    this.logger.debug('Copying: %s => %s', src, this.assetsDestinationPath);
    fs.copy(src, this.assetsDestinationPath, next);
  }

  /**
   * Copies any shared libraries found in the archive to a new destination.
   *
   * This will only be done when sharedLibraryDestinationPath is specified.
   *
   * @param {Function} next Callback function
   */
  copySharedLibraries(next) {
    if (this.sharedLibraryDestinationPath === null) {
      return next();
    }
    var jniPath = path.join(this.outputPath, 'jni');
    if (!fs.existsSync(jniPath)) {
      return next();
    }

    find(jniPath)
      .on('file', (file, stat) => {
        if (path.extname(file) !== '.so') {
          return;
        }
        var dest = path.join(this.sharedLibraryDestinationPath, path.relative(jniPath, file));
        fs.ensureDirSync(path.dirname(dest));
        this.logger.debug('Copying: %s => %s', file, dest);
        fs.copySync(file, dest);
      })
      .on('end', next);
  }
}

module.exports = AarTransformer;
<commit_msg>Use arrow function<commit_after>/* jshint node: true, esversion: 6 */
'use strict';

var async = require('async');
var DOMParser = require('xmldom').DOMParser;
var extract = require('extract-zip');
var find = require('findit2');
var fs = require('fs-extra');
var path = require('path');

var TransformationResult = require('./transformation-result');

var noop = function() {};

/**
 * A transformer for Android Archive artifacts
 */
class AarTransformer {
  /**
   * Constructs a new AAR transformer
   *
   * Uses constructor injection for an optional appc-logger instance
   *
   * @param {Object} logger Instance of an appc-logger (optional)
   */
  constructor(logger) {
    if (logger) {
      this.logger = logger;
    } else {
      this.logger = {
        debug: noop,
        trace: noop
      };
    }
  }

  /**
   * Transforms an Android Archive file using the given options.
   *
   * @param {Object} object Options object
   * @param {Function} callback Callback function
   */
  transform(options, callback) {
    this.reset();

    this.logger.debug('AAR transform options: %s', JSON.stringify(options, null, 2));

    async.series([
      async.apply(this.validateAndApplyOptions.bind(this), options),
      (next) => {
        this.logger.trace('Start processing: %s', this.aarBasename + '.aar');
        next();
      },
      this.extractAndroidArchive.bind(this),
      (next) => {
        async.parallel([
          this.identifyPackageName.bind(this),
          this.registerAndCopyLibraries.bind(this),
          this.copyAssets.bind(this),
          this.copySharedLibraries.bind(this)
        ], next);
      },
      (next) => {
        this.logger.trace('Done processing: %s', this.aarBasename + '.aar');
        next();
      },
    ], (err) => {
      callback(err, this.result);
    });
  }

  /**
   * Resets this transformer for a new transform task.
   */
  reset() {
    this.result = new TransformationResult();
    this.aarPathAndFilename = null;
    this.outputPath = null;
    this.assetsDestinationPath = null;
    this.libraryDestinationPath = null;
    this.sharedLibraryDestinationPath = null;
  }

  /**
   * Validates and applies the values of the given options objects to the
   * transformers own properties.
   *
   * @param {Object} options Options object
   * @param {function} next Callback function
   */
  validateAndApplyOptions(options, next) {
    var requiredOptions = ['aarPathAndFilename', 'outputPath'];
    requiredOptions.forEach((optionName) => {
      if (!options[optionName]) {
        return next(new Error(requiredOptions.join(', ') + ' are required to start the AAR transform.'));
      }
    });

    this.aarPathAndFilename = options.aarPathAndFilename;
    if (!fs.existsSync(this.aarPathAndFilename)) {
      return next(new Error('The file ' + this.aarFile + ' does not exists.'));
    }
    this.aarBasename = path.basename(this.aarPathAndFilename, '.aar');
    this.outputPath = path.join(options.outputPath, this.aarBasename);
    this.assetsDestinationPath = options.assetsDestinationPath ? options.assetsDestinationPath : null;
    this.libraryDestinationPath = options.libraryDestinationPath ? options.libraryDestinationPath : null;
    this.sharedLibraryDestinationPath = options.sharedLibraryDestinationPath ? options.sharedLibraryDestinationPath : null;
    next();
  }

  /**
   * Extracts the android archive file.
   *
   * @param {Function} next Callback function
   */
  extractAndroidArchive(next) {
    this.logger.debug('Extracting: %s => %s', this.aarPathAndFilename, this.outputPath);
    fs.emptyDirSync(this.outputPath);
    extract(this.aarPathAndFilename, {dir: this.outputPath}, next);
  }

  /**
   * Extracts the package name from the module manifest and stores it in the
   * transformation result.
   *
   * @param {Function} next Callback function
   */
  identifyPackageName(next) {
    var manifestFile = path.join(this.outputPath, 'AndroidManifest.xml');
    var contents = fs.readFileSync(manifestFile).toString();
    var doc = new DOMParser().parseFromString(contents, 'text/xml').documentElement;
    this.result.packageName = doc.getAttribute('package');
    this.logger.trace('Package name is: %s', this.result.packageName);
    next();
  }

  /**
   * Registers all found .jar files in the result, optionally copying them to a
   * new destination.
   *
   * @param {Function} next Callback function
   */
  registerAndCopyLibraries(next) {
    var mainJarPathAndFilename = path.join(this.outputPath, 'classes.jar');
    this.logger.trace('Adding main JAR to list: %s', mainJarPathAndFilename);
    this.result.addJar(mainJarPathAndFilename);
    if (this.libraryDestinationPath !== null) {
      var dest = path.join(this.libraryDestinationPath, this.aarBasename + '.jar');
      fs.copySync(mainJarPathAndFilename, dest);
    }

    var libraryPath = path.join(this.outputPath, 'libs');
    if (!fs.existsSync(libraryPath)) {
      return next();
    }
    find(libraryPath)
      .on('file', (file, stat) => {
        if (path.extname(file) !== '.jar') {
          return;
        }

        this.logger.trace('Adding additional JAR to list: %s', file);
        this.result.addJar(file);

        if (this.libraryDestinationPath === null) {
          return next();
        }
        var dest = path.join(this.libraryDestinationPath, path.relative(libraryPath, file));
        fs.ensureDirSync(path.dirname(dest));
        this.logger.debug('Copying: %s => %s', file, dest);
        fs.copySync(file, dest, {logger: this.logger});
      })
      .on('end', next);
  }

  /**
   * Copies any assets found in the archive to a new destination.
   *
   * This will only be done when assetsDestinationPath is specified.
   *
   * @param {Function} next Callback function
   */
  copyAssets(next) {
    if (this.assetsDestinationPath === null) {
      return next();
    }
    var src = path.join(this.outputPath, 'assets');
    if (!fs.existsSync(src)) {
      return next();
    }
    this.logger.debug('Copying: %s => %s', src, this.assetsDestinationPath);
    fs.copy(src, this.assetsDestinationPath, next);
  }

  /**
   * Copies any shared libraries found in the archive to a new destination.
   *
   * This will only be done when sharedLibraryDestinationPath is specified.
   *
   * @param {Function} next Callback function
   */
  copySharedLibraries(next) {
    if (this.sharedLibraryDestinationPath === null) {
      return next();
    }
    var jniPath = path.join(this.outputPath, 'jni');
    if (!fs.existsSync(jniPath)) {
      return next();
    }

    find(jniPath)
      .on('file', (file, stat) => {
        if (path.extname(file) !== '.so') {
          return;
        }
        var dest = path.join(this.sharedLibraryDestinationPath, path.relative(jniPath, file));
        fs.ensureDirSync(path.dirname(dest));
        this.logger.debug('Copying: %s => %s', file, dest);
        fs.copySync(file, dest);
      })
      .on('end', next);
  }
}

module.exports = AarTransformer;
<|endoftext|>


<commit_before>function DroongaProtocolConnectionWrapper(connection, callback, options) {
  this._connection = connection;
  this._callback = callback;
  this._options = options;
}
DroongaProtocolConnectionWrapper.prototype = {
  get routeToSelf() {
    return this._routeToSelf ||
           (this._routeToSelf = this._connection.getRouteToSelf(this._options));
  },
  emit: function(event, data, callback) {
    if (callback) {
      var originalCallback = callback;
      callback = function(error, response) {
        originalCallback(error, response && response.body);
      };
    } else {
      callback = this._callback;
    }
    this._connection.emitMessage(event, data, callback, this._options);
  },
  destroy: function() {
    delete this._connection;
    delete this._callback;
    delete this._options;
  }
};
exports.DroongaProtocolConnectionWrapper = DroongaProtocolConnectionWrapper;

function SocketIOClientSocketWrapper(socket, options) {
  this._socket = socket;
  this._options = options || {};
}
SocketIOClientSocketWrapper.prototype = {
  emit: function(event, data) {
    if (this._options.event)
      event = this._options.event;
    this._socket.emit(event, data);
  },
  destroy: function() {
    delete this._socket;
  }
};
exports.SocketIOClientSocketWrapper = SocketIOClientSocketWrapper;
<commit_msg>DroongaProtocolConnectionWrapper now supports callback-less pattern<commit_after>function DroongaProtocolConnectionWrapper(connection, callback, options) { // or (connection, options)
  this._connection = connection;
  if (typeof callback == 'function') {
    this._callback = callback;
    this._options = options;
  } else {
    this._callback = null;
    this._options = callback;
  }
}
DroongaProtocolConnectionWrapper.prototype = {
  get routeToSelf() {
    return this._routeToSelf ||
           (this._routeToSelf = this._connection.getRouteToSelf(this._options));
  },
  emit: function(event, data, callback) {
    if (callback) {
      var originalCallback = callback;
      callback = function(error, response) {
        originalCallback(error, response && response.body);
      };
    } else {
      callback = this._callback;
    }
    if (callback)
      this._connection.emitMessage(event, data, callback, this._options);
    else
      this._connection.emitMessage(event, data, this._options);
  },
  destroy: function() {
    delete this._connection;
    delete this._callback;
    delete this._options;
  }
};
exports.DroongaProtocolConnectionWrapper = DroongaProtocolConnectionWrapper;

function SocketIOClientSocketWrapper(socket, options) {
  this._socket = socket;
  this._options = options || {};
}
SocketIOClientSocketWrapper.prototype = {
  emit: function(event, data) {
    if (this._options.event)
      event = this._options.event;
    this._socket.emit(event, data);
  },
  destroy: function() {
    delete this._socket;
  }
};
exports.SocketIOClientSocketWrapper = SocketIOClientSocketWrapper;
<|endoftext|>