<commit_before>/** @fileOverview Routing functionality */

var Peer = require('./peer.js');
var BigInteger = require('./third_party/BigInteger.js');
var Sha1 = require('./third_party/sha1.js');
var Range = require('./chord/range.js');

/**
 * @constructor
 * @class The main routing class. Used by {@link ConnectionManager} instances to route
 * messages through the user network.
 *
 * @param id ID of the local peer
 * @param fallbackSignaling
 */
var Router = function(id, fallbackSignaling, connectionManager) {
    if (!(this instanceof Router)) {
        return new Router();
    }

    this._peerTable = {};
    this._fallbackSignaling = fallbackSignaling;
    this._fallbackSignaling.onmessage = this.onmessage.bind(this);
    this._connectionManager = connectionManager;
    this.id = id;
    this._messageCallbacks = {};
    this._monitorCallback = null;
    this._pendingPutRequests = {};
    this._pendingGetRequests = {};
    this.registerDeliveryCallback('discovery', this._onDiscoveryMessage.bind(this));

    return this;
};

Router.randomId = function() {
    var randomId = Sha1.bigIntHash(Math.random().toString());
    return randomId;
};

Router.prototype = {

    /**
     * Add a peer to the peer table.
     *
     * @param peer {Peer} The peer to add.
     * @todo add test for onclosedconnection behaviour
     */
    addPeer: function(peer) {
        this._peerTable[peer.id] = peer;
        setInterval(function() {
            console.log(Object.keys(this._peerTable));
        }.bind(this), 2000);
        peer.dataChannel.onmessage = this.onmessage.bind(this);
        peer.peerConnection.onclosedconnection = this.removePeer.bind(this, peer);
        if (Object.keys(this._peerTable).length === 1) {
            // ask first peer for its neighbours
            this._discoverNeighbours(peer);
        }
    },

    /**
     * Remove a peer from the peer table.
     *
     * @param peer {Peer} The peer to remove.
     * @todo disconnect peerConnection before removal
     */
    removePeer: function(peer) {
        delete this._peerTable[peer.id];
    },

    /**
     * Return a list of all peer ids currently in the routing table.
     *
     * @returns {Array}
     */
    getPeerIds: function() {
        var peers = [];
        var peer;
        for (peer in this._peerTable) {
            if (this._peerTable.hasOwnProperty(peer)) {
                peers.push(peer);
            }
        }
        return peers;
    },

    onmessage: function(msg) {
        try {
            this.forward(JSON.parse(msg.data));
        } catch (e) {
            console.log('Unable to parse incoming message ' + JSON.stringify(msg.data) + ': ' + e);
        }
    },

    /**
     * Encapsulates message in router format and forwards them.
     *
     * @param to recipient
     * @param type the message type
     * @param payload the message payload
     */
    route: function(to, payload) {
        this.forward({
            to: to.toString(),
            from: this.id.toString(),
            type: 'route',
            payload: payload
        });
    },

    /**
     * Forward message or deliver if recipient is me. This implementation
     * implies that we are using a fully meshed network where every peer
     * is connected to all other peers.
     *
     * @param msg {String} The message to route.
     */
    forward: function(msg) {
        if (typeof(this._monitorCallback) === 'function') {
            this._monitorCallback(msg);
        }
        if (!msg.to) {
            throw Error('Unable to route message because no recipient can be determined');
        }
        if (this.id.toString() === msg.to) {
            this.deliver(msg);
            return;
        }
        var receiver = this._peerTable[msg.to];
        if (!(receiver instanceof Peer)) {
            this._fallbackSignaling.send(JSON.stringify(msg));
            return;
        }
        try {
            receiver.dataChannel.send(JSON.stringify(msg));
        } catch (e) {
            console.log('Unable to route message to ' + msg.to + ' because the DataChannel connection failed.');
        }
    },

    get: function(hash, cb) {
        this._pendingGetRequests[hash.toString()] = cb;
        var peer = this.responsible(hash);
        this.forward({
            to: peer.toString(),
            from: this.id.toString(),
            type: 'get-request',
            payload: {
                hash: hash.toString()
            }
        });
    },

    _handleGetRequest: function(msg) {
        try {
            var val = JSON.parse(localStorage.getItem(msg.payload.hash));
            this.forward({
                to: msg.from,
                from: this.id.toString(),
                type: 'get-response',
                payload: {
                    hash: msg.payload.hash,
                    val: val
                }
            });
        } catch (e) {
            console.log(e);
        }
    },

    _handleGetResponse: function(msg) {
        if (typeof(this._pendingGetRequests[msg.payload.hash]) === 'function') {
            this._pendingGetRequests[msg.payload.hash.toString()](msg.payload.val);
            delete this._pendingGetRequests[msg.payload.hash.toString()];
        }
    },

    put: function(hash, val, cb) {
        this._pendingPutRequests[hash.toString()] = cb;
        var peer = this.responsible(hash);
        this.forward({
            to: peer.toString(),
            from: this.id.toString(),
            type: 'put-request',
            payload: {
                hash: hash.toString(),
                val: val
            }
        });
    },

    _handlePutRequest: function(msg) {
        try {
            localStorage.setItem(msg.payload.hash, JSON.stringify(msg.payload.val));
            this.forward({
                to: msg.from,
                from: this.id.toString(),
                type: 'put-response',
                payload: {
                    hash: msg.payload.hash
                }
            });
        } catch (e) {
            console.log(e);
        }
    },

    _handlePutResponse: function(msg) {
        if (typeof(this._pendingPutRequests[msg.payload.hash]) === 'function') {
            this._pendingPutRequests[msg.payload.hash](null);
        }
    },

    responsible: function(hash) {
        var candidate = this.id;
        for (var k in this._peerTable) {
            if (Range.inLeftClosedInterval(new BigInteger(k), hash, candidate)) {
                candidate = new BigInteger(k);
            }
        }
        return candidate;
    },

    /**
     * Deliver a message to this peer. Is called when the `to` field of
     * the message contains the id of this peer. Decides where to deliver
     * the message to by calling the registered callback using the `type`
     * field (e.g. webrtc connection/ neighbour discovery/ application) of
     * the message.
     *
     * @param msg {String}
     */
    deliver: function(msg) {
        switch (msg.type) {
            case 'route':
                try {
                    this._messageCallbacks[msg.payload.type](msg.payload);
                } catch (e) {
                    console.log(msg);
                    console.log('Unable to handle message of type ' + msg.payload.type + ' from ' + msg.payload.from + ' because no callback is registered: ' + e);
                }
                break;
            case 'get-request':
                this._handleGetRequest(msg);
                break;
            case 'get-response':
                this._handleGetResponse(msg);
                break;
            case 'put-request':
                this._handlePutRequest(msg);
                break;
            case 'put-response':
                this._handlePutResponse(msg);
                break;
            default:
                console.log('Discarding message', msg, 'because the type is unknown');
        }
    },

    /**
     * Register a delivery callback. The registered callback gets
     * called when a specific type of message arrives with the `from`
     * field set to this peers' id.
     *
     * @param msgType {String} refers to the `type`-field of the message
     * this callback should respond to
     * @param callback {Function} The callback to call when a message of
     * the given type arrives
     */
    registerDeliveryCallback: function(msgType, callback) {
        this._messageCallbacks[msgType] = callback;
    },

    /**
     * Register a monitor callback. The registered callback gets
     * called when a message arrives with the `from` field set to
     * this peers' id.
     *
     * @param callback {Function} The callback to call when a message is delivered
     */
    registerMonitorCallback: function(callback) {
        this._monitorCallback = callback;
    },

    /**
     * Kick off neighbour discovery mechanism by sending a `discovery-request' message to
     * a connected peer.
     *
     * @param peer {Peer}
     * @todo implement
     */
    _discoverNeighbours: function(peer) {
        this.route(peer.id, {
            type: 'discovery',
            payload: {
                type: 'request',
                from: this.id.toString()
            }
        });
    },

    _onDiscoveryMessage: function(msg) {
        switch (msg.payload.type) {
            case 'response':
                this._processDiscoveryResponse(msg);
                break;
            case 'request':
                this._processDiscoveryRequest(msg);
                break;
            default:
                console.log('Router: received invalid discovery message with type %s from %s', msg.payload.type, msg.payload.from);
                break;
        }
    },

    /**
     * Gets called when a neighbour discovery response message is received.
     *
     * @param msg {String} Message containing ids of another peers peer table.
     * @todo should this call the connection manager?
     */
    _processDiscoveryResponse: function(msg) {
        //console.log('connecting to', msg.payload.ids)
        var i, ids = msg.payload.ids;
        for (i = 0; i < ids.length; i++) {
            if (ids[i] !== this.id) {
                this._connectionManager.connect(ids[i]);
            }
        }
    },

    /**
     * Answer a received neighbour discovery message. Respond with
     * known ids of all peers in the peerTable.
     *
     * @param msg {String} Message containing the discovery request from another peer.
     * @todo discovery message format
     */
    _processDiscoveryRequest: function(msg) {
        var peerIds = [],
            peer;
        for (peer in this._peerTable) {
            if (this._peerTable.hasOwnProperty(peer) && this._peerTable[peer] instanceof Peer) {
                peerIds.push(peer);
            }
        }
        this.route(msg.payload.from, {
            type: 'discovery',
            payload: {
                type: 'response',
                from: this.id.toString(),
                ids: peerIds
            }
        });
    }
};

if (typeof(module) !== 'undefined') {
    module.exports = Router;
}
<commit_msg>Removed debug output<commit_after>/** @fileOverview Routing functionality */

var Peer = require('./peer.js');
var BigInteger = require('./third_party/BigInteger.js');
var Sha1 = require('./third_party/sha1.js');
var Range = require('./chord/range.js');

/**
 * @constructor
 * @class The main routing class. Used by {@link ConnectionManager} instances to route
 * messages through the user network.
 *
 * @param id ID of the local peer
 * @param fallbackSignaling
 */
var Router = function(id, fallbackSignaling, connectionManager) {
    if (!(this instanceof Router)) {
        return new Router();
    }

    this._peerTable = {};
    this._fallbackSignaling = fallbackSignaling;
    this._fallbackSignaling.onmessage = this.onmessage.bind(this);
    this._connectionManager = connectionManager;
    this.id = id;
    this._messageCallbacks = {};
    this._monitorCallback = null;
    this._pendingPutRequests = {};
    this._pendingGetRequests = {};
    this.registerDeliveryCallback('discovery', this._onDiscoveryMessage.bind(this));

    return this;
};

Router.randomId = function() {
    var randomId = Sha1.bigIntHash(Math.random().toString());
    return randomId;
};

Router.prototype = {

    /**
     * Add a peer to the peer table.
     *
     * @param peer {Peer} The peer to add.
     * @todo add test for onclosedconnection behaviour
     */
    addPeer: function(peer) {
        this._peerTable[peer.id] = peer;
        peer.dataChannel.onmessage = this.onmessage.bind(this);
        peer.peerConnection.onclosedconnection = this.removePeer.bind(this, peer);
        if (Object.keys(this._peerTable).length === 1) {
            // ask first peer for its neighbours
            this._discoverNeighbours(peer);
        }
    },

    /**
     * Remove a peer from the peer table.
     *
     * @param peer {Peer} The peer to remove.
     * @todo disconnect peerConnection before removal
     */
    removePeer: function(peer) {
        delete this._peerTable[peer.id];
    },

    /**
     * Return a list of all peer ids currently in the routing table.
     *
     * @returns {Array}
     */
    getPeerIds: function() {
        var peers = [];
        var peer;
        for (peer in this._peerTable) {
            if (this._peerTable.hasOwnProperty(peer)) {
                peers.push(peer);
            }
        }
        return peers;
    },

    onmessage: function(msg) {
        try {
            this.forward(JSON.parse(msg.data));
        } catch (e) {
            console.log('Unable to parse incoming message ' + JSON.stringify(msg.data) + ': ' + e);
        }
    },

    /**
     * Encapsulates message in router format and forwards them.
     *
     * @param to recipient
     * @param type the message type
     * @param payload the message payload
     */
    route: function(to, payload) {
        this.forward({
            to: to.toString(),
            from: this.id.toString(),
            type: 'route',
            payload: payload
        });
    },

    /**
     * Forward message or deliver if recipient is me. This implementation
     * implies that we are using a fully meshed network where every peer
     * is connected to all other peers.
     *
     * @param msg {String} The message to route.
     */
    forward: function(msg) {
        if (typeof(this._monitorCallback) === 'function') {
            this._monitorCallback(msg);
        }
        if (!msg.to) {
            throw Error('Unable to route message because no recipient can be determined');
        }
        if (this.id.toString() === msg.to) {
            this.deliver(msg);
            return;
        }
        var receiver = this._peerTable[msg.to];
        if (!(receiver instanceof Peer)) {
            this._fallbackSignaling.send(JSON.stringify(msg));
            return;
        }
        try {
            receiver.dataChannel.send(JSON.stringify(msg));
        } catch (e) {
            console.log('Unable to route message to ' + msg.to + ' because the DataChannel connection failed.');
        }
    },

    get: function(hash, cb) {
        this._pendingGetRequests[hash.toString()] = cb;
        var peer = this.responsible(hash);
        this.forward({
            to: peer.toString(),
            from: this.id.toString(),
            type: 'get-request',
            payload: {
                hash: hash.toString()
            }
        });
    },

    _handleGetRequest: function(msg) {
        try {
            var val = JSON.parse(localStorage.getItem(msg.payload.hash));
            this.forward({
                to: msg.from,
                from: this.id.toString(),
                type: 'get-response',
                payload: {
                    hash: msg.payload.hash,
                    val: val
                }
            });
        } catch (e) {
            console.log(e);
        }
    },

    _handleGetResponse: function(msg) {
        if (typeof(this._pendingGetRequests[msg.payload.hash]) === 'function') {
            this._pendingGetRequests[msg.payload.hash.toString()](msg.payload.val);
            delete this._pendingGetRequests[msg.payload.hash.toString()];
        }
    },

    put: function(hash, val, cb) {
        this._pendingPutRequests[hash.toString()] = cb;
        var peer = this.responsible(hash);
        this.forward({
            to: peer.toString(),
            from: this.id.toString(),
            type: 'put-request',
            payload: {
                hash: hash.toString(),
                val: val
            }
        });
    },

    _handlePutRequest: function(msg) {
        try {
            localStorage.setItem(msg.payload.hash, JSON.stringify(msg.payload.val));
            this.forward({
                to: msg.from,
                from: this.id.toString(),
                type: 'put-response',
                payload: {
                    hash: msg.payload.hash
                }
            });
        } catch (e) {
            console.log(e);
        }
    },

    _handlePutResponse: function(msg) {
        if (typeof(this._pendingPutRequests[msg.payload.hash]) === 'function') {
            this._pendingPutRequests[msg.payload.hash](null);
        }
    },

    responsible: function(hash) {
        var candidate = this.id;
        for (var k in this._peerTable) {
            if (Range.inLeftClosedInterval(new BigInteger(k), hash, candidate)) {
                candidate = new BigInteger(k);
            }
        }
        return candidate;
    },

    /**
     * Deliver a message to this peer. Is called when the `to` field of
     * the message contains the id of this peer. Decides where to deliver
     * the message to by calling the registered callback using the `type`
     * field (e.g. webrtc connection/ neighbour discovery/ application) of
     * the message.
     *
     * @param msg {String}
     */
    deliver: function(msg) {
        switch (msg.type) {
            case 'route':
                try {
                    this._messageCallbacks[msg.payload.type](msg.payload);
                } catch (e) {
                    console.log(msg);
                    console.log('Unable to handle message of type ' + msg.payload.type + ' from ' + msg.payload.from + ' because no callback is registered: ' + e);
                }
                break;
            case 'get-request':
                this._handleGetRequest(msg);
                break;
            case 'get-response':
                this._handleGetResponse(msg);
                break;
            case 'put-request':
                this._handlePutRequest(msg);
                break;
            case 'put-response':
                this._handlePutResponse(msg);
                break;
            default:
                console.log('Discarding message', msg, 'because the type is unknown');
        }
    },

    /**
     * Register a delivery callback. The registered callback gets
     * called when a specific type of message arrives with the `from`
     * field set to this peers' id.
     *
     * @param msgType {String} refers to the `type`-field of the message
     * this callback should respond to
     * @param callback {Function} The callback to call when a message of
     * the given type arrives
     */
    registerDeliveryCallback: function(msgType, callback) {
        this._messageCallbacks[msgType] = callback;
    },

    /**
     * Register a monitor callback. The registered callback gets
     * called when a message arrives with the `from` field set to
     * this peers' id.
     *
     * @param callback {Function} The callback to call when a message is delivered
     */
    registerMonitorCallback: function(callback) {
        this._monitorCallback = callback;
    },

    /**
     * Kick off neighbour discovery mechanism by sending a `discovery-request' message to
     * a connected peer.
     *
     * @param peer {Peer}
     * @todo implement
     */
    _discoverNeighbours: function(peer) {
        this.route(peer.id, {
            type: 'discovery',
            payload: {
                type: 'request',
                from: this.id.toString()
            }
        });
    },

    _onDiscoveryMessage: function(msg) {
        switch (msg.payload.type) {
            case 'response':
                this._processDiscoveryResponse(msg);
                break;
            case 'request':
                this._processDiscoveryRequest(msg);
                break;
            default:
                console.log('Router: received invalid discovery message with type %s from %s', msg.payload.type, msg.payload.from);
                break;
        }
    },

    /**
     * Gets called when a neighbour discovery response message is received.
     *
     * @param msg {String} Message containing ids of another peers peer table.
     * @todo should this call the connection manager?
     */
    _processDiscoveryResponse: function(msg) {
        //console.log('connecting to', msg.payload.ids)
        var i, ids = msg.payload.ids;
        for (i = 0; i < ids.length; i++) {
            if (ids[i] !== this.id) {
                this._connectionManager.connect(ids[i]);
            }
        }
    },

    /**
     * Answer a received neighbour discovery message. Respond with
     * known ids of all peers in the peerTable.
     *
     * @param msg {String} Message containing the discovery request from another peer.
     * @todo discovery message format
     */
    _processDiscoveryRequest: function(msg) {
        var peerIds = [],
            peer;
        for (peer in this._peerTable) {
            if (this._peerTable.hasOwnProperty(peer) && this._peerTable[peer] instanceof Peer) {
                peerIds.push(peer);
            }
        }
        this.route(msg.payload.from, {
            type: 'discovery',
            payload: {
                type: 'response',
                from: this.id.toString(),
                ids: peerIds
            }
        });
    }
};

if (typeof(module) !== 'undefined') {
    module.exports = Router;
}
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
	errorHandler = require('./errors.server.controller'),
	Shoppinglist = mongoose.model('Shoppinglist'),
	_ = require('lodash');

/**
 * Create a Shoppinglist
 */
exports.create = function(req, res) {
	var shoppinglist = new Shoppinglist(req.body);

	shoppinglist.save(function(err) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(shoppinglist);
		}
	});
};

/**
 * Show the current Shoppinglist
 */
exports.read = function(req, res) {
	res.jsonp(req.shoppinglist);
};

/**
 * Update a Shoppinglist
 */
exports.update = function(req, res) {
	var shoppinglist = req.shoppinglist ;

	shoppinglist = _.extend(shoppinglist , req.body);

	shoppinglist.save(function(err) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(shoppinglist);
		}
	});
};

/**
 * Delete an Shoppinglist
 */
exports.delete = function(req, res) {
	var shoppinglist = req.shoppinglist ;

	shoppinglist.remove(function(err) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(shoppinglist);
		}
	});
};

/**
 * List of Shoppinglists
 */
exports.list = function(req, res) {
	Shoppinglist.find().sort('-created').populate('user', 'displayName').exec(function(err, shoppinglists) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(shoppinglists);
		}
	});
};

exports.getAllFromWg = function(req,res){
	var date = new Date(); // date a week ago
	var last = new Date(date.getTime() - (7 * 24 * 60 * 60 * 1000));

	Shoppinglist.where({wg_id: req.wg._id, $or: [{ done_when: { $gte: last }}, { done_when: null }]}).sort('-created').exec(function(err, todos) {
		if (err) {
			console.log('getallshoppingitems');
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(todos);
		}
	});
};

/**
 * Shoppinglist middleware
 */
exports.shoppinglistByID = function(req, res, next, id) {
	Shoppinglist.findById(id).populate('user', 'displayName').exec(function(err, shoppinglist) {
		if (err) return next(err);
		if (! shoppinglist) return next(new Error('Failed to load Shoppinglist ' + id));
		req.shoppinglist = shoppinglist ;
		next();
	});
};

/**
 * Shoppinglist authorization middleware
 */
exports.hasAuthorization = function(req, res, next) {
	if (req.shoppinglist.user.id !== req.user.id) {
		return res.status(403).send('User is not authorized');
	}
	next();
};
<commit_msg>style: shop-server-ctrl var naming<commit_after>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
	errorHandler = require('./errors.server.controller'),
	Shoppinglist = mongoose.model('Shoppinglist'),
	_ = require('lodash');

/**
 * Create a Shoppinglist
 */
exports.create = function(req, res) {
	var shoppinglist = new Shoppinglist(req.body);

	shoppinglist.save(function(err) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(shoppinglist);
		}
	});
};

/**
 * Show the current Shoppinglist
 */
exports.read = function(req, res) {
	res.jsonp(req.shoppinglist);
};

/**
 * Update a Shoppinglist
 */
exports.update = function(req, res) {
	var shoppinglist = req.shoppinglist ;

	shoppinglist = _.extend(shoppinglist , req.body);

	shoppinglist.save(function(err) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(shoppinglist);
		}
	});
};

/**
 * Delete an Shoppinglist
 */
exports.delete = function(req, res) {
	var shoppinglist = req.shoppinglist ;

	shoppinglist.remove(function(err) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(shoppinglist);
		}
	});
};

/**
 * List of Shoppinglists
 */
exports.list = function(req, res) {
	Shoppinglist.find().sort('-created').populate('user', 'displayName').exec(function(err, shoppinglists) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(shoppinglists);
		}
	});
};

exports.getAllFromWg = function(req,res){
	var date = new Date(); // date a week ago
	var last = new Date(date.getTime() - (7 * 24 * 60 * 60 * 1000));

	Shoppinglist.where({wg_id: req.wg._id, $or: [{ done_when: { $gte: last }}, { done_when: null }]}).sort('-created').exec(function(err, shop) {
		if (err) {
			console.log('getallshoppingitems');
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.jsonp(shop);
		}
	});
};

/**
 * Shoppinglist middleware
 */
exports.shoppinglistByID = function(req, res, next, id) {
	Shoppinglist.findById(id).populate('user', 'displayName').exec(function(err, shoppinglist) {
		if (err) return next(err);
		if (! shoppinglist) return next(new Error('Failed to load Shoppinglist ' + id));
		req.shoppinglist = shoppinglist ;
		next();
	});
};

/**
 * Shoppinglist authorization middleware
 */
exports.hasAuthorization = function(req, res, next) {
	if (req.shoppinglist.user.id !== req.user.id) {
		return res.status(403).send('User is not authorized');
	}
	next();
};
<|endoftext|>


<commit_before>var fs = require('fs')
  , path = require('path')
  , lconfig = require("lconfig")
  , spawn = require('child_process').spawn
  , datastore = require('./synclet/datastore')
  , async = require('async')
  , lutil = require('lutil')
  , EventEmitter = require('events').EventEmitter
  ;

var synclets = {
    available:[],
    installed:{}
};

exports.eventEmitter = new EventEmitter();

exports.synclets = function() {
  return synclets;
};

/**
* Scans the Me directory for instaled synclets
*/
exports.findInstalled = function (callback) {
    if (!path.existsSync(lconfig.me + "/synclets/")) fs.mkdirSync(lconfig.me + "/synclets/", 0755);
    var dirs = fs.readdirSync(lconfig.me + "/synclets/" );
    for (var i = 0; i < dirs.length; i++) {
        var dir =  lconfig.me + '/synclets/' + dirs[i];
        try {
            if(!fs.statSync(dir).isDirectory()) continue;
            if(!fs.statSync(dir+'/me.json').isFile()) continue;
            var js = JSON.parse(fs.readFileSync(dir+'/me.json', 'utf-8'));
            synclets.installed[js.id] = js;
            synclets.installed[js.id].status = "waiting";
            if (js.synclets) {
                for (var j = 0; j < js.synclets.length; j++) {
                    scheduleRun(js, js.synclets[i]);
                }
            }
        } catch (E) {
            console.log("Me/synclets/"+dirs[i]+" does not appear to be a synclet (" +E+ ")");
        }
    }
}

exports.scanDirectory = function(dir) {
    var files = fs.readdirSync(dir);
    for (var i = 0; i < files.length; i++) {
        var fullPath = dir + '/' + files[i];
        var stats = fs.statSync(fullPath);
        if(stats.isDirectory()) {
            exports.scanDirectory(fullPath);
            continue;
        }
        if (RegExp("\\.synclet$").test(fullPath)) {
            mapMetaData(fullPath);
        }
    }
}

/**
* Install a synclet
*/
exports.install = function(metaData) {
    var serviceInfo;
    synclets.available.some(function(svcInfo) {
        if (svcInfo.srcdir == metaData.srcdir) {
            serviceInfo = {};
            for(var a in svcInfo){serviceInfo[a]=svcInfo[a];}
            return true;
        }
        return false;
    });
    if (!serviceInfo) return serviceInfo;
    var authInfo;
    // local/internal name for the service on disk and whatnot, try to make it more friendly to devs/debugging
    if(serviceInfo.provider) {
        try {
            var apiKeys = JSON.parse(fs.readFileSync(lconfig.lockerDir + "/" + lconfig.me + "/apikeys.json", 'ascii'));
            authInfo = apiKeys[serviceInfo.provider];
        } catch (E) { console.dir(E); }
        var inc = 0;
        if (path.existsSync(path.join(lconfig.lockerDir, lconfig.me, 'synclets', serviceInfo.provider))) {
            inc++;
            while (path.existsSync(path.join(lconfig.lockerDir, lconfig.me, 'synclets', serviceInfo.provider + '-' + inc))) inc++;
            serviceInfo.id = serviceInfo.provider + "-" + inc;
        } else {
            serviceInfo.id = serviceInfo.provider;
        }
    } else {
        throw "invalid synclet, has no provider";
    }
    synclets.installed[serviceInfo.id] = serviceInfo;
    fs.mkdirSync(path.join(lconfig.lockerDir, lconfig.me, "synclets", serviceInfo.id),0755);
    if (authInfo) serviceInfo.auth = authInfo;
    fs.writeFileSync(path.join(lconfig.lockerDir, lconfig.me, "synclets", serviceInfo.id, 'me.json'),JSON.stringify(serviceInfo, null, 4));
    if (serviceInfo.synclets) {
        for (var i = 0; i < serviceInfo.synclets; i++) {
            scheduleRun(serviceInfo, serviceInfo.synclets[i]);
        }
    }
    return serviceInfo;
}

exports.isInstalled = function(serviceId) {
    return serviceId in synclets.installed;
}

exports.status = function(serviceId) {
    return synclets.installed[serviceId];
};

exports.syncNow = function(serviceId, callback) {
    if (!synclets.installed[serviceId]) return callback("no service like that installed");
    async.forEach(synclets.installed[serviceId].synclets, function(synclet, cb) { executeSynclet(synclets.installed[serviceId], synclet, cb); }, callback);
};

/**
* Add a timeout to run a synclet
*/
function scheduleRun(info, synclet) {
    info.nextRun = new Date() + parseInt(info.frequency);
    synclet.nextRun = new Date() + parseInt(synclet.frequency);
    setTimeout(function() {
        executeSynclet(info, synclet);
    }, parseInt(synclet.frequency) * 1000);
};

/**
* Executes a synclet
*/
function executeSynclet(info, synclet, callback) {
    if (synclet.status === 'running') {
        if (callback) {
            callback('already running');
        }
        return;
    }
    info.status = synclet.status = "running";
    if (!synclet.run) {
        run = ["node", lconfig.lockerDir + "/Common/node/synclet/client.js"];
    } else {
        run = synclet.run.split(" "); // node foo.js
    }

    process.env["NODE_PATH"] = lconfig.lockerDir+'/synclets/'+info.provider+'/';
    var dataResponse = '';

    app = spawn(run.shift(), run, {cwd: lconfig.lockerDir + '/' + lconfig.me + '/synclets/' + info.id, env:process.env});

    app.stderr.on('data', function (data) {
        var mod = console.outputModule;
        console.outputModule = info.title;
        console.error(data.toString());
        console.outputModule = mod;
    });

    app.stdout.on('data',function (data) {
        dataResponse += data;
    });

    app.on('exit', function (code,signal) {
        var response;
        try {
            response = JSON.parse(dataResponse);
        } catch (E) {
            console.error(E);
            console.error(dataResponse);
            info.status = synclet.status = 'failed : ' + E;
            if (callback) callback(E);
            return;
        }
        info.status = synclet.status = 'processing data';
        tempInfo = JSON.parse(fs.readFileSync(lconfig.lockerDir + "/" + lconfig.me + "/synclets/" + info.id + '/me.json'));
        var deleteIDs = compareIDs(info.config, response.config);
        processResponse(deleteIDs, info, synclet, response, callback);
        info.config = lutil.extend(tempInfo.config, response.config);
        fs.writeFileSync(lconfig.lockerDir + "/" + lconfig.me + "/synclets/" + info.id + '/me.json', JSON.stringify(info, null, 4));
        scheduleRun(info, synclet);
    });
    if (!info.config) info.config = {};

    info.syncletToRun = synclet;
    app.stdin.write(JSON.stringify(info)+"\n"); // Send them the process information
    delete info.syncletToRun;
};

function compareIDs (originalConfig, newConfig) {
    var resp = {};
    if (originalConfig.ids && newConfig.ids) {
        for (var i in newConfig.ids) {
            if (!originalConfig.ids[i]) break;
            var newSet = newConfig.ids[i];
            var oldSet = originalConfig.ids[i];
            seenIDs = {};
            resp[i] = [];
            for (var j = 0; j < newSet.length; j++) seenIDs[newSet[j]] = true;
            for (var j = 0; j < oldSet.length; j++) {
                if (!seenIDs[oldSet[j]]) resp[i].push(oldSet[j]);
            }
        }
    }
    return resp;
}
function processResponse(deleteIDs, info, synclet, response, callback) {
    datastore.init(function() {
        info.status = synclet.status = 'waiting';

        if (!callback) {
            callback = function() {};
        }
        var dataKeys = [];
        for (var i in response.data) {
            dataKeys.push(i);
        }
        for (var i in deleteIDs) {
            if (!dataKeys[i]) dataKeys.push(i);
        }
        async.forEach(dataKeys, function(key, cb) { processData(deleteIDs[key], info, key, response.data[key], cb); }, callback);
    });
};

function processData (deleteIDs, info, key, data, callback) {
    // console.error(deleteIDs);
    var collection = info.id + "_" + key;
    var eventType = key + "/" + info.provider;

    if (key.indexOf('/') !== -1) {
        collection = info.id + "_" + key.substring(key.indexOf('/') + 1);
        eventType = key.substring(0, key.indexOf('/')) + "/" + info.provider;
        key = key.substring(key.indexOf('/') + 1);
    }

    if (info.mongoId) {
        datastore.addCollection(key, info.id, info.mongoId);
    } else {
        datastore.addCollection(key, info.id, "id");
    }

    if (deleteIDs && deleteIDs.length > 0 && data) {
        addData(collection, data, info, eventType, function() {
            deleteData(collection, deleteIDs, info, eventType, callback);
        });
    } else if (data) {
        addData(collection, data, info, eventType, callback);
    } else if (deleteIDs && deleteIDs.length > 0) {
        deleteData(collection, deleteIDs, info, eventType, callback);
    } else {
        callback();
    }
}

function deleteData (collection, deleteIds, info, eventType, callback) {
    async.forEach(deleteIds, function(id, cb) {
        var newEvent = {obj : {source : eventType, type: 'delete', data : {}}};
        newEvent.obj.data[info.mongoId] = id;
        newEvent.fromService = "synclet/" + info.id;
        exports.eventEmitter.emit(eventType, newEvent);
        datastore.removeObject(collection, id, {timeStampe: Date.now()}, cb);
    }, callback);
}

function addData (collection, data, info, eventType, callback) {
    async.forEach(data, function(object, cb) {
        var newEvent = {obj : {source : collection, type: object.type, data: object.obj}};
        newEvent.fromService = "synclet/" + info.id;
        if (object.type === 'delete') {
            datastore.removeObject(collection, object.obj[info.mongoId], {timeStamp: object.timestamp}, cb);
            exports.eventEmitter.emit(eventType, newEvent);
        } else {
            datastore.addObject(collection, object.obj, {timeStamp: object.timestamp}, function(err, type) {
                if (type === 'same') return cb();
                newEvent.obj.type = type;
                exports.eventEmitter.emit(eventType, newEvent);
                cb();
            });
        }
    }, callback);
}
/**
* Map a meta data file JSON with a few more fields and make it available
*/
function mapMetaData(file) {
    var metaData = JSON.parse(fs.readFileSync(file, 'utf-8'));
    metaData.srcdir = path.dirname(file);
    synclets.available.push(metaData);
    return metaData;
}
<commit_msg>haha, infmamous i j downstairs mixup, love it^2<commit_after>var fs = require('fs')
  , path = require('path')
  , lconfig = require("lconfig")
  , spawn = require('child_process').spawn
  , datastore = require('./synclet/datastore')
  , async = require('async')
  , lutil = require('lutil')
  , EventEmitter = require('events').EventEmitter
  ;

var synclets = {
    available:[],
    installed:{}
};

exports.eventEmitter = new EventEmitter();

exports.synclets = function() {
  return synclets;
};

/**
* Scans the Me directory for instaled synclets
*/
exports.findInstalled = function (callback) {
    if (!path.existsSync(lconfig.me + "/synclets/")) fs.mkdirSync(lconfig.me + "/synclets/", 0755);
    var dirs = fs.readdirSync(lconfig.me + "/synclets/" );
    for (var i = 0; i < dirs.length; i++) {
        var dir =  lconfig.me + '/synclets/' + dirs[i];
        try {
            if(!fs.statSync(dir).isDirectory()) continue;
            if(!fs.statSync(dir+'/me.json').isFile()) continue;
            var js = JSON.parse(fs.readFileSync(dir+'/me.json', 'utf-8'));
            synclets.installed[js.id] = js;
            synclets.installed[js.id].status = "waiting";
            if (js.synclets) {
                for (var j = 0; j < js.synclets.length; j++) {
                    scheduleRun(js, js.synclets[j]);
                }
            }
        } catch (E) {
            console.log("Me/synclets/"+dirs[i]+" does not appear to be a synclet (" +E+ ")");
        }
    }
}

exports.scanDirectory = function(dir) {
    var files = fs.readdirSync(dir);
    for (var i = 0; i < files.length; i++) {
        var fullPath = dir + '/' + files[i];
        var stats = fs.statSync(fullPath);
        if(stats.isDirectory()) {
            exports.scanDirectory(fullPath);
            continue;
        }
        if (RegExp("\\.synclet$").test(fullPath)) {
            mapMetaData(fullPath);
        }
    }
}

/**
* Install a synclet
*/
exports.install = function(metaData) {
    var serviceInfo;
    synclets.available.some(function(svcInfo) {
        if (svcInfo.srcdir == metaData.srcdir) {
            serviceInfo = {};
            for(var a in svcInfo){serviceInfo[a]=svcInfo[a];}
            return true;
        }
        return false;
    });
    if (!serviceInfo) return serviceInfo;
    var authInfo;
    // local/internal name for the service on disk and whatnot, try to make it more friendly to devs/debugging
    if(serviceInfo.provider) {
        try {
            var apiKeys = JSON.parse(fs.readFileSync(lconfig.lockerDir + "/" + lconfig.me + "/apikeys.json", 'ascii'));
            authInfo = apiKeys[serviceInfo.provider];
        } catch (E) { console.dir(E); }
        var inc = 0;
        if (path.existsSync(path.join(lconfig.lockerDir, lconfig.me, 'synclets', serviceInfo.provider))) {
            inc++;
            while (path.existsSync(path.join(lconfig.lockerDir, lconfig.me, 'synclets', serviceInfo.provider + '-' + inc))) inc++;
            serviceInfo.id = serviceInfo.provider + "-" + inc;
        } else {
            serviceInfo.id = serviceInfo.provider;
        }
    } else {
        throw "invalid synclet, has no provider";
    }
    synclets.installed[serviceInfo.id] = serviceInfo;
    fs.mkdirSync(path.join(lconfig.lockerDir, lconfig.me, "synclets", serviceInfo.id),0755);
    if (authInfo) serviceInfo.auth = authInfo;
    fs.writeFileSync(path.join(lconfig.lockerDir, lconfig.me, "synclets", serviceInfo.id, 'me.json'),JSON.stringify(serviceInfo, null, 4));
    if (serviceInfo.synclets) {
        for (var i = 0; i < serviceInfo.synclets; i++) {
            scheduleRun(serviceInfo, serviceInfo.synclets[i]);
        }
    }
    return serviceInfo;
}

exports.isInstalled = function(serviceId) {
    return serviceId in synclets.installed;
}

exports.status = function(serviceId) {
    return synclets.installed[serviceId];
};

exports.syncNow = function(serviceId, callback) {
    if (!synclets.installed[serviceId]) return callback("no service like that installed");
    async.forEach(synclets.installed[serviceId].synclets, function(synclet, cb) { executeSynclet(synclets.installed[serviceId], synclet, cb); }, callback);
};

/**
* Add a timeout to run a synclet
*/
function scheduleRun(info, synclet) {
    info.nextRun = new Date() + parseInt(info.frequency);
    synclet.nextRun = new Date() + parseInt(synclet.frequency);
    setTimeout(function() {
        executeSynclet(info, synclet);
    }, parseInt(synclet.frequency) * 1000);
};

/**
* Executes a synclet
*/
function executeSynclet(info, synclet, callback) {
    if (synclet.status === 'running') {
        if (callback) {
            callback('already running');
        }
        return;
    }
    info.status = synclet.status = "running";
    if (!synclet.run) {
        run = ["node", lconfig.lockerDir + "/Common/node/synclet/client.js"];
    } else {
        run = synclet.run.split(" "); // node foo.js
    }

    process.env["NODE_PATH"] = lconfig.lockerDir+'/synclets/'+info.provider+'/';
    var dataResponse = '';

    app = spawn(run.shift(), run, {cwd: lconfig.lockerDir + '/' + lconfig.me + '/synclets/' + info.id, env:process.env});

    app.stderr.on('data', function (data) {
        var mod = console.outputModule;
        console.outputModule = info.title;
        console.error(data.toString());
        console.outputModule = mod;
    });

    app.stdout.on('data',function (data) {
        dataResponse += data;
    });

    app.on('exit', function (code,signal) {
        var response;
        try {
            response = JSON.parse(dataResponse);
        } catch (E) {
            console.error(E);
            console.error(dataResponse);
            info.status = synclet.status = 'failed : ' + E;
            if (callback) callback(E);
            return;
        }
        info.status = synclet.status = 'processing data';
        tempInfo = JSON.parse(fs.readFileSync(lconfig.lockerDir + "/" + lconfig.me + "/synclets/" + info.id + '/me.json'));
        var deleteIDs = compareIDs(info.config, response.config);
        processResponse(deleteIDs, info, synclet, response, callback);
        info.config = lutil.extend(tempInfo.config, response.config);
        fs.writeFileSync(lconfig.lockerDir + "/" + lconfig.me + "/synclets/" + info.id + '/me.json', JSON.stringify(info, null, 4));
        scheduleRun(info, synclet);
    });
    if (!info.config) info.config = {};

    info.syncletToRun = synclet;
    app.stdin.write(JSON.stringify(info)+"\n"); // Send them the process information
    delete info.syncletToRun;
};

function compareIDs (originalConfig, newConfig) {
    var resp = {};
    if (originalConfig.ids && newConfig.ids) {
        for (var i in newConfig.ids) {
            if (!originalConfig.ids[i]) break;
            var newSet = newConfig.ids[i];
            var oldSet = originalConfig.ids[i];
            seenIDs = {};
            resp[i] = [];
            for (var j = 0; j < newSet.length; j++) seenIDs[newSet[j]] = true;
            for (var j = 0; j < oldSet.length; j++) {
                if (!seenIDs[oldSet[j]]) resp[i].push(oldSet[j]);
            }
        }
    }
    return resp;
}
function processResponse(deleteIDs, info, synclet, response, callback) {
    datastore.init(function() {
        info.status = synclet.status = 'waiting';

        if (!callback) {
            callback = function() {};
        }
        var dataKeys = [];
        for (var i in response.data) {
            dataKeys.push(i);
        }
        for (var i in deleteIDs) {
            if (!dataKeys[i]) dataKeys.push(i);
        }
        async.forEach(dataKeys, function(key, cb) { processData(deleteIDs[key], info, key, response.data[key], cb); }, callback);
    });
};

function processData (deleteIDs, info, key, data, callback) {
    // console.error(deleteIDs);
    var collection = info.id + "_" + key;
    var eventType = key + "/" + info.provider;

    if (key.indexOf('/') !== -1) {
        collection = info.id + "_" + key.substring(key.indexOf('/') + 1);
        eventType = key.substring(0, key.indexOf('/')) + "/" + info.provider;
        key = key.substring(key.indexOf('/') + 1);
    }

    if (info.mongoId) {
        datastore.addCollection(key, info.id, info.mongoId);
    } else {
        datastore.addCollection(key, info.id, "id");
    }

    if (deleteIDs && deleteIDs.length > 0 && data) {
        addData(collection, data, info, eventType, function() {
            deleteData(collection, deleteIDs, info, eventType, callback);
        });
    } else if (data) {
        addData(collection, data, info, eventType, callback);
    } else if (deleteIDs && deleteIDs.length > 0) {
        deleteData(collection, deleteIDs, info, eventType, callback);
    } else {
        callback();
    }
}

function deleteData (collection, deleteIds, info, eventType, callback) {
    async.forEach(deleteIds, function(id, cb) {
        var newEvent = {obj : {source : eventType, type: 'delete', data : {}}};
        newEvent.obj.data[info.mongoId] = id;
        newEvent.fromService = "synclet/" + info.id;
        exports.eventEmitter.emit(eventType, newEvent);
        datastore.removeObject(collection, id, {timeStampe: Date.now()}, cb);
    }, callback);
}

function addData (collection, data, info, eventType, callback) {
    async.forEach(data, function(object, cb) {
        var newEvent = {obj : {source : collection, type: object.type, data: object.obj}};
        newEvent.fromService = "synclet/" + info.id;
        if (object.type === 'delete') {
            datastore.removeObject(collection, object.obj[info.mongoId], {timeStamp: object.timestamp}, cb);
            exports.eventEmitter.emit(eventType, newEvent);
        } else {
            datastore.addObject(collection, object.obj, {timeStamp: object.timestamp}, function(err, type) {
                if (type === 'same') return cb();
                newEvent.obj.type = type;
                exports.eventEmitter.emit(eventType, newEvent);
                cb();
            });
        }
    }, callback);
}
/**
* Map a meta data file JSON with a few more fields and make it available
*/
function mapMetaData(file) {
    var metaData = JSON.parse(fs.readFileSync(file, 'utf-8'));
    metaData.srcdir = path.dirname(file);
    synclets.available.push(metaData);
    return metaData;
}
<|endoftext|>


<commit_before>import { CONSUMER_KEY, REDIRECT_URI } from 'react-native-dotenv'

import * as PocketAPI from './PocketAPI'

let memAccessToken = null

export function hasAccessToken() {
  return !!memAccessToken
}

export function setAccessToken(at) {
  memAccessToken = at
  console.tron.display({ name: 'AccessToken has set.', value: at })
}

export async function getAllUntaggedItems() {
  const params = {
    state: 'unread',
    tag: '_untagged_',
    count: 20,
    sort: 'newest',
    detailType: 'simple',
  }
  return await PocketAPI.get(CONSUMER_KEY, memAccessToken, params)
}

export async function getItemsTaggedBy(tagNm) {
  const params = {
    state: 'unread',
    tag: tagNm,
    count: 100,
    sort: 'newest',
    detailType: 'simple',
  }
  return await PocketAPI.get(CONSUMER_KEY, memAccessToken, params)
}

export async function getAllTags() {
  if (!memAccessToken) { return }
  console.tron.info('Adapter#getAllTags - start')
  const params = {
    state: 'unread',
    count: 100,
    sort: 'newest',
    detailType: 'complete',
  }
  const result = await PocketAPI.get(CONSUMER_KEY, memAccessToken, params)
  const listFromPocket = result.list
  let tags = {}
  Object.keys(listFromPocket).forEach((key) => {
    const m = listFromPocket[key]
    if (m.tags) {
      Object.keys(m.tags).forEach((tagKey) => {
        tagObj = m.tags[tagKey]
        let theTag = tags[tagKey] || { name: tagObj.tag, items: []}
        theTag.items.push(tagObj.item_id)
        if (!tagKey.match(/^chase:(a|b|c)/)) {
          tags[tagKey] = theTag
        }
      })
    }
  })
  console.tron.info('Adapter#getAllTags - end', tags)
  return tags
}
<commit_msg>:+1: 100<commit_after>import { CONSUMER_KEY, REDIRECT_URI } from 'react-native-dotenv'

import * as PocketAPI from './PocketAPI'

let memAccessToken = null

export function hasAccessToken() {
  return !!memAccessToken
}

export function setAccessToken(at) {
  memAccessToken = at
  console.tron.display({ name: 'AccessToken has set.', value: at })
}

export async function getAllUntaggedItems() {
  const params = {
    state: 'unread',
    tag: '_untagged_',
    count: 100,
    sort: 'newest',
    detailType: 'simple',
  }
  return await PocketAPI.get(CONSUMER_KEY, memAccessToken, params)
}

export async function getItemsTaggedBy(tagNm) {
  const params = {
    state: 'unread',
    tag: tagNm,
    count: 100,
    sort: 'newest',
    detailType: 'simple',
  }
  return await PocketAPI.get(CONSUMER_KEY, memAccessToken, params)
}

export async function getAllTags() {
  if (!memAccessToken) { return }
  console.tron.info('Adapter#getAllTags - start')
  const params = {
    state: 'unread',
    count: 100,
    sort: 'newest',
    detailType: 'complete',
  }
  const result = await PocketAPI.get(CONSUMER_KEY, memAccessToken, params)
  const listFromPocket = result.list
  let tags = {}
  Object.keys(listFromPocket).forEach((key) => {
    const m = listFromPocket[key]
    if (m.tags) {
      Object.keys(m.tags).forEach((tagKey) => {
        tagObj = m.tags[tagKey]
        let theTag = tags[tagKey] || { name: tagObj.tag, items: []}
        theTag.items.push(tagObj.item_id)
        if (!tagKey.match(/^chase:(a|b|c)/)) {
          tags[tagKey] = theTag
        }
      })
    }
  })
  console.tron.info('Adapter#getAllTags - end', tags)
  return tags
}
<|endoftext|>


<commit_before>maria.SetView.subclass(checkit, 'TodosAppView', {
    uiActions: {
        'keyup #new-todo'       : 'onKeyupNewTodo'       ,
        'click #toggle-all'     : 'onClickToggleAll'     ,
        'click #clear-completed': 'onClickClearCompleted'
    },
    properties: {
        buildData: function() {
            var model = this.getModel();

            var checkbox = this.find('#toggle-all');
            checkbox.checked = model.isAllCompleted();
            checkbox.disabled = model.isEmpty();

            var todoList = this.find('#todo-list');
            model.getPossibleModes().forEach(function(mode) {
                aristocrat.removeClass(todoList, mode);
            });
            aristocrat.addClass(todoList, model.getMode());

            var incompletedLength = model.getIncompleted().length;
            this.find('#todo-count').innerHTML =
                '' + incompletedLength + ' ' +
                ((incompletedLength === 1) ? 'item' : 'items') +
                ' left';

            var selected = this.find('.selected');
            if (selected) {
                aristocrat.removeClass(selected, 'selected');
            }
            aristocrat.addClass(this.find('.' + model.getMode() + '-filter'), 'selected');

            var completedLength = model.getCompleted().length;
            var clearButton = this.find('#clear-completed');
            clearButton.style.display = (completedLength > 0) ? '' : 'none';
            clearButton.innerHTML = 'Clear completed (' + completedLength + ')';
        },
        update: function(evt) {
            maria.SetView.prototype.update.call(this, evt);
            this.buildData();
        },
        getContainerEl: function() {
            // child views will be appended to this element
            return this.find('#todo-list');
        },
        createChildView: function(todoModel) {
            return new checkit.TodoView(todoModel);
        },
        getInputValue: function() {
            return this.find('#new-todo').value;
        },
        clearInput: function() {
            this.find('#new-todo').value = '';
        }
    }
});
<commit_msg>confusion around necessary tag was resolved in favor of using <commit_after>maria.SetView.subclass(checkit, 'TodosAppView', {
    uiActions: {
        'keyup #new-todo'       : 'onKeyupNewTodo'       ,
        'click #toggle-all'     : 'onClickToggleAll'     ,
        'click #clear-completed': 'onClickClearCompleted'
    },
    properties: {
        buildData: function() {
            var model = this.getModel();

            var checkbox = this.find('#toggle-all');
            checkbox.checked = model.isAllCompleted();
            checkbox.disabled = model.isEmpty();

            var todoList = this.find('#todo-list');
            model.getPossibleModes().forEach(function(mode) {
                aristocrat.removeClass(todoList, mode);
            });
            aristocrat.addClass(todoList, model.getMode());

            var incompletedLength = model.getIncompleted().length;
            this.find('#todo-count').innerHTML =
                '' + incompletedLength + ' ' +
                ((incompletedLength === 1) ? 'item' : 'items') +
                ' left';

            var selected = this.find('.selected');
            if (selected) {
                aristocrat.removeClass(selected, 'selected');
            }
            aristocrat.addClass(this.find('.' + model.getMode() + '-filter'), 'selected');

            var completedLength = model.getCompleted().length;
            var clearButton = this.find('#clear-completed');
            clearButton.style.display = (completedLength > 0) ? '' : 'none';
            clearButton.innerHTML = 'Clear completed (' + completedLength + ')';
        },
        update: function(evt) {
            maria.SetView.prototype.update.call(this, evt);
            this.buildData();
        },
        getContainerEl: function() {
            // child views will be appended to this element
            return this.find('#todo-list');
        },
        createChildView: function(todoModel) {
            return new checkit.TodoView(todoModel);
        },
        getInputValue: function() {
            return this.find('#new-todo').value;
        },
        clearInput: function() {
            this.find('#new-todo').value = '';
        }
    }
});
<|endoftext|>


<commit_before>import deepstream from 'deepstream.io-client-js';
import merge from 'lodash.merge';

export const statuses = deepstream.CONSTANTS.CONNECTION_STATE;

/**
 * Add entry to a DS List (avoiding duplicates).
 * !! Always use this instead of list.addEntry !!
 * @param {Object} list Deepstream List handle
 * @param {String} str  Entry to add
 */
export function addEntry(list, str) {
  if (list.getEntries().indexOf(str) > -1) return undefined;
  return list.addEntry(str);
}

function getRecordP(name) {
  return new Promise(resolve => this.record.getRecord(name).whenReady(r => resolve(r)));
}

function getListP(name) {
  return new Promise(resolve => this.record.getList(name).whenReady(r => resolve(r)));
}

function getExistingP(type, pathStr) {
  return new Promise((resolve, reject) =>
    this.record.has(pathStr, (error, hasRecord) => {
      if (error) reject(new Error(error));
      if (hasRecord) this.record[`get${type}`](pathStr).whenReady(r => resolve(r));
      else reject(new Error(`${type} does not exist: ${pathStr}`));
    }),
  );
}

function removeFromListP(listPath, id) {
  return this.record.getExistingListP(listPath).then(l => {
    l.removeEntry(id);
    return l;
  });
}

function snapshotP(name) {
  return new Promise((resolve, reject) =>
    this.record.snapshot(name, (error, data) => {
      if (error) reject(new Error(error));
      else resolve(data);
    }),
  );
}

function setListedRecordP(listPath, recordId, obj, deepMerge, overwrite, fullPathList = true) {
  const id = recordId || this.getUid();
  const rPath = `${listPath}/${id}`;
  return Promise.all([
    this.record.getRecordP(rPath),
    this.record.getListP(listPath),
  ]).then(([r, l]) => {
    // Update list:
    if (fullPathList) addEntry(l, rPath);
    else addEntry(l, id);
    // Update record:
    let created = false;
    const record = r.get();
    const newRecord = { ...obj, id: recordId };
    if (Object.keys(record).length === 0) {
      r.set(newRecord);
      created = true;
    } else if (deepMerge) {
      r.set(merge(record, obj));
    } else if (overwrite) {
      r.set(newRecord);
    } else {
      Object.keys(newRecord).forEach(key => r.set(key, newRecord[key]));
    }
    return [id, created];
  });
}

function setExistingRecordP(name, obj, deepMerge, overwrite) {
  return this.record.getExistingRecordP(name).then(r => {
    if (deepMerge) {
      const record = r.get();
      r.set(merge(record, obj));
    } else if (overwrite) {
      r.set(obj);
    } else {
      Object.keys(obj).forEach(key => r.set(key, obj[key]));
    }
    return r;
  });
}

function loginP(authParams) {
  return new Promise((resolve, reject) =>
    this.login(authParams, (success, data) => {
      if (success) resolve(data);
      else reject(new Error(data));
    }),
  );
}

function hasP(name) {
  return new Promise((resolve, reject) =>
    this.record.has(name, (error, hasRecord) => {
      if (error) reject(new Error(error));
      else resolve(hasRecord);
    }),
  );
}

function makeP(name, data) {
  return new Promise((resolve, reject) =>
    this.rpc.make(name, data, (error, result) => {
      if (error) reject(new Error(error));
      else resolve(result);
    }),
  );
}

function polyfill(obj, key, value) {
  if (typeof obj[key] === 'undefined') {
    // eslint-disable-next-line
    obj[key] = value;
  }
}

export default function getClient(url, options) {
  const c = deepstream(url, options);
  polyfill(c.record, 'getRecordP', getRecordP.bind(c));
  polyfill(c.record, 'getListP', getListP.bind(c));
  polyfill(c.record, 'getExistingRecordP', getExistingP.bind(c, 'Record'));
  polyfill(c.record, 'getExistingListP', getExistingP.bind(c, 'List'));
  polyfill(c.record, 'setExistingRecordP', setExistingRecordP.bind(c));
  polyfill(c.record, 'snapshotP', snapshotP.bind(c));
  polyfill(c.record, 'hasP', hasP.bind(c));
  polyfill(c.record, 'removeFromListP', removeFromListP.bind(c));
  polyfill(c.record, 'setListedRecordP', setListedRecordP.bind(c));
  polyfill(c, 'loginP', loginP.bind(c));
  polyfill(c.rpc, 'makeP', makeP.bind(c));

  // Depricated methods
  polyfill(c.record, 'getListedRecordP', () => {
    throw new Error('use setListedRecordP instead of getListedRecordP');
  });
  polyfill(c.record, 'listedRecordP', () => {
    throw new Error('use setListedRecordP instead of listedRecordP');
  });
  return c;
}

function withTenant(func, name, ...args) {
  return this.record[func](`${this.getTenant()}/${name}`, ...args);
}

export function getClientWithTenant(url, options, tenant = 'demo') {
  const c = getClient(url, options);
  c.getTenant = function () {
    return this;
  }.bind(tenant); // non-closure getter
  polyfill(c.record, 'getRecordPT', withTenant.bind(c, 'getRecordP'));
  polyfill(c.record, 'getRecordT', withTenant.bind(c, 'getRecord'));
  polyfill(c.record, 'getListPT', withTenant.bind(c, 'getListP'));
  polyfill(c.record, 'getListT', withTenant.bind(c, 'getList'));
  polyfill(c.record, 'snapshotPT', withTenant.bind(c, 'snapshotP'));
  polyfill(c.record, 'snapshotT', withTenant.bind(c, 'snapshot'));
  polyfill(c.record, 'hasPT', withTenant.bind(c, 'hasP'));
  polyfill(c.record, 'hasT', withTenant.bind(c, 'has'));
  polyfill(c.record, 'removeFromListPT', withTenant.bind(c, 'removeFromListP'));
  polyfill(
    c.record,
    'removeFromListPTT',
    function (name, id) {
      return this.record.removeFromListP(
        `${this.getTenant()}/${name}`,
        `${this.getTenant()}/${id}`,
      );
    }.bind(c),
  );
  polyfill(c.record, 'getExistingRecordPT', withTenant.bind(c, 'getExistingRecordP'));
  polyfill(c.record, 'getExistingListPT', withTenant.bind(c, 'getExistingListP'));
  polyfill(c.record, 'setListedRecordPT', withTenant.bind(c, 'setListedRecordP'));
  polyfill(c.record, 'setExistingRecordPT', withTenant.bind(c, 'setExistingRecordP'));
  polyfill(
    c.rpc,
    'makePT',
    function (name, data) {
      return this.rpc.makeP(`${this.getTenant()}/${name}`, data);
    }.bind(c),
  );

  // Depricated methods
  polyfill(c.record, 'getListedRecordPT', () => {
    throw new Error('use setListedRecordPT instead of getListedRecordPT');
  });
  polyfill(c.record, 'listedRecordPT', () => {
    throw new Error('use setListedRecordPT instead of listedRecordPT');
  });
  return c;
}

// ------------------------------------------------------------
//  FOR SINGLETON USE
export const ds = { client: undefined };
<commit_msg>new addToListP, getTenant as polyfill, and exporting direct deepstream stuff<commit_after>import deepstream from 'deepstream.io-client-js';
import merge from 'lodash.merge';

export const CONSTANTS = deepstream.CONSTANTS;
export const MERGE_STRATEGIES = deepstream.MERGE_STRATEGIES;
export const statuses = deepstream.CONSTANTS.CONNECTION_STATE; // Depricated

/**
 * Add entry to a DS List (avoiding duplicates).
 * !! Always use this instead of list.addEntry !!
 * @param {Object} list Deepstream List handle
 * @param {String} str  Entry to add
 */
export function addEntry(list, str) {
  if (list.getEntries().indexOf(str) > -1) return undefined;
  return list.addEntry(str);
}

function getRecordP(name) {
  return new Promise(resolve => this.record.getRecord(name).whenReady(r => resolve(r)));
}

function getListP(name) {
  return new Promise(resolve => this.record.getList(name).whenReady(r => resolve(r)));
}

function getExistingP(type, pathStr) {
  return new Promise((resolve, reject) =>
    this.record.has(pathStr, (error, hasRecord) => {
      if (error) reject(new Error(error));
      if (hasRecord) this.record[`get${type}`](pathStr).whenReady(r => resolve(r));
      else reject(new Error(`${type} does not exist: ${pathStr}`));
    }),
  );
}

function removeFromListP(listPath, id) {
  return this.record.getExistingListP(listPath).then(l => {
    l.removeEntry(id);
    return l;
  });
}

function addToListP(listPath, id) {
  return this.record.getExistingListP(listPath).then(l => {
    addEntry(l, id);
    return l;
  });
}

function snapshotP(name) {
  return new Promise((resolve, reject) =>
    this.record.snapshot(name, (error, data) => {
      if (error) reject(new Error(error));
      else resolve(data);
    }),
  );
}

function setListedRecordP(listPath, recordId, obj, deepMerge, overwrite, fullPathList = true) {
  const id = recordId || this.getUid();
  const rPath = `${listPath}/${id}`;
  return Promise.all([
    this.record.getRecordP(rPath),
    this.record.getListP(listPath),
  ]).then(([r, l]) => {
    // Update list:
    if (fullPathList) addEntry(l, rPath);
    else addEntry(l, id);
    // Update record:
    let created = false;
    const record = r.get();
    const newRecord = { ...obj, id: recordId };
    if (Object.keys(record).length === 0) {
      r.set(newRecord);
      created = true;
    } else if (deepMerge) {
      r.set(merge(record, obj));
    } else if (overwrite) {
      r.set(newRecord);
    } else {
      Object.keys(newRecord).forEach(key => r.set(key, newRecord[key]));
    }
    return [id, created];
  });
}

function setExistingRecordP(name, obj, deepMerge, overwrite) {
  return this.record.getExistingRecordP(name).then(r => {
    if (deepMerge) {
      const record = r.get();
      r.set(merge(record, obj));
    } else if (overwrite) {
      r.set(obj);
    } else {
      Object.keys(obj).forEach(key => r.set(key, obj[key]));
    }
    return r;
  });
}

function loginP(authParams) {
  return new Promise((resolve, reject) =>
    this.login(authParams, (success, data) => {
      if (success) resolve(data);
      else reject(new Error(data));
    }),
  );
}

function hasP(name) {
  return new Promise((resolve, reject) =>
    this.record.has(name, (error, hasRecord) => {
      if (error) reject(new Error(error));
      else resolve(hasRecord);
    }),
  );
}

function makeP(name, data) {
  return new Promise((resolve, reject) =>
    this.rpc.make(name, data, (error, result) => {
      if (error) reject(new Error(error));
      else resolve(result);
    }),
  );
}

export function polyfill(obj, key, value) {
  if (typeof obj[key] === 'undefined') {
    // eslint-disable-next-line
    obj[key] = value;
  }
}

export default function getClient(url, options) {
  const c = deepstream(url, options);
  polyfill(c.record, 'getRecordP', getRecordP.bind(c));
  polyfill(c.record, 'getListP', getListP.bind(c));
  polyfill(c.record, 'getExistingRecordP', getExistingP.bind(c, 'Record'));
  polyfill(c.record, 'getExistingListP', getExistingP.bind(c, 'List'));
  polyfill(c.record, 'setExistingRecordP', setExistingRecordP.bind(c));
  polyfill(c.record, 'snapshotP', snapshotP.bind(c));
  polyfill(c.record, 'hasP', hasP.bind(c));
  polyfill(c.record, 'addToListP', addToListP.bind(c));
  polyfill(c.record, 'removeFromListP', removeFromListP.bind(c));
  polyfill(c.record, 'setListedRecordP', setListedRecordP.bind(c));
  polyfill(c, 'loginP', loginP.bind(c));
  polyfill(c.rpc, 'makeP', makeP.bind(c));

  // Depricated methods
  polyfill(c.record, 'getListedRecordP', () => {
    throw new Error('use setListedRecordP instead of getListedRecordP');
  });
  polyfill(c.record, 'listedRecordP', () => {
    throw new Error('use setListedRecordP instead of listedRecordP');
  });
  return c;
}

function withTenant(func, name, ...args) {
  return this.record[func](`${this.getTenant()}/${name}`, ...args);
}

export function getClientWithTenant(url, options, tenant = 'demo') {
  const c = getClient(url, options);
  polyfill(
    c,
    'getTenant',
    function () {
      return this;
    }.bind(tenant),
  ); // non-closure getter
  polyfill(c.record, 'getRecordPT', withTenant.bind(c, 'getRecordP'));
  // polyfill(c.record, 'getRecordT', withTenant.bind(c, 'getRecord'));
  polyfill(c.record, 'getListPT', withTenant.bind(c, 'getListP'));
  // polyfill(c.record, 'getListT', withTenant.bind(c, 'getList'));
  polyfill(c.record, 'snapshotPT', withTenant.bind(c, 'snapshotP'));
  // polyfill(c.record, 'snapshotT', withTenant.bind(c, 'snapshot'));
  polyfill(c.record, 'hasPT', withTenant.bind(c, 'hasP'));
  // polyfill(c.record, 'hasT', withTenant.bind(c, 'has'));
  polyfill(c.record, 'addToListPT', withTenant.bind(c, 'addToListP'));
  polyfill(c.record, 'removeFromListPT', withTenant.bind(c, 'removeFromListP'));
  polyfill(
    c.record,
    'removeFromListPTT',
    function (name, id) {
      return this.record.removeFromListP(
        `${this.getTenant()}/${name}`,
        `${this.getTenant()}/${id}`,
      );
    }.bind(c),
  );
  polyfill(c.record, 'getExistingRecordPT', withTenant.bind(c, 'getExistingRecordP'));
  polyfill(c.record, 'getExistingListPT', withTenant.bind(c, 'getExistingListP'));
  polyfill(c.record, 'setListedRecordPT', withTenant.bind(c, 'setListedRecordP'));
  polyfill(c.record, 'setExistingRecordPT', withTenant.bind(c, 'setExistingRecordP'));
  polyfill(
    c.rpc,
    'makePT',
    function (name, data) {
      return this.rpc.makeP(`${this.getTenant()}/${name}`, data);
    }.bind(c),
  );

  // Depricated methods
  polyfill(c.record, 'getListedRecordPT', () => {
    throw new Error('use setListedRecordPT instead of getListedRecordPT');
  });
  polyfill(c.record, 'listedRecordPT', () => {
    throw new Error('use setListedRecordPT instead of listedRecordPT');
  });
  return c;
}

// ------------------------------------------------------------
//  FOR SINGLETON USE
export const ds = { client: undefined };
<|endoftext|>


<commit_before>// Copyright 2015-16 Volker Sorge
//
// Licensed under the Apache on 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.

/**
 * @fileoverview Skeleton structure for a tree that consists only of a nested
 *     array of node ids.
 *
 * @author volker.sorge@gmail.com (Volker Sorge)
 */

goog.provide('sre.SemanticSkeleton');
goog.provide('sre.SemanticSkeleton.Sexp');

goog.require('sre.BaseUtil');

/**
 * @constructor
 * @param {sre.SemanticSkeleton.Sexp} skeleton The skeleton array.
 */
sre.SemanticSkeleton = function(skeleton) {

  skeleton = skeleton || [];

  /**
   * @type {!sre.SemanticSkeleton.Sexp}
   */
  this.array = skeleton;

};

/**
 * @override
 */
sre.SemanticSkeleton.prototype.toString = function() {
  return sre.SemanticSkeleton.makeSexp_(this.array);
};

/**
 * Turns collapsed element into an sexp like string.
 * @param {!sre.SemanticSkeleton.Sexp} struct Collapse structure.
 * @return {!string} The structure as string.
 * @private
 */
sre.SemanticSkeleton.makeSexp_ = function(struct) {
  if (sre.SemanticSkeleton.simpleCollapseStructure(struct)) {
    return struct.toString();
  }
  if (sre.SemanticSkeleton.contentCollapseStructure(struct)) {
    return '(' + 'c ' +
      struct.slice(1).map(sre.SemanticSkeleton.makeSexp_).join(' ') + ')';
  }
  return '(' + struct.map(sre.SemanticSkeleton.makeSexp_).join(' ') + ')';
};

/**
 * Compute a skeleton structure for a semantic tree.
 * @param {sre.SemanticNode} node The root node of the tree.
 * @return {!sre.SemanticSkeleton} The new skeleton structure object.
 */
sre.SemanticSkeleton.fromTree = function(node) {
  return new sre.SemanticSkeleton(sre.SemanticSkeleton.fromTree_(node));
};

/**
 * Compute a skeleton structure from a string representation.
 * @param {string} skel The skeleton string.
 * @return {!sre.SemanticSkeleton} The new skeleton structure object.
 */
sre.SemanticSkeleton.fromString = function(skel) {
  return new sre.SemanticSkeleton(sre.SemanticSkeleton.fromString_(skel));
};

/**
 * Parses the skeleton structure into an array of integer arrays.
 * @param {!string} skeleton String containing the skeleton structure.
 * @return {!sre.SemanticSkeleton.Sexp} The array of integer arrays.
 * @private
 */
sre.SemanticSkeleton.fromString_ = function(skeleton) {
  var str = skeleton.replace(/\(/g, '[');
  str = str.replace(/\)/g, ']');
  str = str.replace(/ /g, ',');
  str = str.replace(/c/g, '"c"');
  return /** @type {!Array} */(JSON.parse(str));
};

/**
 * Compute a skeleton structure for a semantic tree.
 * @param {sre.SemanticNode} node The root node of the tree.
 * @return {!sre.SemanticSkeleton.Sexp} The collapsed structure annotation
 *     representing the skeleton of the tree.
 * @private
 */
sre.SemanticSkeleton.fromTree_ = function(node) {
  if (!node) {
    return [];
  }
  var content = node.contentNodes;
  if (content.length) {
    var contentStructure = content.map(sre.SemanticSkeleton.fromTree_);
    contentStructure.unshift('c');
  }
  var children = node.childNodes;
  if (!children.length) {
    return content.length ? [node.id, contentStructure] : node.id;
  }
  var structure = children.map(sre.SemanticSkeleton.fromTree_);
  if (content.length) {
    structure.unshift(contentStructure);
  }
  structure.unshift(node.id);
  return structure;
};

/**
 * Type annotation for arrays representing collapsed node structures.
 * @typedef {number|Array.<sre.SemanticSkeleton.Sexp>}
 * @private
 */
sre.SemanticSkeleton.Sexp;

/**
 * Checks if the structure is simple, i.e., a single id number.
 * @param {sre.SemanticSkeleton.Sexp} strct The structure.
 * @return {boolean} True if a simple number.
 */
sre.SemanticSkeleton.simpleCollapseStructure = function(strct) {
  return (typeof strct === 'number');
};

/**
 * Checks if the structure represents collapsed content nodes, i.e., it starts
 * with a c.
 * @param {sre.SemanticSkeleton.Sexp} strct The structure.
 * @return {boolean} True if a content structure.
 */
sre.SemanticSkeleton.contentCollapseStructure = function(strct) {
  return !!strct && !sre.SemanticSkeleton.simpleCollapseStructure(strct) &&
    (strct[0] === 'c');
};

/**
 * Interleaves the ids of two index lists.
 * @param {!sre.SemanticSkeleton.Sexp} first A structured list of
 *     ids.
 * @param {!sre.SemanticSkeleton.Sexp} second A structured list of
 *     ids.
 * @return {!sre.SemanticSkeleton.Sexp} A simple list of ids.
 */
sre.SemanticSkeleton.interleaveIds = function(first, second) {
  return sre.BaseUtil.interleaveLists(
      sre.SemanticSkeleton.collapsedLeafs(first),
      sre.SemanticSkeleton.collapsedLeafs(second));
};

/**
 * Returns a list of the leaf ids for the given collapsed structures.
 * @param {...sre.SemanticSkeleton.Sexp} var_args The collapsed structure
 *     annotations.
 * @return {!Array.<number>} The leafs of the structure annotations.
 */
sre.SemanticSkeleton.collapsedLeafs = function(var_args) {
  var collapseStructure = function(coll) {
    if (sre.SemanticSkeleton.simpleCollapseStructure(coll)) {
      return [coll];
    }
    return sre.SemanticSkeleton.contentCollapseStructure(coll[1]) ?
      coll.slice(2) : coll.slice(1);
  };
  return Array.prototype.slice.call(arguments, 0).
      reduce(function(x, y) {
        return x.concat(collapseStructure(y));
      }, []);
};
<commit_msg>Constructs administrative structures for working with skeletons.<commit_after>// Copyright 2015-16 Volker Sorge
//
// Licensed under the Apache on 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.

/**
 * @fileoverview Skeleton structure for a tree that consists only of a nested
 *     array of node ids.
 *
 * @author volker.sorge@gmail.com (Volker Sorge)
 */

goog.provide('sre.SemanticSkeleton');
goog.provide('sre.SemanticSkeleton.Sexp');

goog.require('sre.BaseUtil');

/**
 * @constructor
 * @param {sre.SemanticSkeleton.Sexp} skeleton The skeleton array.
 */
sre.SemanticSkeleton = function(skeleton) {

  skeleton = skeleton || [];

  /**
   * @type {!sre.SemanticSkeleton.Sexp}
   */
  this.array = skeleton;

  /**
   * @type {Object.<number, !Array.<number>>}
   */
  this.parents = null;

  /**
   * @type {Object.<number, sre.SemanticSkeleton.Sexp>}
   */
  this.levelsMap = null;

};

/**
 * Populates the mapping from id to parents and the mapping from id to levels in
 * the skeleton.
 */
sre.SemanticSkeleton.prototype.populate = function() {
  if (this.parents && this.levelsMap) {
    return;
  }
  this.parents = {};
  this.levelsMap = {};
  this.populate_(this.array, this.array, []);
};

/**
 * Traverses the skeleton tree and composes the parents and layer mappings.
 * @param {!sre.SemanticSkeleton.Sexp} element The current element to traverse.
 * @param {!sre.SemanticSkeleton.Sexp} layer The layer of which the element is a
 *     member.
 * @param {Array.<number>} parents The list of parents of that element.
 * @private
 */
sre.SemanticSkeleton.prototype.populate_ = function(element, layer, parents) {
  if (sre.SemanticSkeleton.simpleCollapseStructure(element)) {
    element = /** @type {number} */(element);
    this.levelsMap[element] = layer;
    this.parents[element] = element === parents[0] ? parents.slice(1) : parents;
    return;
  }
  var newElement = sre.SemanticSkeleton.contentCollapseStructure(element) ?
      element.slice(1) : element;
  var newParents = [newElement[0]].concat(parents);
  for (var i = 0, l = newElement.length; i < l; i++) {
    var current = newElement[i];
    this.populate_(current, element, newParents);
  }
};

/**
 * @override
 */
sre.SemanticSkeleton.prototype.toString = function() {
  return sre.SemanticSkeleton.makeSexp_(this.array);
};

/**
 * Turns collapsed element into an sexp like string.
 * @param {!sre.SemanticSkeleton.Sexp} struct Collapse structure.
 * @return {!string} The structure as string.
 * @private
 */
sre.SemanticSkeleton.makeSexp_ = function(struct) {
  if (sre.SemanticSkeleton.simpleCollapseStructure(struct)) {
    return struct.toString();
  }
  if (sre.SemanticSkeleton.contentCollapseStructure(struct)) {
    return '(' + 'c ' +
        struct.slice(1).map(sre.SemanticSkeleton.makeSexp_).join(' ') + ')';
  }
  return '(' + struct.map(sre.SemanticSkeleton.makeSexp_).join(' ') + ')';
};

/**
 * Compute a skeleton structure for a semantic tree.
 * @param {sre.SemanticNode} node The root node of the tree.
 * @return {!sre.SemanticSkeleton} The new skeleton structure object.
 */
sre.SemanticSkeleton.fromTree = function(node) {
  return new sre.SemanticSkeleton(sre.SemanticSkeleton.fromTree_(node));
};

/**
 * Compute a skeleton structure from a string representation.
 * @param {string} skel The skeleton string.
 * @return {!sre.SemanticSkeleton} The new skeleton structure object.
 */
sre.SemanticSkeleton.fromString = function(skel) {
  return new sre.SemanticSkeleton(sre.SemanticSkeleton.fromString_(skel));
};

/**
 * Parses the skeleton structure into an array of integer arrays.
 * @param {!string} skeleton String containing the skeleton structure.
 * @return {!sre.SemanticSkeleton.Sexp} The array of integer arrays.
 * @private
 */
sre.SemanticSkeleton.fromString_ = function(skeleton) {
  var str = skeleton.replace(/\(/g, '[');
  str = str.replace(/\)/g, ']');
  str = str.replace(/ /g, ',');
  str = str.replace(/c/g, '"c"');
  return /** @type {!Array} */(JSON.parse(str));
};

/**
 * Compute a skeleton structure for a semantic tree.
 * @param {sre.SemanticNode} node The root node of the tree.
 * @return {!sre.SemanticSkeleton.Sexp} The collapsed structure annotation
 *     representing the skeleton of the tree.
 * @private
 */
sre.SemanticSkeleton.fromTree_ = function(node) {
  if (!node) {
    return [];
  }
  var content = node.contentNodes;
  if (content.length) {
    var contentStructure = content.map(sre.SemanticSkeleton.fromTree_);
    contentStructure.unshift('c');
  }
  var children = node.childNodes;
  if (!children.length) {
    return content.length ? [node.id, contentStructure] : node.id;
  }
  var structure = children.map(sre.SemanticSkeleton.fromTree_);
  if (content.length) {
    structure.unshift(contentStructure);
  }
  structure.unshift(node.id);
  return structure;
};

/**
 * Type annotation for arrays representing collapsed node structures.
 * @typedef {number|Array.<sre.SemanticSkeleton.Sexp>}
 * @private
 */
sre.SemanticSkeleton.Sexp;

/**
 * Checks if the structure is simple, i.e., a single id number.
 * @param {sre.SemanticSkeleton.Sexp} strct The structure.
 * @return {boolean} True if a simple number.
 */
sre.SemanticSkeleton.simpleCollapseStructure = function(strct) {
  return (typeof strct === 'number');
};

/**
 * Checks if the structure represents collapsed content nodes, i.e., it starts
 * with a c.
 * @param {sre.SemanticSkeleton.Sexp} strct The structure.
 * @return {boolean} True if a content structure.
 */
sre.SemanticSkeleton.contentCollapseStructure = function(strct) {
  return !!strct && !sre.SemanticSkeleton.simpleCollapseStructure(strct) &&
      (strct[0] === 'c');
};

/**
 * Interleaves the ids of two index lists.
 * @param {!sre.SemanticSkeleton.Sexp} first A structured list of
 *     ids.
 * @param {!sre.SemanticSkeleton.Sexp} second A structured list of
 *     ids.
 * @return {!sre.SemanticSkeleton.Sexp} A simple list of ids.
 */
sre.SemanticSkeleton.interleaveIds = function(first, second) {
  return sre.BaseUtil.interleaveLists(
      sre.SemanticSkeleton.collapsedLeafs(first),
      sre.SemanticSkeleton.collapsedLeafs(second));
};

/**
 * Returns a list of the leaf ids for the given collapsed structures.
 * @param {...sre.SemanticSkeleton.Sexp} var_args The collapsed structure
 *     annotations.
 * @return {!Array.<number>} The leafs of the structure annotations.
 */
sre.SemanticSkeleton.collapsedLeafs = function(var_args) {
  var collapseStructure = function(coll) {
    if (sre.SemanticSkeleton.simpleCollapseStructure(coll)) {
      return [coll];
    }
    return sre.SemanticSkeleton.contentCollapseStructure(coll[1]) ?
        coll.slice(2) : coll.slice(1);
  };
  return Array.prototype.slice.call(arguments, 0).
      reduce(function(x, y) {
        return x.concat(collapseStructure(y));
      }, []);
};
<|endoftext|>


<commit_before>Joshfire.define(['joshfire/class', 'joshfire/tree.ui', 'joshfire/uielements/list', 'joshfire/uielements/panel', 'joshfire/uielements/panel.manager', 'joshfire/uielements/button', 'src/ui-components'], function(Class, UITree, List, Panel, PanelManager, Button, UI) {

  return Class(UITree, {

    buildTree: function() {

      var app = this.app;

      return [
        {
          id: 'header',
          type: Panel,
          htmlClass: 'header',
          children: [
            {
              id: 'prev',
              type: Button,
              label: 'Prev',
              autoShow: false
            },
            {
              id: 'title', // the title or the logo
              type: Panel,
              innerTemplate: UI.tplHeader
            }
          ]
        },
        {
          id: 'content',
          type: PanelManager,
          uiMaster: '/footer',
          children: [
            {
              id: 'itemList',
              //scroller: false, // do not use iScroll it's crappy crap (and doesn't work with a grid made of floats)
              type: List,
              htmlClass: 'abs100',
              loadingTemplate: '

',
              itemTemplate: "<li id='<%=itemHtmlId%>' " +
                            "data-josh-ui-path='<%= path %>' data-josh-grid-id='<%= item.id %>'" +
                            "class='josh-List joshover item-<%= item.itemType.replace('/', '') %> mainitemlist " +
                            // grid view
                            "<% if (item.itemType === 'ImageObject') { %>" +
                              " grid" +
                            "<% } else { %>" +
                            // list view
                              " list" +
                            "<% } %>" +
                            "' >" +
                            "<%= itemInner %>" +
                            "
",
              itemInnerTemplate:
                '<% if (item.itemType === "VideoObject") { %>' +
                  '
<%= item.name %>
' +
                  UI.getItemDescriptionTemplate(70) +
                  UI.tplItemPreview +
                  '' +
                '<% } else if (item.itemType === "ImageObject") { %>' +
                  UI.tplItemThumbnail +
                '<% } else if (item.itemType === "Article/Status") { %>' +
                  UI.tplTweetItem +
                '<% } else if (item.itemType === "Event") { %>' +
                  UI.tplEventItem +
                '<% } else { %>' +
                  '<%= item.name %>' +
                '<% } %>'
            },
            {
              id: 'detail',
              type: Panel,
              loadingTemplate: '

',
              uiDataMaster: '/content/itemList',
              autoShow: false,
              children: [
                {
                  // Article
                  id: 'article',
                  type: Panel,
                  uiDataMaster: '/content/itemList',
                  forceDataPathRefresh: true,
                  loadingTemplate: '

',
                  innerTemplate:
                    '

<%= data.name %>
' +
                      UI.tplDataAuthor +
                    '
' +
                    '<% if (data.articleBody) { print(data.articleBody); } %>',
                  onData: function(ui) {
                    var thisEl = app.ui.element('/content/detail/article').htmlEl;
                    if (ui.data.itemType === 'VideoObject'
                        || ui.data.itemType === 'ImageObject'
                        || ui.data.itemType === 'Article/Status'
                        || ui.data.itemType === 'Event'
                      ) {
                      $(thisEl).hide();
                    }
                    else {
                      $(thisEl).show();
                    }
                  }
                },
                {
                  // Twitter
                  id: 'twitter',
                  type: Panel,
                  uiDataMaster: '/content/itemList',
                  forceDataPathRefresh: true,
                  loadingTemplate: '

',
                  innerTemplate: UI.tplTweetPage,
                  onData: function(ui) {
                    var thisEl = app.ui.element('/content/detail/twitter').htmlEl;
                    if (ui.data.itemType === 'Article/Status') {
                      $(thisEl).show();
                    } else {
                      $(thisEl).hide();
                    }
                  }
                },
                {
                  // Flickr
                  id: 'image',
                  type: Panel,
                  uiDataMaster: '/content/itemList',
                  forceDataPathRefresh: true,
                  loadingTemplate: '

',
                  innerTemplate: '<img src="<%= data.contentURL %>" alt="" />',
                  onData: function(ui) {
                    var thisEl = app.ui.element('/content/detail/image').htmlEl;
                    if (ui.data.itemType === 'ImageObject') {
                      $(thisEl).show();
                    } else {
                      $(thisEl).hide();
                    }
                  }
                },
                {
                  // Event
                  id: 'event',
                  type: Panel,
                  uiDataMaster: '/content/itemList',
                  forceDataPathRefresh: true,
                  loadingTemplate: '

',
                  innerTemplate: UI.tplEventPage,
                  onData: function(ui) {
                    var thisEl = app.ui.element('/content/detail/event').htmlEl;
                    if (ui.data.itemType === 'Event') {
                      $(thisEl).show();
                    } else {
                      $(thisEl).hide();
                    }
                  }
                },
                {
                  // Video
                  id: 'video',
                  type: Panel,
                  forceDataPathRefresh: true,
                  loadingTemplate: '

',
                  uiDataMaster: '/content/itemList',
                  onData: function(ui) {
                    var thisEl = app.ui.element('/content/detail/video').htmlEl,
                        player = app.ui.element('/content/detail/video/player.youtube');

                    if ((ui.data.itemType === 'VideoObject') && ui.data.publisher && (ui.data.publisher.name === 'Youtube')) {
                      player.playWithStaticUrl({
                        url: ui.data.url.replace('http://www.youtube.com/watch?v=', ''),
                        width: '100%'
                      });
                      $(thisEl).show();
                    }
                    else {
                      $(thisEl).hide();
                    }
                  },
                  children: [
                    {
                      id: 'title',
                      type: Panel,
                      uiDataMaster: '/content/itemList',
                      innerTemplate:
                        '

<%= data.name %>
' +
                        UI.tplDataAuthor +
                        '
'
                    },
                    {
                      id: 'player.youtube',
                      type: 'video.youtube',
                      autoShow: true,
                      controls: true,
                      noAutoPlay: false
                    }
                  ]
                }
              ]
            },
            {
              id: 'about',
              type: Panel,
              loadingTemplate: '

',
              autoShow: false,
              innerTemplate: UI.tplAboutPage
            }
          ]
        },
        {
          id: 'footer',
          type: List,
          dataPath: '/datasourcelist/',
          itemInnerTemplate: '<div class="picto item-<%= item.config.col %>">

<%= item.name %>
',
          onData: function(data) {} // trigger data, WTF?
        }
      ];
    }

  });

});<commit_msg>smal work on spreadsheet<commit_after>Joshfire.define(['joshfire/class', 'joshfire/tree.ui', 'joshfire/uielements/list', 'joshfire/uielements/panel', 'joshfire/uielements/panel.manager', 'joshfire/uielements/button', 'src/ui-components'], function(Class, UITree, List, Panel, PanelManager, Button, UI) {

  return Class(UITree, {

    buildTree: function() {

      var app = this.app;

      return [
        {
          id: 'header',
          type: Panel,
          htmlClass: 'header',
          children: [
            {
              id: 'prev',
              type: Button,
              label: 'Prev',
              autoShow: false
            },
            {
              id: 'title', // the title or the logo
              type: Panel,
              innerTemplate: UI.tplHeader
            }
          ]
        },
        {
          id: 'content',
          type: PanelManager,
          uiMaster: '/footer',
          children: [
            {
              id: 'itemList',
              //scroller: false, // do not use iScroll it's crappy crap (and doesn't work with a grid made of floats)
              type: List,
              htmlClass: 'abs100',
              loadingTemplate: '

',
              itemTemplate: "<li id='<%=itemHtmlId%>' " +
                            "data-josh-ui-path='<%= path %>' data-josh-grid-id='<%= item.id %>'" +
                            "class='josh-List joshover item-<%= item.itemType.replace('/', '') %> mainitemlist " +
                            // grid view
                            "<% if (item.itemType === 'ImageObject') { %>" +
                              " grid" +
                            "<% } else { %>" +
                            // list view
                              " list" +
                            "<% } %>" +
                            "' >" +
                            "<%= itemInner %>" +
                            "
",
              itemInnerTemplate:
                '<% if (item.itemType === "VideoObject") { %>' +
                  '
<%= item.name %>
' +
                  UI.getItemDescriptionTemplate(70) +
                  UI.tplItemPreview +
                  '' +
                '<% } else if (item.itemType === "ImageObject") { %>' +
                  UI.tplItemThumbnail +
                '<% } else if (item.itemType === "Article/Status") { %>' +
                  UI.tplTweetItem +
                '<% } else if (item.itemType === "Event") { %>' +
                  UI.tplEventItem +
                //'<% } else if (item.itemType === "Article && item.url && item.url.indexOf("spreadsheed.google.com") != -1) { %>' +
                //  UI.tplEventItem +
                '<% } else { %>' +
                // check if
                  UI.tplItemPreview +
                  '<%= item.name %>' +
                '<% } %>'
            },
            {
              id: 'detail',
              type: Panel,
              loadingTemplate: '

',
              uiDataMaster: '/content/itemList',
              autoShow: false,
              children: [
                {
                  // Article
                  id: 'article',
                  type: Panel,
                  uiDataMaster: '/content/itemList',
                  forceDataPathRefresh: true,
                  loadingTemplate: '

',
                  innerTemplate:
                    '

<%= data.name %>
' +
                      UI.tplDataAuthor +
                    '
' +
                    '<% if (data.articleBody) { print(data.articleBody); } %>',
                  onData: function(ui) {
                    var thisEl = app.ui.element('/content/detail/article').htmlEl;
                    if (ui.data.itemType === 'VideoObject'
                        || ui.data.itemType === 'ImageObject'
                        || ui.data.itemType === 'Article/Status'
                        || ui.data.itemType === 'Event'
                      ) {
                      $(thisEl).hide();
                    }
                    else {
                      $(thisEl).show();
                    }
                  }
                },
                {
                  // Twitter
                  id: 'twitter',
                  type: Panel,
                  uiDataMaster: '/content/itemList',
                  forceDataPathRefresh: true,
                  loadingTemplate: '

',
                  innerTemplate: UI.tplTweetPage,
                  onData: function(ui) {
                    var thisEl = app.ui.element('/content/detail/twitter').htmlEl;
                    if (ui.data.itemType === 'Article/Status') {
                      $(thisEl).show();
                    } else {
                      $(thisEl).hide();
                    }
                  }
                },
                {
                  // Flickr
                  id: 'image',
                  type: Panel,
                  uiDataMaster: '/content/itemList',
                  forceDataPathRefresh: true,
                  loadingTemplate: '

',
                  innerTemplate: '<img src="<%= data.contentURL %>" alt="" />',
                  onData: function(ui) {
                    var thisEl = app.ui.element('/content/detail/image').htmlEl;
                    if (ui.data.itemType === 'ImageObject') {
                      $(thisEl).show();
                    } else {
                      $(thisEl).hide();
                    }
                  }
                },
                {
                  // Event
                  id: 'event',
                  type: Panel,
                  uiDataMaster: '/content/itemList',
                  forceDataPathRefresh: true,
                  loadingTemplate: '

',
                  innerTemplate: UI.tplEventPage,
                  onData: function(ui) {
                    var thisEl = app.ui.element('/content/detail/event').htmlEl;
                    if (ui.data.itemType === 'Event') {
                      $(thisEl).show();
                    } else {
                      $(thisEl).hide();
                    }
                  }
                },
                {
                  // Video
                  id: 'video',
                  type: Panel,
                  forceDataPathRefresh: true,
                  loadingTemplate: '

',
                  uiDataMaster: '/content/itemList',
                  onData: function(ui) {
                    var thisEl = app.ui.element('/content/detail/video').htmlEl,
                        player = app.ui.element('/content/detail/video/player.youtube');

                    if ((ui.data.itemType === 'VideoObject') && ui.data.publisher && (ui.data.publisher.name === 'Youtube')) {
                      player.playWithStaticUrl({
                        url: ui.data.url.replace('http://www.youtube.com/watch?v=', ''),
                        width: '100%'
                      });
                      $(thisEl).show();
                    }
                    else {
                      $(thisEl).hide();
                    }
                  },
                  children: [
                    {
                      id: 'title',
                      type: Panel,
                      uiDataMaster: '/content/itemList',
                      innerTemplate:
                        '

<%= data.name %>
' +
                        UI.tplDataAuthor +
                        '
'
                    },
                    {
                      id: 'player.youtube',
                      type: 'video.youtube',
                      autoShow: true,
                      controls: true,
                      noAutoPlay: false
                    }
                  ]
                }
              ]
            },
            {
              id: 'about',
              type: Panel,
              loadingTemplate: '

',
              autoShow: false,
              innerTemplate: UI.tplAboutPage
            }
          ]
        },
        {
          id: 'footer',
          type: List,
          dataPath: '/datasourcelist/',
          itemInnerTemplate: '<div class="picto item-<%= item.config.col %>">

<%= item.name %>
',
          onData: function(data) {} // trigger data, WTF?
        }
      ];
    }

  });

});<|endoftext|>