<commit_before>var crypto = require('crypto');

var _ = require('lodash');
var uuid = require('node-uuid');
var Promise = require('es6-promise').Promise;

var leaderboard = require('./leaderboard');
var utils = require('./utils');

const MAX_LEADERBOARD_INCR = 10;
const TIME_UPDATE_PLAYTIME = 1000 * 60 * 5;  // 5 minutes

function newUser(client, email) {
    var userData = {
        username: email.split('@')[0],
        email: email,
        permissions: {
            developer: false,
            reviewer: false,
            admin: false
        },
        homepage: '',
        support: '',
        companySlug: '',
        companyName: '',
        dateLastLogin: utils.now(),
        id: uuid.v4()
    };
    client.hset('users', userData.id, JSON.stringify(userData));
    client.hset('usersByEmail', email, userData.id);
    // TODO: Figure out a way to store users by username.
    return userData;
}
exports.newUser = newUser;

function updateUser(client, userID, opts, callback) {
    getUserFromID(client, userID, function(err, userData) {
        if (err) {
            callback(err);
            return;
        }

        var newUserData = _.clone(userData);

        var updateFunctions = {
            companySlug: updateCompanySlug,
            email: updateEmail,
            username: updateUsername
        };

        Promise.all(Object.keys(opts).map(function(key) {
            var updateFunction = updateFunctions[key];
            var value = opts[key];
            if (updateFunction) {
                return updateFunction(value);
            }
            else {
                return updateProperty(key, value);
            }
        })).then(updateClient).catch(function(err) {
            console.error('Error updating user:', err);
            callback(err.message);
        });

        // fallback update method
        function updateProperty(key, newValue) {
            return new Promise(function(resolve, reject) {
                newUserData[key] = newValue;
                resolve(newUserData);
            });
        }
        function updateCompanySlug(newSlug) {
            return new Promise(function(resolve, reject) {
                if (userData.companySlug !== newSlug) {
                    newUserData.companySlug = newSlug;
                    // remove id from old companySlug and add to new one
                    client.lrem(userData.companySlug, 1, userData.id);
                    client.rpush(newUserData.companySlug, newUserData.id);
                }
                resolve(newUserData);
            });
        }
        function updateEmail(newEmail) {
            return new Promise(function(resolve, reject) {
                // Email validation is handled by node-restify-validation, so we can assume
                // that if it exists, it is a well-formatted address.
                // TODO: We should probably send an email when the address changes
                var email = userData.email;
                if (newEmail && newEmail !== email) {
                    client.hexists('usersByEmail', newEmail, function(err, resp) {
                        if (err || resp) {
                            reject(err);
                            return;
                        }
                        console.log('updating email from', email, 'to', newEmail, 'for user', newUserData.id);
                        newUserData.email = newEmail;
                        client.hdel('usersByEmail', email);
                        client.hset('usersByEmail', newEmail, newUserData.id);

                        resolve(newUserData);
                    });
                } else {
                    resolve(newUserData);
                }
            });
        }
        function updateUsername(newUsername) {
            return new Promise(function(resolve, reject) {
                // TODO: Some username validation would be nice to have (ie. profanity check)
                if (newUsername && newUsername !== newUserData.username) {
                    console.log('updating username from', newUserData.username, 'to', newUsername, 'for user', newUserData.id);
                    newUserData.username = newUsername;
                }
                resolve(newUserData);
            });
        }

        function updateClient() {
            // Only update the modified timestamp if anything but the lastLogin
            // timestamp has changed.
            if (!_.isEqual(_.omit(userData, 'dateLastLogin'),
                           _.omit(newUserData, 'dateLastLogin'))) {
                newUserData.datelastModified = utils.now();
            }

            if (!_.isEqual(userData, newUserData)) {
                client.hset('users', newUserData.id, JSON.stringify(newUserData), function(err, reply) {
                    if (err) {
                        callback(err);
                    } else {
                        callback(undefined, newUserData);
                    }
                });
            } else {
                callback(undefined, newUserData);
            }
        }
    });
}
exports.updateUser = updateUser;

// This manages user state for a single connection.
function user(dataChannel) {
    this.authenticated = false;
    this.dataChannel = dataChannel;
    this.data = {};
    this.friends = null;  // A cache.
}

user.prototype.eachFriend = function(cb) {
    if (this.friends) {
        this.friends.forEach(cb);
    } else {
        var self = this;
        this.dataChannel.smembers('friends:' + this.get('id'), function(err, resp) {
            if (err || !resp) return;
            // NOTE: This cheats. If a user adds a friend, the friend won't get
            // notifications until this user re-connects. Womp womp.
            (self.friends = resp).forEach(cb);
        });
    }
};

user.prototype.notifyFriends = function(type, data) {
    data.notification_type = type;
    data.type = 'notification';
    data.from = publicUserObj(this.get('full'));
    var json = JSON.stringify(data);
    var self = this;
    this.eachFriend(function(friendID) {
        self.dataChannel.publish('user:' + friendID, json);
    });
};

user.prototype.set = function(key, value) {
    this.data[key] = value;
};

user.prototype.get = function(key) {
    return this.data[key] || null;
};

user.prototype.authenticate = function(email, callback) {
    var self = this;
    getUserFromEmail(this.dataChannel, email, function(err, userData) {
        if (err) {
            callback('error_fetching_user');
            return;
        }
        if (!userData) {
            finishAuth(newUser(self.dataChannel, email));
        } else {
            if (userData.id === self.get('id')) {
                callback(null);
                return;
            }
            self.dataChannel.sismember('authenticated', userData.id, function(err, resp) {
                if (resp) {
                    callback('already_authenticated');
                    return;
                }
                finishAuth(userData);
            });
        }
    });

    function finishAuth(userData) {
        self.dataChannel.sadd('authenticated', userData.id);
        self.set('username', userData.username);
        self.set('id', userData.id);
        self.set('email', userData.email);
        self.set('full', userData);
        self.authenticated = true;
        callback(null);
        // After we've responded, send out notifications.
        self.notifyFriends('friend_online', {});
    }
    // TODO: There can be a race condition here. If the client doesn't
    // wait for the server to process an auth request before sending a
    // second request, their second request will be processed before
    // this callback executes. Someday, there should be a queue of
    // requests which can get processed in order when this callback
    // fires.
};

user.prototype.startPlaying = function(game, callback) {
    var self = this;
    this.dataChannel.sismember('gamesPurchased:' + this.get('id'), game, function(err, resp) {
        if (err || !resp) {
            callback('does_not_own');
            return;
        }
        self._startPlaying(game);
        callback(null);
    });
};

user.prototype._startPlaying = function(game) {
    var timeNow = utils.now();
    var currentlyPlaying = this.get('currentlyPlaying');
    if (currentlyPlaying) {
        clearInterval(this.get('playTimer'));

        var startedPlaying = this.get('startedPlaying');
        this.incrPlaytime(currentlyPlaying, timeNow - startedPlaying);
    }
    // TODO: Validate that game is valid.
    // And validate that user can play/paid for game.
    this.set('startedPlaying', timeNow);
    this.set('currentlyPlaying', game);
    this.dataChannel.hset('currentlyPlaying', this.get('id'), game);
    this.dataChannel.sadd('gamePlaying:' + game, this.get('id'));
    this.dataChannel.sadd('userPlayed:' + this.get('id'), game);
    this.dataChannel.sadd('gamePlayed:' + game, this.get('id'));
    this.notifyFriends('friend_playing', {
        game: game  // TODO: Make this use the real game name.
    });

    // Set up a timer to update this every few minutes so the data doesn't
    // get stale.
    var self = this;
    this.set('playTimer', setInterval(function() {
        self.incrPlaytime(game, TIME_UPDATE_PLAYTIME);
        self.set('startedPlaying', utils.now());
    }, TIME_UPDATE_PLAYTIME));
};

user.prototype.incrPlaytime = function(game, amount) {
    if (!amount) {
        return;
    }
    this.dataChannel.hincrby(
        'playtime:' + this.get('currentlyPlaying'),
        this.get('id'),
        amount || 0
    );
};

user.prototype.donePlaying = function() {
    var currentlyPlaying = this.get('currentlyPlaying');
    if (!currentlyPlaying) {
        return;
    }
    this.incrPlaytime(currentlyPlaying, utils.now() - this.get('startedPlaying'));
    this.dataChannel.hdel('currentlyPlaying', this.get('id'));
    this.dataChannel.srem('gamePlaying:' + currentlyPlaying, this.get('id'));

    // Clean up the playtime updater.
    clearInterval(this.get('playTimer'));

    this.set('currentlyPlaying', false);
};

user.prototype.updateLeaderboard = function(board, value, callback) {
    // TODO: Throttle this function.

    var currentlyPlaying = this.get('currentlyPlaying');
    // Check that the user is playing a game.
    if (!currentlyPlaying) {
        callback('not_playing_a_game');
        return;
    }

    // Check that the board is being updated with a valid value.
    if (typeof value !== 'number' ||
        value === 0 ||
        value < -1 * MAX_LEADERBOARD_INCR ||
        value > MAX_LEADERBOARD_INCR) {
        callback('invalid_increment');
        return;
    }

    // Check that the board the user is trying to update exists.
    var self = this;
    leaderboard.isLeaderboard(
        this.dataChannel,
        currentlyPlaying,
        board,
        function(exists) {
            if (!exists) {
                callback('invalid_board');
                return;
            }

            leaderboard.updateLeaderboard(
                self.dataChannel,
                currentlyPlaying,
                board,
                self.get('id'),
                value
            );
        }
    );
};

user.prototype.isFriendsWith = function(friend, cb) {
    // Cheaty optimization.
    if (this.friends && this.friends.indexOf(friend) !== -1) {
        cb(true);
        return;
    }
    this.dataChannel.sismember('friends:' + this.get('id'), friend, function(err, resp) {
        cb(!err && resp);
    });
};

user.prototype.finish = function() {
    if (this.authenticated) {
        this.dataChannel.srem('authenticated', this.get('id'));
        this.authenticated = false;
    }
    this.donePlaying();
};

exports.User = user;

function getUserFromID(client, id, callback) {
    /*
    `callback` is called with an error parameter and a parameter
    containing a JSON blob of user data.
    */
    client.hget('users', id, function(err, resp) {
        if (err) {
            callback('db_error');
        } else if (!resp) {
            callback('no_such_user');
        } else {
            callback(null, JSON.parse(resp));
        }
    });
}
exports.getUserFromID = getUserFromID;

function getUserFromEmail(client, email, callback) {
    getUserIDFromEmail(client, email, function(err, id) {
        if (err && err !== 'no_such_user') {
            callback(err);
        } else if (!id) {
            callback(null, null);
        } else {
            getUserFromID(client, id, callback);
        }
    });
}
exports.getUserFromEmail = getUserFromEmail;

function getUserIDFromEmail(client, email, callback) {
    client.hget('usersByEmail', email, function(err, resp) {
        if (err) {
            callback('db_error');
        } else if (!resp) {
            callback('no_such_user');
        } else {
            // `resp` is the user ID.
            callback(null, resp);
        }
    });
}
exports.getUserIDFromEmail = getUserIDFromEmail;

// Get all developers for a given slug
function getUserIDsFromDevSlug(client, slug, callback) {
    client.lrange(slug, 0, -1, function(err, resp) {
        if (err) {
            callback('db_error');
        } else if (!resp) {
            callback('no_users');
        } else {
            // `resp` is the list of developer IDs.
            callback(null, resp);
        }
    });
}
exports.getUserIDsFromDevSlug = getUserIDsFromDevSlug;

// Given a slug, get company info.  Look at one developer to get Company Info (they should all be the same)
function getCompanyInfoFromDevSlug(client, slug, callback) {
    getUserIDsFromDevSlug(client, slug, function(err, devs) {
        if (err && err !== 'no_users') {
            callback(err);
        } else if (!devs) {
            callback(null, null);
        } else {
            getUserFromID(client, devs[0], function(err, user) {
                if (err && err !== 'no_such_user') {
                    callback(err);
                } else if (err === 'no_such_user') {
                    callback('no users with dev slug');
                } else {
                    // the response will be company info
                    callback(null, publicDevObj(user));
                }
            })
        }
    });
}
exports.getCompanyInfoFromDevSlug = getCompanyInfoFromDevSlug;

function getGravatarURL(email) {
    return 'http://www.gravatar.com/avatar/' + crypto.createHash('md5').update(email.toLowerCase()).digest('hex');
}
exports.getGravatarURL = getGravatarURL;

function publicUserObj(full) {
    return {
        avatar: getGravatarURL(full.email),
        username: full.username,
        id: full.id
    };
}
exports.publicUserObj = publicUserObj;

function publicDevObj(full) {
    return {
        // TODO: Company images
        avatar: getGravatarURL(full.email),
        companyName: full.companyName,
        homepage: full.homepage,
        support: full.support
    }
}
exports.publicDevObj = publicDevObj;

function getPublicUserObj(client, id, callback) {
    // `callback` is called with a single parameter, which is either
    // the public user object or `null`.
    client.hget('users', id, function(err, resp) {
        if (err || !resp) {
            callback(null);
            return;
        }
        try {
            callback(publicUserObj(JSON.parse(resp)));
        } catch(e) {
            callback(null);
        }
    });
}
exports.getPublicUserObj = getPublicUserObj;

function getPublicUserObjList(client, ids, callback) {
    // `callback` is called with a single parameter, which is an
    // array of public user objects. If any result is invalid, it
    // is not included.
    if (!ids.length) {
        callback([]);
        return;
    }
    client.hmget(['users'].concat(ids), function(err, resp) {
        if (err || !resp) {
            callback(null);
            return;
        }
        callback(resp.map(function(full) {
            if (!full) return;
            try {
                return publicUserObj(JSON.parse(full));
            } catch(e) {
                return;
            }
        }).filter(function(x) {return x;}));
    });
}
exports.getPublicUserObjList = getPublicUserObjList;
<commit_msg>Removed an entire line of code<commit_after>var crypto = require('crypto');

var _ = require('lodash');
var uuid = require('node-uuid');
var Promise = require('es6-promise').Promise;

var leaderboard = require('./leaderboard');
var utils = require('./utils');

const MAX_LEADERBOARD_INCR = 10;
const TIME_UPDATE_PLAYTIME = 1000 * 60 * 5;  // 5 minutes

function newUser(client, email) {
    var userData = {
        username: email.split('@')[0],
        email: email,
        permissions: {
            developer: false,
            reviewer: false,
            admin: false
        },
        homepage: '',
        support: '',
        companySlug: '',
        companyName: '',
        dateLastLogin: utils.now(),
        id: uuid.v4()
    };
    client.hset('users', userData.id, JSON.stringify(userData));
    client.hset('usersByEmail', email, userData.id);
    // TODO: Figure out a way to store users by username.
    return userData;
}
exports.newUser = newUser;

function updateUser(client, userID, opts, callback) {
    getUserFromID(client, userID, function(err, userData) {
        if (err) {
            callback(err);
            return;
        }

        var newUserData = _.clone(userData);

        var updateFunctions = {
            companySlug: updateCompanySlug,
            email: updateEmail,
            username: updateUsername
        };

        Promise.all(Object.keys(opts).map(function(key) {
            var updateFunction = updateFunctions[key];
            var value = opts[key];
            if (updateFunction) {
                return updateFunction(value);
            } else {
                return updateProperty(key, value);
            }
        })).then(updateClient).catch(function(err) {
            console.error('Error updating user:', err);
            callback(err.message);
        });

        // fallback update method
        function updateProperty(key, newValue) {
            return new Promise(function(resolve, reject) {
                newUserData[key] = newValue;
                resolve(newUserData);
            });
        }
        function updateCompanySlug(newSlug) {
            return new Promise(function(resolve, reject) {
                if (userData.companySlug !== newSlug) {
                    newUserData.companySlug = newSlug;
                    // remove id from old companySlug and add to new one
                    client.lrem(userData.companySlug, 1, userData.id);
                    client.rpush(newUserData.companySlug, newUserData.id);
                }
                resolve(newUserData);
            });
        }
        function updateEmail(newEmail) {
            return new Promise(function(resolve, reject) {
                // Email validation is handled by node-restify-validation, so we can assume
                // that if it exists, it is a well-formatted address.
                // TODO: We should probably send an email when the address changes
                var email = userData.email;
                if (newEmail && newEmail !== email) {
                    client.hexists('usersByEmail', newEmail, function(err, resp) {
                        if (err || resp) {
                            reject(err);
                            return;
                        }
                        console.log('updating email from', email, 'to', newEmail, 'for user', newUserData.id);
                        newUserData.email = newEmail;
                        client.hdel('usersByEmail', email);
                        client.hset('usersByEmail', newEmail, newUserData.id);

                        resolve(newUserData);
                    });
                } else {
                    resolve(newUserData);
                }
            });
        }
        function updateUsername(newUsername) {
            return new Promise(function(resolve, reject) {
                // TODO: Some username validation would be nice to have (ie. profanity check)
                if (newUsername && newUsername !== newUserData.username) {
                    console.log('updating username from', newUserData.username, 'to', newUsername, 'for user', newUserData.id);
                    newUserData.username = newUsername;
                }
                resolve(newUserData);
            });
        }

        function updateClient() {
            // Only update the modified timestamp if anything but the lastLogin
            // timestamp has changed.
            if (!_.isEqual(_.omit(userData, 'dateLastLogin'),
                           _.omit(newUserData, 'dateLastLogin'))) {
                newUserData.datelastModified = utils.now();
            }

            if (!_.isEqual(userData, newUserData)) {
                client.hset('users', newUserData.id, JSON.stringify(newUserData), function(err, reply) {
                    if (err) {
                        callback(err);
                    } else {
                        callback(undefined, newUserData);
                    }
                });
            } else {
                callback(undefined, newUserData);
            }
        }
    });
}
exports.updateUser = updateUser;

// This manages user state for a single connection.
function user(dataChannel) {
    this.authenticated = false;
    this.dataChannel = dataChannel;
    this.data = {};
    this.friends = null;  // A cache.
}

user.prototype.eachFriend = function(cb) {
    if (this.friends) {
        this.friends.forEach(cb);
    } else {
        var self = this;
        this.dataChannel.smembers('friends:' + this.get('id'), function(err, resp) {
            if (err || !resp) return;
            // NOTE: This cheats. If a user adds a friend, the friend won't get
            // notifications until this user re-connects. Womp womp.
            (self.friends = resp).forEach(cb);
        });
    }
};

user.prototype.notifyFriends = function(type, data) {
    data.notification_type = type;
    data.type = 'notification';
    data.from = publicUserObj(this.get('full'));
    var json = JSON.stringify(data);
    var self = this;
    this.eachFriend(function(friendID) {
        self.dataChannel.publish('user:' + friendID, json);
    });
};

user.prototype.set = function(key, value) {
    this.data[key] = value;
};

user.prototype.get = function(key) {
    return this.data[key] || null;
};

user.prototype.authenticate = function(email, callback) {
    var self = this;
    getUserFromEmail(this.dataChannel, email, function(err, userData) {
        if (err) {
            callback('error_fetching_user');
            return;
        }
        if (!userData) {
            finishAuth(newUser(self.dataChannel, email));
        } else {
            if (userData.id === self.get('id')) {
                callback(null);
                return;
            }
            self.dataChannel.sismember('authenticated', userData.id, function(err, resp) {
                if (resp) {
                    callback('already_authenticated');
                    return;
                }
                finishAuth(userData);
            });
        }
    });

    function finishAuth(userData) {
        self.dataChannel.sadd('authenticated', userData.id);
        self.set('username', userData.username);
        self.set('id', userData.id);
        self.set('email', userData.email);
        self.set('full', userData);
        self.authenticated = true;
        callback(null);
        // After we've responded, send out notifications.
        self.notifyFriends('friend_online', {});
    }
    // TODO: There can be a race condition here. If the client doesn't
    // wait for the server to process an auth request before sending a
    // second request, their second request will be processed before
    // this callback executes. Someday, there should be a queue of
    // requests which can get processed in order when this callback
    // fires.
};

user.prototype.startPlaying = function(game, callback) {
    var self = this;
    this.dataChannel.sismember('gamesPurchased:' + this.get('id'), game, function(err, resp) {
        if (err || !resp) {
            callback('does_not_own');
            return;
        }
        self._startPlaying(game);
        callback(null);
    });
};

user.prototype._startPlaying = function(game) {
    var timeNow = utils.now();
    var currentlyPlaying = this.get('currentlyPlaying');
    if (currentlyPlaying) {
        clearInterval(this.get('playTimer'));

        var startedPlaying = this.get('startedPlaying');
        this.incrPlaytime(currentlyPlaying, timeNow - startedPlaying);
    }
    // TODO: Validate that game is valid.
    // And validate that user can play/paid for game.
    this.set('startedPlaying', timeNow);
    this.set('currentlyPlaying', game);
    this.dataChannel.hset('currentlyPlaying', this.get('id'), game);
    this.dataChannel.sadd('gamePlaying:' + game, this.get('id'));
    this.dataChannel.sadd('userPlayed:' + this.get('id'), game);
    this.dataChannel.sadd('gamePlayed:' + game, this.get('id'));
    this.notifyFriends('friend_playing', {
        game: game  // TODO: Make this use the real game name.
    });

    // Set up a timer to update this every few minutes so the data doesn't
    // get stale.
    var self = this;
    this.set('playTimer', setInterval(function() {
        self.incrPlaytime(game, TIME_UPDATE_PLAYTIME);
        self.set('startedPlaying', utils.now());
    }, TIME_UPDATE_PLAYTIME));
};

user.prototype.incrPlaytime = function(game, amount) {
    if (!amount) {
        return;
    }
    this.dataChannel.hincrby(
        'playtime:' + this.get('currentlyPlaying'),
        this.get('id'),
        amount || 0
    );
};

user.prototype.donePlaying = function() {
    var currentlyPlaying = this.get('currentlyPlaying');
    if (!currentlyPlaying) {
        return;
    }
    this.incrPlaytime(currentlyPlaying, utils.now() - this.get('startedPlaying'));
    this.dataChannel.hdel('currentlyPlaying', this.get('id'));
    this.dataChannel.srem('gamePlaying:' + currentlyPlaying, this.get('id'));

    // Clean up the playtime updater.
    clearInterval(this.get('playTimer'));

    this.set('currentlyPlaying', false);
};

user.prototype.updateLeaderboard = function(board, value, callback) {
    // TODO: Throttle this function.

    var currentlyPlaying = this.get('currentlyPlaying');
    // Check that the user is playing a game.
    if (!currentlyPlaying) {
        callback('not_playing_a_game');
        return;
    }

    // Check that the board is being updated with a valid value.
    if (typeof value !== 'number' ||
        value === 0 ||
        value < -1 * MAX_LEADERBOARD_INCR ||
        value > MAX_LEADERBOARD_INCR) {
        callback('invalid_increment');
        return;
    }

    // Check that the board the user is trying to update exists.
    var self = this;
    leaderboard.isLeaderboard(
        this.dataChannel,
        currentlyPlaying,
        board,
        function(exists) {
            if (!exists) {
                callback('invalid_board');
                return;
            }

            leaderboard.updateLeaderboard(
                self.dataChannel,
                currentlyPlaying,
                board,
                self.get('id'),
                value
            );
        }
    );
};

user.prototype.isFriendsWith = function(friend, cb) {
    // Cheaty optimization.
    if (this.friends && this.friends.indexOf(friend) !== -1) {
        cb(true);
        return;
    }
    this.dataChannel.sismember('friends:' + this.get('id'), friend, function(err, resp) {
        cb(!err && resp);
    });
};

user.prototype.finish = function() {
    if (this.authenticated) {
        this.dataChannel.srem('authenticated', this.get('id'));
        this.authenticated = false;
    }
    this.donePlaying();
};

exports.User = user;

function getUserFromID(client, id, callback) {
    /*
    `callback` is called with an error parameter and a parameter
    containing a JSON blob of user data.
    */
    client.hget('users', id, function(err, resp) {
        if (err) {
            callback('db_error');
        } else if (!resp) {
            callback('no_such_user');
        } else {
            callback(null, JSON.parse(resp));
        }
    });
}
exports.getUserFromID = getUserFromID;

function getUserFromEmail(client, email, callback) {
    getUserIDFromEmail(client, email, function(err, id) {
        if (err && err !== 'no_such_user') {
            callback(err);
        } else if (!id) {
            callback(null, null);
        } else {
            getUserFromID(client, id, callback);
        }
    });
}
exports.getUserFromEmail = getUserFromEmail;

function getUserIDFromEmail(client, email, callback) {
    client.hget('usersByEmail', email, function(err, resp) {
        if (err) {
            callback('db_error');
        } else if (!resp) {
            callback('no_such_user');
        } else {
            // `resp` is the user ID.
            callback(null, resp);
        }
    });
}
exports.getUserIDFromEmail = getUserIDFromEmail;

// Get all developers for a given slug
function getUserIDsFromDevSlug(client, slug, callback) {
    client.lrange(slug, 0, -1, function(err, resp) {
        if (err) {
            callback('db_error');
        } else if (!resp) {
            callback('no_users');
        } else {
            // `resp` is the list of developer IDs.
            callback(null, resp);
        }
    });
}
exports.getUserIDsFromDevSlug = getUserIDsFromDevSlug;

// Given a slug, get company info.  Look at one developer to get Company Info (they should all be the same)
function getCompanyInfoFromDevSlug(client, slug, callback) {
    getUserIDsFromDevSlug(client, slug, function(err, devs) {
        if (err && err !== 'no_users') {
            callback(err);
        } else if (!devs) {
            callback(null, null);
        } else {
            getUserFromID(client, devs[0], function(err, user) {
                if (err && err !== 'no_such_user') {
                    callback(err);
                } else if (err === 'no_such_user') {
                    callback('no users with dev slug');
                } else {
                    // the response will be company info
                    callback(null, publicDevObj(user));
                }
            })
        }
    });
}
exports.getCompanyInfoFromDevSlug = getCompanyInfoFromDevSlug;

function getGravatarURL(email) {
    return 'http://www.gravatar.com/avatar/' + crypto.createHash('md5').update(email.toLowerCase()).digest('hex');
}
exports.getGravatarURL = getGravatarURL;

function publicUserObj(full) {
    return {
        avatar: getGravatarURL(full.email),
        username: full.username,
        id: full.id
    };
}
exports.publicUserObj = publicUserObj;

function publicDevObj(full) {
    return {
        // TODO: Company images
        avatar: getGravatarURL(full.email),
        companyName: full.companyName,
        homepage: full.homepage,
        support: full.support
    }
}
exports.publicDevObj = publicDevObj;

function getPublicUserObj(client, id, callback) {
    // `callback` is called with a single parameter, which is either
    // the public user object or `null`.
    client.hget('users', id, function(err, resp) {
        if (err || !resp) {
            callback(null);
            return;
        }
        try {
            callback(publicUserObj(JSON.parse(resp)));
        } catch(e) {
            callback(null);
        }
    });
}
exports.getPublicUserObj = getPublicUserObj;

function getPublicUserObjList(client, ids, callback) {
    // `callback` is called with a single parameter, which is an
    // array of public user objects. If any result is invalid, it
    // is not included.
    if (!ids.length) {
        callback([]);
        return;
    }
    client.hmget(['users'].concat(ids), function(err, resp) {
        if (err || !resp) {
            callback(null);
            return;
        }
        callback(resp.map(function(full) {
            if (!full) return;
            try {
                return publicUserObj(JSON.parse(full));
            } catch(e) {
                return;
            }
        }).filter(function(x) {return x;}));
    });
}
exports.getPublicUserObjList = getPublicUserObjList;
<|endoftext|>


<commit_before>var WebVTTParser = require("..").WebVTTParser,
    FakeWindow = require("./fake-window.js"),
    assert = Object.create(require("assert")),
    deepEqual = require("underscore").isEqual,
    difflet = require("difflet")({ indent: 2, deepEqual: deepEqual }),
    fs = require("fs"),
    path = require("path");

// Converts a node buffer to a Uint8Array buffer.
Buffer.prototype.toUint8Array = function() {
  var buf = new Uint8Array(this.length);
  for (var i = 0; i < this.length; i++)
    buf[i] = this[i];
  return buf;
};

// Re-root paths to tests/* since we're in tests/util/*
function fixTestPath(filename) {
  return path.join(__dirname, "..", "tests", filename);
}

// Set the parentNode value to undefined when trying to stringify the JSON.
// Without this we will get a circular data structure which stringify will
// not be able to handle.
function filterJson(key, value) {
  if (key == "parentNode")
    return undefined;
  return value;
}

// Parse the data (i.e., file is already read into `data`), potentially
// splitting parsing into two chunks at `chunkAt`. Leave `chunkAt`
// undefined to parse file whole. The `decoder` is how we'll decode the
// data (null means use default TextDecoder).
function _parse(data, decoder, chunkAt) {
  var result = {
    regions: [],
    cues: []
  };
  chunkAt = !!chunkAt ? Math.min(chunkAt, data.length) : 0;

  var p = new WebVTTParser(FakeWindow, decoder);
  p.oncue = function(cue) {
    // Also parse the cue's content and run the cue through the processing
    // model.
    cue.domTree = WebVTTParser.processCues(FakeWindow,
                                           [cue], filterJson)[0];
    result.cues.push(cue);
  };
  p.onregion = function(region) {
    result.regions.push(region);
  };

  if (!chunkAt) {
    p.parse(data);
  } else {
    p.parse(data.subarray(0, chunkAt));
    p.parse(data.subarray(chunkAt));
  }
  p.flush();

  return result;
}

// Parse the vtt file at filename. If filename is rooted in tests/
// we need to fix up the path (usePathUnchanged should be false).
// If we want to break the file at a character position to simulate
// streaming, chunkAt should be set to some number <= the number of
// characters in the file.
function parse(filename, usePathUnchanged) {
  if (!usePathUnchanged) {
    filename = fixTestPath(filename);
  }

  return _parse(fs.readFileSync(filename).toUint8Array());
}

function getVttData(data, decoder, chunkAt) {
  var vttData;
  try {
    vttData = _parse(data, decoder, chunkAt);
  } catch(e) {
    return null;
  }

  return JSON.parse(JSON.stringify(vttData, filterJson, 2));
}

// Extend assert with an easy way to compare json files to parsed vtt files.
function jsonEqual(vttFilename, jsonFilename, message, utf8Only) {
  message = message || "Compare " + vttFilename + " to " + jsonFilename;
  jsonFilename = fixTestPath(jsonFilename);
  vttFilename = fixTestPath(vttFilename);

  var json, vtt;

  function fail(error) {
    // Pretty-print an object diff so we can figure out what failed
    var diff = difflet.compare(vtt, json);
    console.error("Failed JSON diff for " + jsonFilename + " (" + error + ")", diff);
    assert.ok(false, message);
  }

  try {
    json = require(jsonFilename);
  } catch(e) {
    return assert.fail(vttFilename, jsonFilename, "Unable to open " + jsonFilename, "===");
  }

  var data = fs.readFileSync(vttFilename).toUint8Array(),
      size = data.length;

  // First check that things work when parsing the file whole
  vtt = getVttData(data);
  if (!deepEqual(vtt, json)) {
    return fail("parsing file as binary utf8 without streaming");
  }

  // Now check again using streaming with different chunk sizes
  while (--size >= 3) {
    vtt = getVttData(data, null, size);
    if (!deepEqual(vtt, json)) {
      return fail("parsing file as binary utf8 with streaming, chunk size=" + size);
    }
  }

  // Bail now if we're only supposed to run utf8 based tests.
  if (utf8Only) {
    assert.ok(true, message);
    return;
  }

  // Finally, check it using a StringDecoder
  vtt = getVttData(String(fs.readFileSync(vttFilename)), WebVTTParser.StringDecoder());
  if (!deepEqual(vtt, json)) {
    return fail("parsing file as string without streaming");
  }

  assert.ok(true, message);
};

// Compare JSON to live parsed data using both UTF8 and String decoders
assert.jsonEqual = function(vttFilename, jsonFilename, message) {
  jsonEqual(vttFilename, jsonFilename, message);
};

// Compare JSON to live parsed data using only UTF8 decoder
assert.jsonEqualUTF8 = function(vttFilename, jsonFilename, message) {
  jsonEqual(vttFilename, jsonFilename, message, true);
};

// Shim TextEncoder and TextDecoder on the global if necessary so vtt.js
// can find them. See http://encoding.spec.whatwg.org/
var stringencoding = require("./stringencoding/encoding.js");
global.TextEncoder = global.TextEncoder || stringencoding.TextEncoder;
global.TextDecoder = global.TextDecoder || stringencoding.TextDecoder;

module.exports = {
  WebVTTParser: WebVTTParser,
  FakeWindow: FakeWindow,
  filterJson: filterJson,
  assert: assert,
  parse: parse
};
<commit_msg>Remove incorrect arg param to convertCueToDOMTree().<commit_after>var WebVTTParser = require("..").WebVTTParser,
    FakeWindow = require("./fake-window.js"),
    assert = Object.create(require("assert")),
    deepEqual = require("underscore").isEqual,
    difflet = require("difflet")({ indent: 2, deepEqual: deepEqual }),
    fs = require("fs"),
    path = require("path");

// Converts a node buffer to a Uint8Array buffer.
Buffer.prototype.toUint8Array = function() {
  var buf = new Uint8Array(this.length);
  for (var i = 0; i < this.length; i++)
    buf[i] = this[i];
  return buf;
};

// Re-root paths to tests/* since we're in tests/util/*
function fixTestPath(filename) {
  return path.join(__dirname, "..", "tests", filename);
}

// Set the parentNode value to undefined when trying to stringify the JSON.
// Without this we will get a circular data structure which stringify will
// not be able to handle.
function filterJson(key, value) {
  if (key == "parentNode")
    return undefined;
  return value;
}

// Parse the data (i.e., file is already read into `data`), potentially
// splitting parsing into two chunks at `chunkAt`. Leave `chunkAt`
// undefined to parse file whole. The `decoder` is how we'll decode the
// data (null means use default TextDecoder).
function _parse(data, decoder, chunkAt) {
  var result = {
    regions: [],
    cues: []
  };
  chunkAt = !!chunkAt ? Math.min(chunkAt, data.length) : 0;

  var p = new WebVTTParser(FakeWindow, decoder);
  p.oncue = function(cue) {
    // Also parse the cue's content and run the cue through the processing
    // model.
    cue.domTree = WebVTTParser.processCues(FakeWindow, [cue])[0];
    result.cues.push(cue);
  };
  p.onregion = function(region) {
    result.regions.push(region);
  };

  if (!chunkAt) {
    p.parse(data);
  } else {
    p.parse(data.subarray(0, chunkAt));
    p.parse(data.subarray(chunkAt));
  }
  p.flush();

  return result;
}

// Parse the vtt file at filename. If filename is rooted in tests/
// we need to fix up the path (usePathUnchanged should be false).
// If we want to break the file at a character position to simulate
// streaming, chunkAt should be set to some number <= the number of
// characters in the file.
function parse(filename, usePathUnchanged) {
  if (!usePathUnchanged) {
    filename = fixTestPath(filename);
  }

  return _parse(fs.readFileSync(filename).toUint8Array());
}

function getVttData(data, decoder, chunkAt) {
  var vttData;
  try {
    vttData = _parse(data, decoder, chunkAt);
  } catch(e) {
    return null;
  }

  return JSON.parse(JSON.stringify(vttData, filterJson, 2));
}

// Extend assert with an easy way to compare json files to parsed vtt files.
function jsonEqual(vttFilename, jsonFilename, message, utf8Only) {
  message = message || "Compare " + vttFilename + " to " + jsonFilename;
  jsonFilename = fixTestPath(jsonFilename);
  vttFilename = fixTestPath(vttFilename);

  var json, vtt;

  function fail(error) {
    // Pretty-print an object diff so we can figure out what failed
    var diff = difflet.compare(vtt, json);
    console.error("Failed JSON diff for " + jsonFilename + " (" + error + ")", diff);
    assert.ok(false, message);
  }

  try {
    json = require(jsonFilename);
  } catch(e) {
    return assert.fail(vttFilename, jsonFilename, "Unable to open " + jsonFilename, "===");
  }

  var data = fs.readFileSync(vttFilename).toUint8Array(),
      size = data.length;

  // First check that things work when parsing the file whole
  vtt = getVttData(data);
  if (!deepEqual(vtt, json)) {
    return fail("parsing file as binary utf8 without streaming");
  }

  // Now check again using streaming with different chunk sizes
  while (--size >= 3) {
    vtt = getVttData(data, null, size);
    if (!deepEqual(vtt, json)) {
      return fail("parsing file as binary utf8 with streaming, chunk size=" + size);
    }
  }

  // Bail now if we're only supposed to run utf8 based tests.
  if (utf8Only) {
    assert.ok(true, message);
    return;
  }

  // Finally, check it using a StringDecoder
  vtt = getVttData(String(fs.readFileSync(vttFilename)), WebVTTParser.StringDecoder());
  if (!deepEqual(vtt, json)) {
    return fail("parsing file as string without streaming");
  }

  assert.ok(true, message);
};

// Compare JSON to live parsed data using both UTF8 and String decoders
assert.jsonEqual = function(vttFilename, jsonFilename, message) {
  jsonEqual(vttFilename, jsonFilename, message);
};

// Compare JSON to live parsed data using only UTF8 decoder
assert.jsonEqualUTF8 = function(vttFilename, jsonFilename, message) {
  jsonEqual(vttFilename, jsonFilename, message, true);
};

// Shim TextEncoder and TextDecoder on the global if necessary so vtt.js
// can find them. See http://encoding.spec.whatwg.org/
var stringencoding = require("./stringencoding/encoding.js");
global.TextEncoder = global.TextEncoder || stringencoding.TextEncoder;
global.TextDecoder = global.TextDecoder || stringencoding.TextDecoder;

module.exports = {
  WebVTTParser: WebVTTParser,
  FakeWindow: FakeWindow,
  filterJson: filterJson,
  assert: assert,
  parse: parse
};
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from "react";

import LayoutPane from "../components/LayoutPane";
import LayoutHeader from "../components/LayoutHeader";
import LayoutContent from "../components/LayoutContent";
import LayoutMasterSplit from "../components/LayoutMasterSplit";
import InfiniteScroll from "../components/InfiniteScroll";
import FeedEntryList from "../components/FeedEntryList";
import FeedEntryGrid from "../components/FeedEntryGrid";
import FeedEntryContent from "../components/FeedEntryContent";
import NoMoreContent from "../components/NoMoreContent";

import EntryContentToolbar from "../components/EntryContentToolbar";
import EntryListToolbar from "../components/EntryListToolbar";

class MainAppViewLayout extends Component {

  static propTypes = {
    entries: PropTypes.shape({
      items: PropTypes.array.isRequired,
      isLoading: PropTypes.bool.isRequired,
      error: PropTypes.string
    }).isRequired,
    currentEntry: PropTypes.object,
    feedsActions: PropTypes.object.isRequired,
    entriesActions: PropTypes.object.isRequired,
    currentEntryActions: PropTypes.object.isRequired,
    modalsActions: PropTypes.object.isRequired,

    onNextEntryClick: PropTypes.func.isRequired,
    onPreviousEntryClick: PropTypes.func.isRequired,
    onOpenExternalClick: PropTypes.func.isRequired,
    onMarkAsReadClick: PropTypes.func.isRequired,
    onRefreshEntriesClick: PropTypes.func.isRequired,
    onRemoveFeedOrCategoryClick: PropTypes.func.isRequired,
    onLoadMore: PropTypes.func.isRequired
  };

  constructor(props) {
    super(props);

    this.state = {
      currentViewLayout: "list"
    };

    this.handleEntryShown = this.handleEntryShown.bind(this);
    this.handleSelectCurrentEntry = this.handleSelectCurrentEntry.bind(this);
    this.handleViewLayoutChange = this.handleViewLayoutChange.bind(this);
  }

  handleEntryShown(entry) {
    const { entriesActions, feedsActions } = this.props;
    entriesActions.requestMarkEntryAsRead(entry).then(() => {
      feedsActions.decrementUnreadCount({ id: entry.feed.id });
    });
  }

  handleSelectCurrentEntry(entry) {
    const { currentEntryActions, modalsActions } = this.props;
    const { currentViewLayout } = this.state;

    currentEntryActions.selectEntry({ entry: entry });
    if (currentViewLayout === "grid") {
      modalsActions.openEntryContentModal();
    }
  }

  handleViewLayoutChange(value) {
    this.setState({ currentViewLayout: value });
  }

  render() {
    const { currentViewLayout } = this.state;
    const { entries, currentEntry } = this.props;
    const { modalsActions } = this.props;
    const {
      onNextEntryClick,
      onPreviousEntryClick,
      onOpenExternalClick,
      onMarkAsReadClick,
      onRefreshEntriesClick,
      onRemoveFeedOrCategoryClick,
      onLoadMore
    } = this.props;

    const content = (
      <FeedEntryContent
        entry={currentEntry.entry}
        onEntryShown={this.handleEntryShown}/>
    );

    let items;
    if (currentViewLayout === "list") {
      items = (<FeedEntryList
        entries={entries.items}
        currentEntry={currentEntry.entry}
        onEntryClick={entry => this.handleSelectCurrentEntry(entry)}/>
      );
    } else if (currentViewLayout === "compact_list") {
      items = (<FeedEntryList
        entries={entries.items}
        currentEntry={currentEntry.entry}
        onEntryClick={entry => this.handleSelectCurrentEntry(entry)}
        className="compact"/>
      );
    } else if (currentViewLayout === "grid") {
      items = (<FeedEntryGrid
        entries={entries.items}
        currentEntry={currentEntry.entry}
        onEntryClick={entry => this.handleSelectCurrentEntry(entry)}/>
      );
    } else {
      throw Error(`Unknown currentViewLayout ${currentViewLayout}`);
    }

    const paginatedItems = (<InfiniteScroll
      threshold={300}
      loadMore={onLoadMore}
      hasMore={entries.hasMoreEntries}>
      {entries.items.length > 0 && items}
      {!entries.hasMoreEntries && <NoMoreContent/>}
    </InfiniteScroll>);

    const listHeader = (
      <LayoutHeader>
        <EntryListToolbar
          currentViewLayout={currentViewLayout}
          entries={entries}
          onMarkAsReadClick={onMarkAsReadClick}
          onRefreshEntriesClick={onRefreshEntriesClick}
          onRemoveFeedOrCategoryClick={onRemoveFeedOrCategoryClick}
          onViewLayoutChangeClick={this.handleViewLayoutChange}
          onOpenEditFeedOrCategoryModalClick={modalsActions.openEditFeedOrCategoryModal}
          onPreviousEntryClick={onPreviousEntryClick}
          onNextEntryClick={onNextEntryClick}
          onOpenExternalClick={onOpenExternalClick}
          onOpenEntryContentModalClick={modalsActions.openEntryContentModal}
          />

      </LayoutHeader>
    );

    const entryHeader = (
      <LayoutHeader>
        <EntryContentToolbar
          onPreviousEntryClick={onPreviousEntryClick}
          onNextEntryClick={onNextEntryClick}
          onOpenExternalClick={onOpenExternalClick}
          onOpenEntryContentModalClick={modalsActions.openEntryContentModal}
          />
      </LayoutHeader>
    );

    return (

        {currentViewLayout === "list" &&
          <LayoutMasterSplit>
            <LayoutPane size={30}>
              {listHeader}
              <LayoutContent>{paginatedItems}</LayoutContent>
            </LayoutPane>
            <LayoutPane size={70}>
              {entryHeader}
              <LayoutContent>{currentEntry.entry && content}</LayoutContent>
            </LayoutPane>
          </LayoutMasterSplit>
        }
        {currentViewLayout === "compact_list" &&
          <LayoutMasterSplit>
            <LayoutPane size={30}>
              {listHeader}
              <LayoutContent>{paginatedItems}</LayoutContent>
            </LayoutPane>
            <LayoutPane size={70}>
              {entryHeader}
              <LayoutContent>{currentEntry.entry && content}</LayoutContent>
            </LayoutPane>
          </LayoutMasterSplit>
        }
        {currentViewLayout === "grid" &&
          <LayoutMasterSplit>
            <LayoutPane size={100}>
              {listHeader}
              <LayoutContent>{paginatedItems}</LayoutContent>
            </LayoutPane>
          </LayoutMasterSplit>
        }

    );
  }
}

export default MainAppViewLayout;
<commit_msg>more app view layout refactoring<commit_after>import React, { Component, PropTypes } from "react";

import LayoutPane from "../components/LayoutPane";
import LayoutHeader from "../components/LayoutHeader";
import LayoutContent from "../components/LayoutContent";
import LayoutMasterSplit from "../components/LayoutMasterSplit";
import InfiniteScroll from "../components/InfiniteScroll";
import FeedEntryList from "../components/FeedEntryList";
import FeedEntryGrid from "../components/FeedEntryGrid";
import FeedEntryContent from "../components/FeedEntryContent";
import NoMoreContent from "../components/NoMoreContent";

import EntryContentToolbar from "../components/EntryContentToolbar";
import EntryListToolbar from "../components/EntryListToolbar";

class MainAppViewLayout extends Component {

  static propTypes = {
    entries: PropTypes.shape({
      items: PropTypes.array.isRequired,
      isLoading: PropTypes.bool.isRequired,
      error: PropTypes.string
    }).isRequired,
    currentEntry: PropTypes.object,
    feedsActions: PropTypes.object.isRequired,
    entriesActions: PropTypes.object.isRequired,
    currentEntryActions: PropTypes.object.isRequired,
    modalsActions: PropTypes.object.isRequired,

    onNextEntryClick: PropTypes.func.isRequired,
    onPreviousEntryClick: PropTypes.func.isRequired,
    onOpenExternalClick: PropTypes.func.isRequired,
    onMarkAsReadClick: PropTypes.func.isRequired,
    onRefreshEntriesClick: PropTypes.func.isRequired,
    onRemoveFeedOrCategoryClick: PropTypes.func.isRequired,
    onLoadMore: PropTypes.func.isRequired
  };

  constructor(props) {
    super(props);

    this.state = {
      currentViewLayout: "list"
    };

    this.handleEntryShown = this.handleEntryShown.bind(this);
    this.handleSelectCurrentEntry = this.handleSelectCurrentEntry.bind(this);
    this.handleViewLayoutChange = this.handleViewLayoutChange.bind(this);
  }

  handleEntryShown(entry) {
    const { entriesActions, feedsActions } = this.props;
    entriesActions.requestMarkEntryAsRead(entry).then(() => {
      feedsActions.decrementUnreadCount({ id: entry.feed.id });
    });
  }

  handleSelectCurrentEntry(entry) {
    const { currentEntryActions, modalsActions } = this.props;
    const { currentViewLayout } = this.state;

    currentEntryActions.selectEntry({ entry: entry });
    if (currentViewLayout === "grid") {
      modalsActions.openEntryContentModal();
    }
  }

  handleViewLayoutChange(value) {
    this.setState({ currentViewLayout: value });
  }

  render() {
    const { currentViewLayout } = this.state;
    const { entries, currentEntry } = this.props;
    const { modalsActions } = this.props;
    const {
      onNextEntryClick,
      onPreviousEntryClick,
      onOpenExternalClick,
      onMarkAsReadClick,
      onRefreshEntriesClick,
      onRemoveFeedOrCategoryClick,
      onLoadMore
    } = this.props;

    const content = (
      <FeedEntryContent
        entry={currentEntry.entry}
        onEntryShown={this.handleEntryShown}/>
    );

    let items;
    if (currentViewLayout === "list" || currentViewLayout === "compact_list") {
      items = (
        <FeedEntryList
          entries={entries.items}
          currentEntry={currentEntry.entry}
          onEntryClick={entry => this.handleSelectCurrentEntry(entry)}
          className={currentViewLayout === "compact_list" ? "compact" : "" }/>
      );
    } else if (currentViewLayout === "grid") {
      items = (
        <FeedEntryGrid
          entries={entries.items}
          currentEntry={currentEntry.entry}
          onEntryClick={entry => this.handleSelectCurrentEntry(entry)}/>
      );
    } else {
      throw Error(`Unknown currentViewLayout ${currentViewLayout}`);
    }

    const paginatedItems = (
      <InfiniteScroll
        threshold={300}
        loadMore={onLoadMore}
        hasMore={entries.hasMoreEntries}>

        {entries.items.length > 0 && items}
        {!entries.hasMoreEntries && <NoMoreContent/>}
      </InfiniteScroll>
    );

    const entryListToolbar= (
      <EntryListToolbar
        currentViewLayout={currentViewLayout}
        entries={entries}
        onMarkAsReadClick={onMarkAsReadClick}
        onRefreshEntriesClick={onRefreshEntriesClick}
        onRemoveFeedOrCategoryClick={onRemoveFeedOrCategoryClick}
        onViewLayoutChangeClick={this.handleViewLayoutChange}
        onOpenEditFeedOrCategoryModalClick={modalsActions.openEditFeedOrCategoryModal}
        onPreviousEntryClick={onPreviousEntryClick}
        onNextEntryClick={onNextEntryClick}
        onOpenExternalClick={onOpenExternalClick}
        onOpenEntryContentModalClick={modalsActions.openEntryContentModal}
        />
    );

    const entryContentToolbar = (
      <EntryContentToolbar
        onPreviousEntryClick={onPreviousEntryClick}
        onNextEntryClick={onNextEntryClick}
        onOpenExternalClick={onOpenExternalClick}
        onOpenEntryContentModalClick={modalsActions.openEntryContentModal}
        />
    );

    return (

        {currentViewLayout === "list" &&
          <LayoutMasterSplit>
            <LayoutPane size={30}>
              <LayoutHeader>{entryListToolbar}</LayoutHeader>
              <LayoutContent>{paginatedItems}</LayoutContent>
            </LayoutPane>
            <LayoutPane size={70}>
              <LayoutHeader>{entryContentToolbar}</LayoutHeader>
              <LayoutContent>{currentEntry.entry && content}</LayoutContent>
            </LayoutPane>
          </LayoutMasterSplit>
        }
        {currentViewLayout === "compact_list" &&
          <LayoutMasterSplit>
            <LayoutPane size={30}>
              <LayoutHeader>{entryListToolbar}</LayoutHeader>
              <LayoutContent>{paginatedItems}</LayoutContent>
            </LayoutPane>
            <LayoutPane size={70}>
              <LayoutHeader>{entryContentToolbar}</LayoutHeader>
              <LayoutContent>{currentEntry.entry && content}</LayoutContent>
            </LayoutPane>
          </LayoutMasterSplit>
        }
        {currentViewLayout === "grid" &&
          <LayoutMasterSplit>
            <LayoutPane size={100}>
              <LayoutHeader>{entryListToolbar}</LayoutHeader>
              <LayoutContent>{paginatedItems}</LayoutContent>
            </LayoutPane>
          </LayoutMasterSplit>
        }

    );
  }
}

export default MainAppViewLayout;
<|endoftext|>


<commit_before>const errorTemplate = require('./estemplate').error
const globalTypesObj = {}

const getType = (expr, expectedType, localTypeObj) => {
  if (expr !== undefined) {
    let type = exprType(expr, localTypeObj)
    return type === expectedType || expectedType === 'bool' ? type : null
  }
}

const checkTypes = (expr, expectedType, localTypeObj) => {
  let typeLeft = getType(expr.left, expectedType, localTypeObj)
  let typeRight = getType(expr.right, expectedType, localTypeObj)
  return typeLeft === typeRight && (typeLeft === expectedType || expectedType === 'bool') ? expectedType : null
}

const statementType = expr => {
  let type = expr.type
  switch (type) {
    case 'VariableDeclaration':
      declTypeExtract(expr)
      break
    default:
      return expr
  }
}

const exprType = (expr, localTypeObj = {}) => {
  let type = expr.type
  switch (type) {
    case 'Literal':
      return literalType(expr)
    case 'Identifier':
      return identifierType(expr, localTypeObj)
    case 'UnaryExpression':
      return unaryExprType(expr)
    case 'BinaryExpression':
      return binaryExprType(expr, localTypeObj)
    case 'CallExpression':
      return callExprType(expr, localTypeObj)
    case 'ConditionalExpression':
      return conditionalExprType(expr)
    case 'ArrowFunctionExpression':
      return arrowFunctionExprType(expr)
    case 'MemberExpression':
      return memberExprType(expr, localTypeObj)
    default:
      return expr
  }
}

const literalType = literal => literal.sType

const isFunction = (id, typeObj) => typeObj[id] !== undefined && typeObj[id].type === 'function'

const getParamReturnTypes = (id, typeObj) => [typeObj[id.name].paramTypes, typeObj[id.name].returnType]

const returnIdentifierType = (id, typeObj) => isFunction(id.name, typeObj) ? getParamReturnTypes(id, typeObj) : typeObj[id.name]

const identifierType = (id, localTypeObj) => (
    localTypeObj[id.name] !== undefined ? returnIdentifierType(id, localTypeObj)
    : globalTypesObj[id.name] !== undefined ? returnIdentifierType(id, globalTypesObj)
    : null)

const unaryExprType = expr => {
  if (expr.operator === 'typeof') return 'string'
  if (expr.operator === '!' && exprType(expr.argument) === 'bool') return 'bool'
  if (expr.operator === '-' && exprType(expr.argument) === 'number') return 'number'
  return null
}

const assignTypeToUninferredIdentifier = (inferredType, localTypeObj) => {
  if (isEmpty(localTypeObj)) return localTypeObj
  for (let identifier in localTypeObj) {
    if (localTypeObj[identifier] === 'needsInference') {
      localTypeObj[identifier] = inferredType
    }
  }
  return localTypeObj
}

const binaryExprType = (expr, localTypeObj) => {
  let assumedType = expr.sType
  assignTypeToUninferredIdentifier(assumedType, localTypeObj)
  return checkTypes(expr, assumedType, localTypeObj)
}

const conditionalExprType = expr => {
  if (exprType(expr.test) !== 'bool') return null
  return exprType(expr.consequent) === exprType(expr.alternate) ? exprType(expr.consequent) : null
}

const mapArgTypeToParams = (params, args) => {
  return params.map((param, index) => args[index].sType === undefined ? exprType(args[index]) : args[index].sType)
}

const isEmpty = obj => obj.toString() === ''

const makeParamTypeArray = (localTypeObj) => {
  let paramTypes = []
  for (let identifier in localTypeObj) {
    paramTypes.push(localTypeObj[identifier])
  }
  return paramTypes
}

const arrowFunctionExprType = expr => {
  let [params, body] = [expr.params, expr.body]
  let localTypeObj = {}
  params.map(param => { localTypeObj[param.name] = 'needsInference' })
  let returnType = exprType(body, localTypeObj)
  return {type: 'function', paramTypes: makeParamTypeArray(localTypeObj), returnType}
}

const matchArgTypesToAcceptedTypes = (args, acceptedTypes) => args
      .map((arg, index) => exprType(arg) === acceptedTypes[index])
      .reduce((type1, type2) => type1 === type2)

const callExprType = (expr, localTypeObj = {}) => {
  let [params, body, args] = [expr.callee.params, expr.callee.body, expr.arguments]
  if (params !== undefined && body !== undefined && !(isEmpty(localTypeObj))) {
    let typesOfParams = mapArgTypeToParams(params, args)
    params.map((param, index) => {
      localTypeObj[param.name] = typesOfParams[index]
      return param
    })
    return exprType(body, localTypeObj)
  }
  let [acceptedTypes, returnType] = exprType(expr.callee, localTypeObj)
  return matchArgTypesToAcceptedTypes(args, acceptedTypes) ? returnType : null
}

// TODO
// const memberExprType = (expr, localTypeObj) => {
//   let id = expr.object
//   return exprType(id, localTypeObj)
// }

const declTypeExtract = stmnt => {
  let [decl] = stmnt.declarations
  let [id, exp] = [decl.id, decl.init]
  let type = exprType(exp)
  globalTypesObj[id.name] = type
}

const types = body => {
  body.map(statementType)
  let error = null
  for (let decl in globalTypesObj) {
    if (globalTypesObj[decl] === null) {
      error = errorTemplate(`${decl} is not defined`)
      break
    }
  }
  return error !== null ? [error] : body
}

/*  Module Exports types  */
module.exports = types
<commit_msg>Add type inference for recursive functions<commit_after>const errorTemplate = require('./estemplate').error
const globalTypesObj = {}

const getType = (expr, expectedType, localTypeObj) => {
  if (expr !== undefined) {
    let type = exprType(expr, localTypeObj)
    return type === expectedType || expectedType === 'bool' ? type : null
  }
}

const checkTypes = (expr, expectedType, localTypeObj) => {
  let typeLeft = getType(expr.left, expectedType, localTypeObj)
  let typeRight = getType(expr.right, expectedType, localTypeObj)
  return typeLeft === typeRight && (typeLeft === expectedType || expectedType === 'bool') ? expectedType : null
}

const statementType = expr => {
  let type = expr.type
  switch (type) {
    case 'VariableDeclaration':
      declTypeExtract(expr)
      break
    default:
      return expr
  }
}

const exprType = (expr, localTypeObj = {}, id = null) => {
  let type = expr.type
  switch (type) {
    case 'Literal':
      return literalType(expr)
    case 'Identifier':
      return identifierType(expr, localTypeObj)
    case 'UnaryExpression':
      return unaryExprType(expr)
    case 'BinaryExpression':
      return binaryExprType(expr, localTypeObj)
    case 'CallExpression':
      return callExprType(expr, localTypeObj)
    case 'ConditionalExpression':
      return conditionalExprType(expr)
    case 'ArrowFunctionExpression':
      return arrowFunctionExprType(expr, localTypeObj, id)
    case 'MemberExpression':
      return memberExprType(expr, localTypeObj)
    case 'BlockStatement':
      return blockStatementType(expr, localTypeObj, id)
    case 'SwitchStatement':
      return switchStatementType(expr, localTypeObj, id)
    default:
      return expr
  }
}

const literalType = literal => literal.sType

const isFunction = (id, typeObj) => typeObj[id] !== undefined && typeObj[id].type === 'function'

const getParamReturnTypes = (id, typeObj) => [typeObj[id.name].paramTypes, typeObj[id.name].returnType]

const returnIdentifierType = (id, typeObj) => isFunction(id.name, typeObj) ? getParamReturnTypes(id, typeObj) : typeObj[id.name]

const identifierType = (id, localTypeObj) => (
    localTypeObj[id.name] !== undefined ? returnIdentifierType(id, localTypeObj)
    : globalTypesObj[id.name] !== undefined ? returnIdentifierType(id, globalTypesObj)
    : null)

const unaryExprType = expr => {
  if (expr.operator === 'typeof') return 'string'
  if (expr.operator === '!' && exprType(expr.argument) === 'bool') return 'bool'
  if (expr.operator === '-' && exprType(expr.argument) === 'number') return 'number'
  return null
}

const assignTypeToUninferredIdentifier = (inferredType, localTypeObj) => {
  if (isEmpty(localTypeObj)) return localTypeObj
  for (let identifier in localTypeObj) {
    if (localTypeObj[identifier] === 'needsInference') {
      localTypeObj[identifier] = inferredType
    }
  }
  return localTypeObj
}

const binaryExprType = (expr, localTypeObj) => {
  let assumedType = expr.sType
  assignTypeToUninferredIdentifier(assumedType, localTypeObj)
  return checkTypes(expr, assumedType, localTypeObj)
}

const conditionalExprType = expr => {
  if (exprType(expr.test) !== 'bool') return null
  return exprType(expr.consequent) === exprType(expr.alternate) ? exprType(expr.consequent) : null
}

const mapArgTypeToParams = (params, args) => {
  return params.map((param, index) => args[index].sType === undefined ? exprType(args[index]) : args[index].sType)
}

const isEmpty = obj => obj.toString() === ''

const makeParamTypeArray = (localTypeObj) => {
  let paramTypes = []
  for (let identifier in localTypeObj) {
    paramTypes.push(localTypeObj[identifier])
  }
  return paramTypes
}

const arrowFunctionExprType = (expr, localTypeObj, id) => {
  let [params, body] = [expr.params, expr.body]
  params.map(param => { localTypeObj[param.name] = 'needsInference' })
  let returnType = exprType(body, localTypeObj, id)
  return returnType === null ? null : {type: 'function', paramTypes: makeParamTypeArray(localTypeObj), returnType}
}

const matchArgTypesToAcceptedTypes = (args, acceptedTypes, localTypeObj) => args.map(
  (arg, index) => exprType(arg, localTypeObj) === acceptedTypes[index])
  .reduce((type1, type2) => type1 === type2)

const callExprType = (expr, localTypeObj = {}) => {
  let [params, body, args] = [expr.callee.params, expr.callee.body, expr.arguments]

  if (params !== undefined && body !== undefined && !(isEmpty(localTypeObj))) {
    let typesOfParams = mapArgTypeToParams(params, args)
    params.map((param, index) => {
      localTypeObj[param.name] = typesOfParams[index]
      return param
    })
    return exprType(body, localTypeObj)
  }

  let [acceptedTypes, returnType] = exprType(expr.callee, localTypeObj)
  return matchArgTypesToAcceptedTypes(args, acceptedTypes, localTypeObj) ? returnType : null
}
// TODO
// const memberExprType = (expr, localTypeObj) => {
//   return expr
// }

const reduceTypes = (typesArr, localTypeObj) => (
  typesArr.map(e => exprType(e, localTypeObj))
          .reduce((type1, type2) => type1 === type2 ? type1 : false))

const switchStatementType = (body, localTypeObj, id) => {
  let cases = body.cases
  let [initialPattern] = cases

  let [returnType, acceptedType] = [
    initialPattern.consequent[0].argument,
    initialPattern.test].map(e => exprType(e, localTypeObj))

  let paramId = body.discriminant.name

  localTypeObj[paramId] = acceptedType
  globalTypesObj[id] = {type: 'function', paramTypes: makeParamTypeArray(localTypeObj), returnType}
  let [caseArgArray, caseTestArray] = [
    cases.map(c => c.consequent[0].argument),
    cases.map(c => c.test === null ? {type: 'Identifier', name: paramId, sType: acceptedType} : c.test)]

  if (reduceTypes(caseArgArray, localTypeObj) === false || reduceTypes(caseTestArray, localTypeObj) === false) return null
  return returnType
}

const blockStatementType = (stmnt, localTypeObj, id) => {
  let [expr] = stmnt.body
  return exprType(expr, localTypeObj, id)
}

const declTypeExtract = stmnt => {
  let [decl] = stmnt.declarations
  let [id, exp] = [decl.id.name, decl.init]
  let type = exprType(exp, {}, id)
  globalTypesObj[id] = type
}

const types = body => {
  body.map(statementType)
  let error = null
  for (let decl in globalTypesObj) {
    if (globalTypesObj[decl] === null) {
      error = errorTemplate(`${decl} is not defined`)
      break
    }
  }
  return error !== null ? [error] : body
}

/*  Module Exports types  */
module.exports = types
<|endoftext|>


<commit_before>/*
 * index.js: Top level module include for utils module.
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var crypto = require('crypto'),
    fs = require('fs'),
    http = require('http'),
    os = require('os'),
    path = require('path'),
    spawn = require('child_process').spawn,
    flatiron = require('flatiron'),
    async = flatiron.common.async,
    rimraf = flatiron.common.rimraf,
    haibu = require('../../haibu');

var common = module.exports = flatiron.common;

//
// **REALLY DONT DO THIS HERE! But where?**
//
if (!Error.prototype.toJSON) {
  Object.defineProperty(Error.prototype, "toJSON", {
    enumerable: false,
    value: function () {
      return flatiron.common.mixin({
        message: this.message,
        stack: this.stack,
        arguments: this.arguments
      }, flatiron.common.clone(this));
    }
  });
}

//
// ### Include Exports
// Export additional common components
//
var npm = common.npm = require('./npm');
var brunch = common.brunch = require('./brunch');
var log = common.log = require('./log');

common.welcome = [
  '    ______  ______  ______  _    _      '.yellow,
  '   / ____/ / __  / /___  / //___//       '.yellow,
  '  / /     / / / /   ___//  \\_  _/  '.yellow,
  ' / /___  / /_/ / //____     / /   '.yellow,
  '/_____/ /_____/ /_____/    /_/ '.yellow,
  '  ',
  '  The Cozy Controller is Open Source Software available under'.grey,
  '  the MIT License.'.grey,
  '  ',
  ('  © ' + (new Date()).getFullYear() + ' Cozy Cloud').grey,
  ''
];

//
// ### function showWelcome (mode, ipAddress, port)
// #### @mode {string} The mode that haibu is currently running in.
// #### @ipAddress {string} The IP Address / host that haibu is binding to.
// #### @port {int} The port that haibu is binding to.
// Prints the signature `haibu` welcome message using the colors module.
//
common.showWelcome = function (role, ipAddress, port) {
  var plugins = Object.keys(haibu.plugins),
      serverMsg;

  serverMsg = [
    'haibu'.yellow.bold,
    'started @'.grey,
    (ipAddress || '127.0.0.1').green.bold,
    'on port'.grey,
    port.toString().green.bold,
    'as'.grey,
    role.green.bold
  ].join(' ');

  console.log(common.welcome.join('\n'));
  console.log(' ' + serverMsg);

  //
  // If there are any active plugins then
  // indicate this via logged messages
  //
  if (plugins.length > 0) {
    plugins = plugins.map(function (p) { return p.yellow.bold }).join(', '.grey);
    console.log('    using plugins: '.grey + plugins);
  }

  dirApp = haibu.config.get('directories:apps');
  per = haibu.config.get('permissions');
  if (per != undefined) {
    console.log('    Application directories : '.grey + dirApp + ' with permissions : '.grey + per + '\n');
  } else {
    console.log('    Application directories : '.grey + dirApp);
  }
  console.log('    Run in '.grey + process.env.NODE_ENV + " environment\n".grey);
  datetime = new Date();
  console.log("[" + datetime + "]")
  console.log('GET READY \n'.green);
};

//
// ### function getEndKey (startKey)
// #### @startKey {string} Startkey paramater for querying CouchDB.
// Returns the 'endkey' associated with the `startKey`, that is,
// the same string except with the last character alphabetically incremented.
//
// e.g. `char ==> chas`
//
common.getEndKey = function (startKey) {
  var length = startKey.length;
  return startKey.slice(0, length - 1) + String.fromCharCode(startKey.charCodeAt(length - 1) + 1);
};

//
// ### function rmApp (appsDir, app, callback)
// #### @appsDir {string} Root for all application source files.
// #### @app {App} Application to remove directories for.
// #### @callback {function} Continuation passed to respond to.
// Removes all source code associated with the specified `app`.
//
common.rmApp = function (appsDir, app, callback) {
  return rimraf(path.join(appsDir, app.user, app.name), callback);
};

//
// ### function rmApps (appsDir, callback)
// #### @appsDir {string} Root for all application source files.
// #### @callback {function} Continuation passed to respond to.
// Removes all source code associated with all users and all applications
// from this Haibu process.
//
common.rmApps = function (appsDir, callback) {
  if (!callback && typeof appsDir === 'function') {
    callback = appsDir;
    appsDir = null;
  }

  appsDir = appsDir || haibu.config.get('directories:apps');
  fs.readdir(appsDir, function (err, users) {
    if (err) {
      return callback(err);
    }

    async.forEach(users, function rmUser (user, next) {
      rimraf(path.join(appsDir, user), next);
    }, callback);
  });
};

//
// ### sanitizeAppname (name)
// Returns sanitized appname (with removed characters) concatenated with
// original name's hash
//
common.sanitizeAppname = function (name) {
  var sha1 = crypto.createHash('sha1');

  sha1.update(name);
  return name.replace(/[^a-z0-9\-\_]+/g, '-') + '-' + sha1.digest('hex');
};

//
// ### function ipAddress (name)
// #### @name {string} **Optional** Name of the network interface
// Returns the address for the network interface on the current
// system with the specified `name`. If no interface or `IPv4`
// family is found return the loopback addres `127.0.0.1`.
//
common.ipAddress = function (name) {
  var interfaces = os.networkInterfaces();

  var addresses = Object.keys(interfaces).map(function (nic) {
    var addrs = interfaces[nic].filter(function (details) {
      return details.address !== '127.0.0.1' && details.family === 'IPv4'
    });
    return addrs.length ? addrs[0].address : undefined;
  }).filter(Boolean);

  return addresses.length
    ? addresses[0]
    : '127.0.0.1';
};
<commit_msg>remove all application repository after an uninstallation<commit_after>/*
 * index.js: Top level module include for utils module.
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var crypto = require('crypto'),
    fs = require('fs'),
    http = require('http'),
    os = require('os'),
    path = require('path'),
    spawn = require('child_process').spawn,
    flatiron = require('flatiron'),
    async = flatiron.common.async,
    rimraf = flatiron.common.rimraf,
    haibu = require('../../haibu');

var common = module.exports = flatiron.common;

//
// **REALLY DONT DO THIS HERE! But where?**
//
if (!Error.prototype.toJSON) {
  Object.defineProperty(Error.prototype, "toJSON", {
    enumerable: false,
    value: function () {
      return flatiron.common.mixin({
        message: this.message,
        stack: this.stack,
        arguments: this.arguments
      }, flatiron.common.clone(this));
    }
  });
}

//
// ### Include Exports
// Export additional common components
//
var npm = common.npm = require('./npm');
var brunch = common.brunch = require('./brunch');
var log = common.log = require('./log');

common.welcome = [
  '    ______  ______  ______  _    _      '.yellow,
  '   / ____/ / __  / /___  / //___//       '.yellow,
  '  / /     / / / /   ___//  \\_  _/  '.yellow,
  ' / /___  / /_/ / //____     / /   '.yellow,
  '/_____/ /_____/ /_____/    /_/ '.yellow,
  '  ',
  '  The Cozy Controller is Open Source Software available under'.grey,
  '  the MIT License.'.grey,
  '  ',
  ('  © ' + (new Date()).getFullYear() + ' Cozy Cloud').grey,
  ''
];

//
// ### function showWelcome (mode, ipAddress, port)
// #### @mode {string} The mode that haibu is currently running in.
// #### @ipAddress {string} The IP Address / host that haibu is binding to.
// #### @port {int} The port that haibu is binding to.
// Prints the signature `haibu` welcome message using the colors module.
//
common.showWelcome = function (role, ipAddress, port) {
  var plugins = Object.keys(haibu.plugins),
      serverMsg;

  serverMsg = [
    'haibu'.yellow.bold,
    'started @'.grey,
    (ipAddress || '127.0.0.1').green.bold,
    'on port'.grey,
    port.toString().green.bold,
    'as'.grey,
    role.green.bold
  ].join(' ');

  console.log(common.welcome.join('\n'));
  console.log(' ' + serverMsg);

  //
  // If there are any active plugins then
  // indicate this via logged messages
  //
  if (plugins.length > 0) {
    plugins = plugins.map(function (p) { return p.yellow.bold }).join(', '.grey);
    console.log('    using plugins: '.grey + plugins);
  }

  dirApp = haibu.config.get('directories:apps');
  per = haibu.config.get('permissions');
  if (per != undefined) {
    console.log('    Application directories : '.grey + dirApp + ' with permissions : '.grey + per + '\n');
  } else {
    console.log('    Application directories : '.grey + dirApp);
  }
  console.log('    Run in '.grey + process.env.NODE_ENV + " environment\n".grey);
  datetime = new Date();
  console.log("[" + datetime + "]")
  console.log('GET READY \n'.green);
};

//
// ### function getEndKey (startKey)
// #### @startKey {string} Startkey paramater for querying CouchDB.
// Returns the 'endkey' associated with the `startKey`, that is,
// the same string except with the last character alphabetically incremented.
//
// e.g. `char ==> chas`
//
common.getEndKey = function (startKey) {
  var length = startKey.length;
  return startKey.slice(0, length - 1) + String.fromCharCode(startKey.charCodeAt(length - 1) + 1);
};

//
// ### function rmApp (appsDir, app, callback)
// #### @appsDir {string} Root for all application source files.
// #### @app {App} Application to remove directories for.
// #### @callback {function} Continuation passed to respond to.
// Removes all source code associated with the specified `app`.
//
common.rmApp = function (appsDir, app, callback) {
  return rimraf(path.join(appsDir, app.user), callback);
};

//
// ### function rmApps (appsDir, callback)
// #### @appsDir {string} Root for all application source files.
// #### @callback {function} Continuation passed to respond to.
// Removes all source code associated with all users and all applications
// from this Haibu process.
//
common.rmApps = function (appsDir, callback) {
  if (!callback && typeof appsDir === 'function') {
    callback = appsDir;
    appsDir = null;
  }

  appsDir = appsDir || haibu.config.get('directories:apps');
  fs.readdir(appsDir, function (err, users) {
    if (err) {
      return callback(err);
    }

    async.forEach(users, function rmUser (user, next) {
      rimraf(path.join(appsDir, user), next);
    }, callback);
  });
};

//
// ### sanitizeAppname (name)
// Returns sanitized appname (with removed characters) concatenated with
// original name's hash
//
common.sanitizeAppname = function (name) {
  var sha1 = crypto.createHash('sha1');

  sha1.update(name);
  return name.replace(/[^a-z0-9\-\_]+/g, '-') + '-' + sha1.digest('hex');
};

//
// ### function ipAddress (name)
// #### @name {string} **Optional** Name of the network interface
// Returns the address for the network interface on the current
// system with the specified `name`. If no interface or `IPv4`
// family is found return the loopback addres `127.0.0.1`.
//
common.ipAddress = function (name) {
  var interfaces = os.networkInterfaces();

  var addresses = Object.keys(interfaces).map(function (nic) {
    var addrs = interfaces[nic].filter(function (details) {
      return details.address !== '127.0.0.1' && details.family === 'IPv4'
    });
    return addrs.length ? addrs[0].address : undefined;
  }).filter(Boolean);

  return addresses.length
    ? addresses[0]
    : '127.0.0.1';
};
<|endoftext|>


<commit_before>// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

base.require('tracing.trace_model.object_instance');
base.require('cc.layer_impl');

base.exportTo('cc', function() {
  var ObjectSnapshot = tracing.trace_model.ObjectSnapshot;

  /**
   * @constructor
   */
  function LayerTreeImplSnapshot() {
    ObjectSnapshot.apply(this, arguments);
  }

  LayerTreeImplSnapshot.prototype = {
    __proto__: ObjectSnapshot.prototype,

    preInitialize: function() {
      cc.preInitializeObject(this);
      this.layerTreeHostImpl = undefined;
      this.whichTree = undefined;
    },

    initialize: function() {
      cc.moveRequiredFieldsFromArgsToToplevel(
          this, ['rootLayer',
            'renderSurfaceLayerList']);
      this.rootLayer.layerTreeImpl = this;
    },

    iterLayers: function(func, thisArg) {
      var visitedLayers = {};
      function visitLayer(layer, depth, isMask, isReplica) {
        if (visitedLayers[layer.layerId])
          return;
        visitedLayers[layer.layerId] = true;
        func.call(thisArg, layer, depth, isMask, isReplica);
        for (var i = 0; i < layer.children.length; i++)
          visitLayer(layer.children[i], depth + 1);
        if (layer.maskLayer)
          visitLayer(layer, depth + 1, true, false);
        if (layer.replicaLayer)
          visitLayer(layer, depth + 1, false, true);
      }
      visitLayer(this.rootLayer, 0, false, false);
    },
    findLayerWithId: function(id) {
      var foundLayer = undefined;
      function visitLayer(layer) {
        if (layer.layerId == id)
          foundLayer = layer;
      }
      this.iterLayers(visitLayer);
      return foundLayer;
    }
  };

  ObjectSnapshot.register('cc::LayerTreeImpl', LayerTreeImplSnapshot);

  return {
    LayerTreeImplSnapshot: LayerTreeImplSnapshot
  };
});
<commit_msg>Include mask layers when iterating the tree<commit_after>// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

base.require('tracing.trace_model.object_instance');
base.require('cc.layer_impl');

base.exportTo('cc', function() {
  var ObjectSnapshot = tracing.trace_model.ObjectSnapshot;

  /**
   * @constructor
   */
  function LayerTreeImplSnapshot() {
    ObjectSnapshot.apply(this, arguments);
  }

  LayerTreeImplSnapshot.prototype = {
    __proto__: ObjectSnapshot.prototype,

    preInitialize: function() {
      cc.preInitializeObject(this);
      this.layerTreeHostImpl = undefined;
      this.whichTree = undefined;
    },

    initialize: function() {
      cc.moveRequiredFieldsFromArgsToToplevel(
          this, ['rootLayer',
            'renderSurfaceLayerList']);
      this.rootLayer.layerTreeImpl = this;
    },

    iterLayers: function(func, thisArg) {
      var visitedLayers = {};
      function visitLayer(layer, depth, isMask, isReplica) {
        if (visitedLayers[layer.layerId])
          return;
        visitedLayers[layer.layerId] = true;
        func.call(thisArg, layer, depth, isMask, isReplica);
        for (var i = 0; i < layer.children.length; i++)
          visitLayer(layer.children[i], depth + 1);
        if (layer.maskLayer)
          visitLayer(layer.maskLayer, depth + 1, true, false);
        if (layer.replicaLayer)
          visitLayer(layer.replicaLayer, depth + 1, false, true);
      }
      visitLayer(this.rootLayer, 0, false, false);
    },
    findLayerWithId: function(id) {
      var foundLayer = undefined;
      function visitLayer(layer) {
        if (layer.layerId == id)
          foundLayer = layer;
      }
      this.iterLayers(visitLayer);
      return foundLayer;
    }
  };

  ObjectSnapshot.register('cc::LayerTreeImpl', LayerTreeImplSnapshot);

  return {
    LayerTreeImplSnapshot: LayerTreeImplSnapshot
  };
});
<|endoftext|>