<commit_before>const botVersion = require('../package.json').version;
const snekfetch  = require('snekfetch');
const moment     = require('moment');
const exec       = require('child_process').exec;
const time       = require('time-parser');
const fs         = require('fs');
const os         = require('os');
require('moment-duration-format');

exports.run = async function (msg) {
    if (isCommand(msg, 'ping'))
        msg.channel.send(`:ping_pong: Pong! ${client.pings[0]}ms`);

    if (isCommand(msg, ['reboot', 'restart'])) {
        if (msg.author.id !== settings.ownerID)
            return;
        await msg.channel.send('Restarting...');
        await client.destroy();
        process.exit();
    }

    if (isCommand(msg, 'invite'))
        msg.channel.send({
            embed: new Discord.RichEmbed()
                .setColor(settings.embedColor)
                .setDescription(`Click [here](https://discordapp.com/oauth2/authorize?permissions=27648&scope=bot&client_id=${client.user.id}) to invite me to your server, or click [here](https://discord.gg/Yphr6WG) for an invite to RemindMeBot\'s support server.`)
        });

    if (isCommand(msg, ['stats', 'info'])) {
        const embed = new Discord.RichEmbed()
            .setColor(settings.embedColor)
            .setTitle(`RemindMeBot ${botVersion}`)
            .setURL('https://discordbots.org/bot/290947970457796608')
            .addField('Guilds', client.guilds.size, true)
            .addField('Uptime', moment.duration(process.uptime(), 'seconds').format('dd:hh:mm:ss'), true)
            .addField('Ping', `${client.ping.toFixed(0)} ms`, true)
            .addField('RAM Usage', `${(process.memoryUsage().rss / 1048576).toFixed()}MB/${(os.totalmem() > 1073741824 ? `${(os.totalmem() / 1073741824).toFixed(1)} GB` : `${(os.totalmem() / 1048576).toFixed()} MB`)}
(${(process.memoryUsage().rss / os.totalmem() * 100).toFixed(2)}%)`, true)
            .addField('System Info', `${process.platform} (${process.arch})\n${(os.totalmem() > 1073741824 ? `${(os.totalmem() / 1073741824).toFixed(1)} GB` : `${(os.totalmem() / 1048576).toFixed(2)} MB`)}`, true)
            .addField('Libraries', `[Discord.js](https://discord.js.org) v${Discord.version}\nNode.js ${process.version}`, true)
            .addField('Links', '[Bot invite](https://discordapp.com/oauth2/authorize?permissions=27648&scope=bot&client_id=290947970457796608) | [Support server invite](https://discord.gg/Yphr6WG) | [GitHub](https://github.com/Aetheryx/remindme)', true)
            .setFooter('Created by Aetheryx#2222');

        msg.channel.send({ embed });
    }

    if (isCommand(msg, 'help'))
        msg.channel.send(`To set a reminder, simply send \`${settings.defaultPrefix}remindme\` and follow the instructions. Alternatively, you can also send \`${settings.defaultPrefix}remindme time_argument "message"\`, \ne.g. \`${settings.defaultPrefix}remindme 31 December 2017 "New Years"\`.\nMy prefix is \`${settings.defaultPrefix}\`; here's a list of my commands: `, {
            embed: new Discord.RichEmbed()
                .setColor(settings.embedColor)
                .setDescription('remindme, list, clear, info, ping, help, invite, forget'.split(', ').sort().join(', '))
        });

    if (isCommand(msg, ['reminders', 'list'])) {
        if (!db[msg.author.id] || db[msg.author.id].length === 0)
            return msg.reply('You have no reminders set!');
        client.users.get(msg.author.id).send({
            embed: new Discord.RichEmbed()
                .setColor(settings.embedColor)
                .addField(`Current reminder${plural(db[msg.author.id])}:`, db[msg.author.id].map((r) => r.reminder).join('\n'))
                .setFooter(`Reminder${plural(db[msg.author.id])} set to expire in(dd:hh:mm:ss): ${db[msg.author.id].map((b) => moment.duration(b.when - Date.now(), 'milliseconds').format('dd:hh:mm:ss')).join(', ')}`)
        });
    }

    if (isCommand(msg, 'clear')) {
        if (!db[msg.author.id] || db[msg.author.id].length === 0)
            return msg.reply('You have no reminders set!');

        msg.channel.send(':warning: This will delete all of your reminders! Are you sure? (`y`/`n`)');

        const collector = msg.channel.createMessageCollector(m => msg.author.id === m.author.id, { time: 40000 });

        collector.on('collect', (m) => {
            if (m.content.toLowerCase() === 'y' || m.content.toLowerCase() === 'yes') {
                db[msg.author.id] = [];
                fs.writeFile('./storage/reminders.json', JSON.stringify(db, '', '\t'), (err) => {
                    if (err)
                        return msg.channel.send(`Your reminders weren't cleared.\n${err.message}`);
                    msg.channel.send(':ballot_box_with_check: Reminders cleared.');
                });
            } else {
                msg.channel.send(':ballot_box_with_check: Cancelled.');
            }
            return collector.stop();
        });

        collector.on('end', (collected, reason) => {
            if (reason === 'time')
                msg.channel.send('Prompt timed out.');
        });
    }

    if (isCommand(msg, ['forget', 'delete'])) { // Very beta, kind of unstable.
        if (!db[msg.author.id] || db[msg.author.id].length === 0)
            return msg.reply('You have no reminders set!');

        msg.channel.send('Here\'s a list of your current reminders: ', {
            embed: new Discord.RichEmbed()
                .setColor(settings.embedColor)
                .setTitle('Reminders')
                .setDescription(Object.keys(db[msg.author.id]).map((e, i) => `[${i + 1}] ${db[msg.author.id][e].reminder}`).join('\n'))
                .setFooter('Send the number of the reminder you want me to forget(e.g. 3), or send c to cancel.')
        });
        const collector = msg.channel.createMessageCollector((m) => msg.author.id === m.author.id, { time: 40000 });
        collector.on('collect', (m) => {
            if (m.content.toLowerCase().startsWith(`${settings.defaultPrefix}forget`) || m.content.toLowerCase() === 'cancel' || m.content.toLowerCase() === 'c')
                return collector.stop();

            if (isNaN(m.content))
                return msg.channel.send('Argument entered is not a number. Send the number of the reminder you want me to forget (e.g. `3`), or send `c` to cancel.');

            if (parseInt(m.content) > Object.keys(db[msg.author.id]).length)
                return msg.channel.send('You don\'t have that many reminders, please choose a lower number.');

            const reminder = db[msg.author.id][parseInt(m.content) - 1];
            db[msg.author.id] = db[msg.author.id].filter((x) => x.reminder !== db[msg.author.id][parseInt(m.content) - 1].reminder);

            fs.writeFile('./storage/reminders.json', JSON.stringify(db, '', '\t'), (err) => {
                if (err) return msg.channel.send(`Your reminder wasn't removed.\n${err.message}`);
            });

            msg.channel.send(`Reminder \`${reminder.reminder}\` deleted.`);
            return collector.stop();
        });
    }

    if (isCommand(msg, 'remindme') && !args[0]) {
        msg.channel.send('What would you like the reminder to be? (You can send `cancel` at any time to cancel creation.)');

        const collector = msg.channel.createMessageCollector((m) => msg.author.id === m.author.id, { time: 40000 });

        let step = 1;

        const dboption = {
            'reminder': undefined,
            'when': undefined,
            'made': msg.createdTimestamp
        };

        collector.on('collect', (m) => {
            if (m.content.toLowerCase() === `${settings.defaultPrefix}remindme` || m.content.toLowerCase() === 'cancel')
                return collector.stop();

            if (step === 1) {
                if (m.content.length === 0)
                    return msg.channel.send('The reminder cannot be empty.\nWhat would you like the reminder to be?');

                dboption.reminder = m.content;

                msg.channel.send('When would you like to be reminded? (e.g. 24 hours)');
            }

            if (step === 2) {
                let tParse = time(m.content).absolute;

                if (m.content.includes('next'))
                    tParse = time(m.content.replace(/next/g, 'one')).absolute;

                if (m.content.startsWith('a ') || m.content.startsWith('an '))
                    tParse = time(m.content.replace(/a /g, 'one ').replace(/an /g, 'one ')).absolute;

                if (m.content.includes(' min'))
                    tParse = time(m.content.replace(/ min/g, 'minutes ')).absolute;

                if (!isNaN(m.content) || !tParse)
                    return msg.channel.send('Invalid time.\nWhen would you like to be reminded? (e.g. 24 hours)');

                if (time(m.content).relative < 0) {
                    collector.stop();
                    return msg.channel.send('Your reminder wasn\'t added because it was set for the past. Note that if you\'re trying to set a reminder for the same day at a specific time (e.g. `6 PM`), UTC time will be assumed.');
                }

                collector.stop();
                dboption.when = tParse;
                if (!db[msg.author.id])
                    db[msg.author.id] = [];

                db[msg.author.id].push(dboption);
                fs.writeFile('./storage/reminders.json', JSON.stringify(db, '', '\t'), (err) => {
                    if (err) return msg.channel.send(`Your reminder wasn't added.\n${err.message}`);
                    msg.channel.send({
                        embed: new Discord.RichEmbed()
                            .setColor(settings.embedColor)
                            .setDescription(`:ballot_box_with_check: Reminder added: ${dboption.reminder}`)
                            .setFooter('Reminder set for ')
                            .setTimestamp(new Date(tParse))
                    });
                });
            }
            step++;
        });
        collector.on('end', (collected, reason) => {
            if (reason === 'time')
                msg.channel.send('Prompt timed out.');
        });
    }

    const cmd = msg.content.toLowerCase().substring(settings.defaultPrefix.length).split(' ')[0];
    const args = msg.content.split(' ').slice(1);

    if (cmd === 'ev') {
        if (msg.author.id !== settings.ownerID)
            return false;
        let res = args.join(' ');
        const silent = res.includes('--silent') ? true : false;
        const asynchr = res.includes('--async') ? true : false;
        if (silent || asynchr) res = res.replace('--silent', '').replace('--async', '');

        try {
            res = asynchr ? eval(`(async()=>{${res}})();`) : eval(res);
            if (res instanceof Promise && asynchr) code = await res;
            if (typeof code !== 'string')
                res = require('util').inspect(res, { depth: 0 });
            res = res.replace(new RegExp(client.token, 'gi'), 'fite me irl');
        } catch (e) {
            res = e;
        }
        if (!silent)
            msg.channel.send(res, { code: 'js' });
    }

    if (cmd === 'exec') {
        if (msg.author.id !== settings.ownerID)
            return false;

        exec(args.join(' '), async (e, stdout, stderr) => {
            if (stdout.length > 2000 || stderr.length > 2000) {
                const res = await snekfetch.post('https://hastebin.com/documents')
                    .send(`${stdout}\n\n${stderr}`)
                    .catch((e) => msg.channel.send(e.message));

                msg.channel.send({
                    embed: new Discord.RichEmbed()
                        .setColor(settings.embedColor)
                        .setDescription(`Console log exceeds 2000 characters. View [here](https://hastebin.com/${res.body.key}).`)
                });
            } else {
                stdout && msg.channel.send(`Info: \n\`\`\`${stdout}\`\`\``);
                stderr && msg.channel.send(`Errors: \n\`\`\`${stderr}\`\`\``);
                if (!stderr && !stdout)
                    msg.react('\u2611');
            }
        });
    }

    if (cmd === 'prefix')
        return msg.channel.send('Custom prefixes aren\'t supported in DMs!');

    if (cmd === 'remindme' && msg.content.length > settings.defaultPrefix.length + 10) {
        if (!msg.content.includes('"'))
            return msg.channel.send(`Argument error. Please follow the proper syntax for the command:\n\`${settings.defaultPrefix}remindme time_argument "message"\`, e.g. \`${settings.defaultPrefix}remindme 31 December 2017 "New Years"\``);

        const timeArg = msg.content.substring(settings.defaultPrefix.length + 9, msg.content.indexOf('"') - 1);
        let tParse = time(timeArg).absolute;

        if (timeArg.includes('next'))
            tParse = time(timeArg.replace(/next/g, 'one')).absolute;

        if (timeArg.startsWith('a ') || timeArg.startsWith('an '))
            tParse = time(timeArg.replace(/a /g, 'one ').replace(/an /g, 'one ')).absolute;

        if (!isNaN(timeArg) || !tParse)
            return msg.channel.send('Invalid time argument. Please enter a proper time argument, e.g. `12 hours` or `next week`.');

        if (time(timeArg).relative < 0)
            return msg.channel.send('Your reminder wasn\'t added because it was set for the past. Note that if you\'re trying to set a reminder for the same day at a specific time (e.g. `6 PM`), UTC time will be assumed.');

        const reminder = msg.content.substring(msg.content.indexOf('"') + 1, msg.content.length - 1),
            dboption = {
                'reminder': reminder,
                'when': tParse,
                'made': msg.createdTimestamp
            };

        if (!db[msg.author.id])
            db[msg.author.id] = [];

        db[msg.author.id].push(dboption);
        fs.writeFile('./storage/reminders.json', JSON.stringify(db, '', '\t'), (err) => {
            if (err) return msg.channel.send(`Your reminder wasn't added.\n${err.message}`);
            msg.channel.send({
                embed: new Discord.RichEmbed()
                    .setColor(settings.embedColor)
                    .setDescription(`:ballot_box_with_check: Reminder added: ${dboption.reminder}`)
                    .setFooter('Reminder set for ')
                    .setTimestamp(new Date(tParse))
            });
        });
    }
};

function plural (x) {
    return x.length > 1 ? 's' : '';
}

function isCommand (msg, x) {
    const cmd = msg.content.toLowerCase().substring(prefixdb[msg.guild.id].length).split(' ')[0];
    if (!Array.isArray(x))
        x = [x];
    return x.includes(cmd) || msg.isMentioned(client.user.id) && x.some((c) => msg.content.toLowerCase().includes(c));
}<commit_msg>Fixed trying to fetch prefix in DMs<commit_after>const botVersion = require('../package.json').version;
const snekfetch  = require('snekfetch');
const moment     = require('moment');
const exec       = require('child_process').exec;
const time       = require('time-parser');
const fs         = require('fs');
const os         = require('os');
require('moment-duration-format');

exports.run = async function (msg) {
    if (isCommand(msg, 'ping'))
        msg.channel.send(`:ping_pong: Pong! ${client.pings[0]}ms`);

    if (isCommand(msg, ['reboot', 'restart'])) {
        if (msg.author.id !== settings.ownerID)
            return;
        await msg.channel.send('Restarting...');
        await client.destroy();
        process.exit();
    }

    if (isCommand(msg, 'invite'))
        msg.channel.send({
            embed: new Discord.RichEmbed()
                .setColor(settings.embedColor)
                .setDescription(`Click [here](https://discordapp.com/oauth2/authorize?permissions=27648&scope=bot&client_id=${client.user.id}) to invite me to your server, or click [here](https://discord.gg/Yphr6WG) for an invite to RemindMeBot\'s support server.`)
        });

    if (isCommand(msg, ['stats', 'info'])) {
        const embed = new Discord.RichEmbed()
            .setColor(settings.embedColor)
            .setTitle(`RemindMeBot ${botVersion}`)
            .setURL('https://discordbots.org/bot/290947970457796608')
            .addField('Guilds', client.guilds.size, true)
            .addField('Uptime', moment.duration(process.uptime(), 'seconds').format('dd:hh:mm:ss'), true)
            .addField('Ping', `${client.ping.toFixed(0)} ms`, true)
            .addField('RAM Usage', `${(process.memoryUsage().rss / 1048576).toFixed()}MB/${(os.totalmem() > 1073741824 ? `${(os.totalmem() / 1073741824).toFixed(1)} GB` : `${(os.totalmem() / 1048576).toFixed()} MB`)}
(${(process.memoryUsage().rss / os.totalmem() * 100).toFixed(2)}%)`, true)
            .addField('System Info', `${process.platform} (${process.arch})\n${(os.totalmem() > 1073741824 ? `${(os.totalmem() / 1073741824).toFixed(1)} GB` : `${(os.totalmem() / 1048576).toFixed(2)} MB`)}`, true)
            .addField('Libraries', `[Discord.js](https://discord.js.org) v${Discord.version}\nNode.js ${process.version}`, true)
            .addField('Links', '[Bot invite](https://discordapp.com/oauth2/authorize?permissions=27648&scope=bot&client_id=290947970457796608) | [Support server invite](https://discord.gg/Yphr6WG) | [GitHub](https://github.com/Aetheryx/remindme)', true)
            .setFooter('Created by Aetheryx#2222');

        msg.channel.send({ embed });
    }

    if (isCommand(msg, 'help'))
        msg.channel.send(`To set a reminder, simply send \`${settings.defaultPrefix}remindme\` and follow the instructions. Alternatively, you can also send \`${settings.defaultPrefix}remindme time_argument "message"\`, \ne.g. \`${settings.defaultPrefix}remindme 31 December 2017 "New Years"\`.\nMy prefix is \`${settings.defaultPrefix}\`; here's a list of my commands: `, {
            embed: new Discord.RichEmbed()
                .setColor(settings.embedColor)
                .setDescription('remindme, list, clear, info, ping, help, invite, forget'.split(', ').sort().join(', '))
        });

    if (isCommand(msg, ['reminders', 'list'])) {
        if (!db[msg.author.id] || db[msg.author.id].length === 0)
            return msg.reply('You have no reminders set!');
        client.users.get(msg.author.id).send({
            embed: new Discord.RichEmbed()
                .setColor(settings.embedColor)
                .addField(`Current reminder${plural(db[msg.author.id])}:`, db[msg.author.id].map((r) => r.reminder).join('\n'))
                .setFooter(`Reminder${plural(db[msg.author.id])} set to expire in(dd:hh:mm:ss): ${db[msg.author.id].map((b) => moment.duration(b.when - Date.now(), 'milliseconds').format('dd:hh:mm:ss')).join(', ')}`)
        });
    }

    if (isCommand(msg, 'clear')) {
        if (!db[msg.author.id] || db[msg.author.id].length === 0)
            return msg.reply('You have no reminders set!');

        msg.channel.send(':warning: This will delete all of your reminders! Are you sure? (`y`/`n`)');

        const collector = msg.channel.createMessageCollector(m => msg.author.id === m.author.id, { time: 40000 });

        collector.on('collect', (m) => {
            if (m.content.toLowerCase() === 'y' || m.content.toLowerCase() === 'yes') {
                db[msg.author.id] = [];
                fs.writeFile('./storage/reminders.json', JSON.stringify(db, '', '\t'), (err) => {
                    if (err)
                        return msg.channel.send(`Your reminders weren't cleared.\n${err.message}`);
                    msg.channel.send(':ballot_box_with_check: Reminders cleared.');
                });
            } else {
                msg.channel.send(':ballot_box_with_check: Cancelled.');
            }
            return collector.stop();
        });

        collector.on('end', (collected, reason) => {
            if (reason === 'time')
                msg.channel.send('Prompt timed out.');
        });
    }

    if (isCommand(msg, ['forget', 'delete'])) { // Very beta, kind of unstable.
        if (!db[msg.author.id] || db[msg.author.id].length === 0)
            return msg.reply('You have no reminders set!');

        msg.channel.send('Here\'s a list of your current reminders: ', {
            embed: new Discord.RichEmbed()
                .setColor(settings.embedColor)
                .setTitle('Reminders')
                .setDescription(Object.keys(db[msg.author.id]).map((e, i) => `[${i + 1}] ${db[msg.author.id][e].reminder}`).join('\n'))
                .setFooter('Send the number of the reminder you want me to forget(e.g. 3), or send c to cancel.')
        });
        const collector = msg.channel.createMessageCollector((m) => msg.author.id === m.author.id, { time: 40000 });
        collector.on('collect', (m) => {
            if (m.content.toLowerCase().startsWith(`${settings.defaultPrefix}forget`) || m.content.toLowerCase() === 'cancel' || m.content.toLowerCase() === 'c')
                return collector.stop();

            if (isNaN(m.content))
                return msg.channel.send('Argument entered is not a number. Send the number of the reminder you want me to forget (e.g. `3`), or send `c` to cancel.');

            if (parseInt(m.content) > Object.keys(db[msg.author.id]).length)
                return msg.channel.send('You don\'t have that many reminders, please choose a lower number.');

            const reminder = db[msg.author.id][parseInt(m.content) - 1];
            db[msg.author.id] = db[msg.author.id].filter((x) => x.reminder !== db[msg.author.id][parseInt(m.content) - 1].reminder);

            fs.writeFile('./storage/reminders.json', JSON.stringify(db, '', '\t'), (err) => {
                if (err) return msg.channel.send(`Your reminder wasn't removed.\n${err.message}`);
            });

            msg.channel.send(`Reminder \`${reminder.reminder}\` deleted.`);
            return collector.stop();
        });
    }

    if (isCommand(msg, 'remindme') && !args[0]) {
        msg.channel.send('What would you like the reminder to be? (You can send `cancel` at any time to cancel creation.)');

        const collector = msg.channel.createMessageCollector((m) => msg.author.id === m.author.id, { time: 40000 });

        let step = 1;

        const dboption = {
            'reminder': undefined,
            'when': undefined,
            'made': msg.createdTimestamp
        };

        collector.on('collect', (m) => {
            if (m.content.toLowerCase() === `${settings.defaultPrefix}remindme` || m.content.toLowerCase() === 'cancel')
                return collector.stop();

            if (step === 1) {
                if (m.content.length === 0)
                    return msg.channel.send('The reminder cannot be empty.\nWhat would you like the reminder to be?');

                dboption.reminder = m.content;

                msg.channel.send('When would you like to be reminded? (e.g. 24 hours)');
            }

            if (step === 2) {
                let tParse = time(m.content).absolute;

                if (m.content.includes('next'))
                    tParse = time(m.content.replace(/next/g, 'one')).absolute;

                if (m.content.startsWith('a ') || m.content.startsWith('an '))
                    tParse = time(m.content.replace(/a /g, 'one ').replace(/an /g, 'one ')).absolute;

                if (m.content.includes(' min'))
                    tParse = time(m.content.replace(/ min/g, 'minutes ')).absolute;

                if (!isNaN(m.content) || !tParse)
                    return msg.channel.send('Invalid time.\nWhen would you like to be reminded? (e.g. 24 hours)');

                if (time(m.content).relative < 0) {
                    collector.stop();
                    return msg.channel.send('Your reminder wasn\'t added because it was set for the past. Note that if you\'re trying to set a reminder for the same day at a specific time (e.g. `6 PM`), UTC time will be assumed.');
                }

                collector.stop();
                dboption.when = tParse;
                if (!db[msg.author.id])
                    db[msg.author.id] = [];

                db[msg.author.id].push(dboption);
                fs.writeFile('./storage/reminders.json', JSON.stringify(db, '', '\t'), (err) => {
                    if (err) return msg.channel.send(`Your reminder wasn't added.\n${err.message}`);
                    msg.channel.send({
                        embed: new Discord.RichEmbed()
                            .setColor(settings.embedColor)
                            .setDescription(`:ballot_box_with_check: Reminder added: ${dboption.reminder}`)
                            .setFooter('Reminder set for ')
                            .setTimestamp(new Date(tParse))
                    });
                });
            }
            step++;
        });
        collector.on('end', (collected, reason) => {
            if (reason === 'time')
                msg.channel.send('Prompt timed out.');
        });
    }

    const cmd = msg.content.toLowerCase().substring(settings.defaultPrefix.length).split(' ')[0];
    const args = msg.content.split(' ').slice(1);

    if (cmd === 'ev') {
        if (msg.author.id !== settings.ownerID)
            return false;
        let res = args.join(' ');
        const silent = res.includes('--silent') ? true : false;
        const asynchr = res.includes('--async') ? true : false;
        if (silent || asynchr) res = res.replace('--silent', '').replace('--async', '');

        try {
            res = asynchr ? eval(`(async()=>{${res}})();`) : eval(res);
            if (res instanceof Promise && asynchr) code = await res;
            if (typeof code !== 'string')
                res = require('util').inspect(res, { depth: 0 });
            res = res.replace(new RegExp(client.token, 'gi'), 'fite me irl');
        } catch (e) {
            res = e;
        }
        if (!silent)
            msg.channel.send(res, { code: 'js' });
    }

    if (cmd === 'exec') {
        if (msg.author.id !== settings.ownerID)
            return false;

        exec(args.join(' '), async (e, stdout, stderr) => {
            if (stdout.length > 2000 || stderr.length > 2000) {
                const res = await snekfetch.post('https://hastebin.com/documents')
                    .send(`${stdout}\n\n${stderr}`)
                    .catch((e) => msg.channel.send(e.message));

                msg.channel.send({
                    embed: new Discord.RichEmbed()
                        .setColor(settings.embedColor)
                        .setDescription(`Console log exceeds 2000 characters. View [here](https://hastebin.com/${res.body.key}).`)
                });
            } else {
                stdout && msg.channel.send(`Info: \n\`\`\`${stdout}\`\`\``);
                stderr && msg.channel.send(`Errors: \n\`\`\`${stderr}\`\`\``);
                if (!stderr && !stdout)
                    msg.react('\u2611');
            }
        });
    }

    if (cmd === 'prefix')
        return msg.channel.send('Custom prefixes aren\'t supported in DMs!');

    if (cmd === 'remindme' && msg.content.length > settings.defaultPrefix.length + 10) {
        if (!msg.content.includes('"'))
            return msg.channel.send(`Argument error. Please follow the proper syntax for the command:\n\`${settings.defaultPrefix}remindme time_argument "message"\`, e.g. \`${settings.defaultPrefix}remindme 31 December 2017 "New Years"\``);

        const timeArg = msg.content.substring(settings.defaultPrefix.length + 9, msg.content.indexOf('"') - 1);
        let tParse = time(timeArg).absolute;

        if (timeArg.includes('next'))
            tParse = time(timeArg.replace(/next/g, 'one')).absolute;

        if (timeArg.startsWith('a ') || timeArg.startsWith('an '))
            tParse = time(timeArg.replace(/a /g, 'one ').replace(/an /g, 'one ')).absolute;

        if (!isNaN(timeArg) || !tParse)
            return msg.channel.send('Invalid time argument. Please enter a proper time argument, e.g. `12 hours` or `next week`.');

        if (time(timeArg).relative < 0)
            return msg.channel.send('Your reminder wasn\'t added because it was set for the past. Note that if you\'re trying to set a reminder for the same day at a specific time (e.g. `6 PM`), UTC time will be assumed.');

        const reminder = msg.content.substring(msg.content.indexOf('"') + 1, msg.content.length - 1),
            dboption = {
                'reminder': reminder,
                'when': tParse,
                'made': msg.createdTimestamp
            };

        if (!db[msg.author.id])
            db[msg.author.id] = [];

        db[msg.author.id].push(dboption);
        fs.writeFile('./storage/reminders.json', JSON.stringify(db, '', '\t'), (err) => {
            if (err) return msg.channel.send(`Your reminder wasn't added.\n${err.message}`);
            msg.channel.send({
                embed: new Discord.RichEmbed()
                    .setColor(settings.embedColor)
                    .setDescription(`:ballot_box_with_check: Reminder added: ${dboption.reminder}`)
                    .setFooter('Reminder set for ')
                    .setTimestamp(new Date(tParse))
            });
        });
    }
};

function plural (x) {
    return x.length > 1 ? 's' : '';
}

function isCommand (msg, x) {
    const cmd = msg.content.toLowerCase().substring(settings.defaultPrefix.length).split(' ')[0];
    if (!Array.isArray(x))
        x = [x];
    return x.includes(cmd) || msg.isMentioned(client.user.id) && x.some((c) => msg.content.toLowerCase().includes(c));
}<|endoftext|>


<commit_before>/*global define*/
define([
        '../Core/DeveloperError',
        '../Core/destroyObject',
        '../Core/Cartesian2',
        '../Core/Math',
        './PixelFormat',
        './MipmapHint',
        './TextureMagnificationFilter',
        './TextureMinificationFilter',
        './TextureWrap'
    ], function(
        DeveloperError,
        destroyObject,
        Cartesian2,
        CesiumMath,
        PixelFormat,
        MipmapHint,
        TextureMagnificationFilter,
        TextureMinificationFilter,
        TextureWrap) {
    "use strict";

    /**
     * DOC_TBA
     *
     * @alias Texture
     * @internalConstructor
     *
     * @see Context#createTexture2D
     * @see Context#createTexture2DFromFramebuffer
     */
    var Texture = function(gl, textureFilterAnisotropic, textureTarget, texture, pixelFormat, pixelDatatype, width, height, preMultiplyAlpha) {
        this._gl = gl;
        this._textureFilterAnisotropic = textureFilterAnisotropic;
        this._textureTarget = textureTarget;
        this._texture = texture;
        this._pixelFormat = pixelFormat;
        this._pixelDatatype = pixelDatatype;
        this._width = width;
        this._height = height;
        this._dimensions = new Cartesian2(width, height);
        this._preMultiplyAlpha = preMultiplyAlpha;
        this._sampler = undefined;

        this.setSampler();
    };

    /**
     * DOC_TBA
     *
     * @memberof Texture
     *
     * @param {Object} source The source {ImageData}, {HTMLImageElement}, {HTMLCanvasElement}, or {HTMLVideoElement}.
     * @param {Number} xOffset optional
     * @param {Number} yOffset optional
     *
     * @exception {DeveloperError} Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
     * @exception {DeveloperError} source is required.
     * @exception {DeveloperError} xOffset must be greater than or equal to zero.
     * @exception {DeveloperError} yOffset must be greater than or equal to zero.
     * @exception {DeveloperError} xOffset + source.width must be less than or equal to getWidth().
     * @exception {DeveloperError} yOffset + source.height must be less than or equal to getHeight().
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.copyFrom = function(source, xOffset, yOffset) {
        if (!source) {
            throw new DeveloperError('source is required.');
        }

        xOffset = xOffset || 0;
        yOffset = yOffset || 0;

        var width = source.width;
        var height = source.height;

        if (PixelFormat.isDepthFormat(this._pixelFormat)) {
            throw new DeveloperError('Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
        }

        if (xOffset < 0) {
            throw new DeveloperError('xOffset must be greater than or equal to zero.');
        }

        if (yOffset < 0) {
            throw new DeveloperError('yOffset must be greater than or equal to zero.');
        }

        if (xOffset + width > this._width) {
            throw new DeveloperError('xOffset + source.width must be less than or equal to getWidth().');
        }

        if (yOffset + height > this._height) {
            throw new DeveloperError('yOffset + source.height must be less than or equal to getHeight().');
        }

        var gl = this._gl;
        var target = this._textureTarget;

        // TODO: gl.pixelStorei(gl._UNPACK_ALIGNMENT, 4);
        gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this._preMultiplyAlpha);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);

        //Firefox bug: texSubImage2D has overloads and can't resolve our enums, so we use + to explicitly convert to a number.
        if (source.arrayBufferView) {
            gl.texSubImage2D(target, 0, xOffset, yOffset, width, height, +this._pixelFormat, +this._pixelDatatype, source.arrayBufferView);
        } else {
            gl.texSubImage2D(target, 0, xOffset, yOffset, this._pixelFormat, this._pixelDatatype, source);
        }

        gl.bindTexture(target, null);
    };

    /**
     * DOC_TBA
     *
     * @memberof Texture
     *
     * @param {Number} xOffset optional
     * @param {Number} yOffset optional
     * @param {Number} framebufferXOffset optional
     * @param {Number} framebufferYOffset optional
     * @param {Number} width optional
     * @param {Number} height optional
     *
     * @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     * @exception {DeveloperError} xOffset must be greater than or equal to zero.
     * @exception {DeveloperError} yOffset must be greater than or equal to zero.
     * @exception {DeveloperError} framebufferXOffset must be greater than or equal to zero.
     * @exception {DeveloperError} framebufferYOffset must be greater than or equal to zero.
     * @exception {DeveloperError} xOffset + source.width must be less than or equal to getWidth().
     * @exception {DeveloperError} yOffset + source.height must be less than or equal to getHeight().
     */
    Texture.prototype.copyFromFramebuffer = function(xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height) {
        xOffset = xOffset || 0;
        yOffset = yOffset || 0;
        framebufferXOffset = framebufferXOffset || 0;
        framebufferYOffset = framebufferYOffset || 0;
        width = width || this._width;
        height = height || this._height;

        if (PixelFormat.isDepthFormat(this._pixelFormat)) {
            throw new DeveloperError('Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
        }

        if (xOffset < 0) {
            throw new DeveloperError('xOffset must be greater than or equal to zero.');
        }

        if (yOffset < 0) {
            throw new DeveloperError('yOffset must be greater than or equal to zero.');
        }

        if (framebufferXOffset < 0) {
            throw new DeveloperError('framebufferXOffset must be greater than or equal to zero.');
        }

        if (framebufferYOffset < 0) {
            throw new DeveloperError('framebufferYOffset must be greater than or equal to zero.');
        }

        if (xOffset + width > this._width) {
            throw new DeveloperError('xOffset + source.width must be less than or equal to getWidth().');
        }

        if (yOffset + height > this._height) {
            throw new DeveloperError('yOffset + source.height must be less than or equal to getHeight().');
        }

        var gl = this._gl;
        var target = this._textureTarget;

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);
        gl.copyTexSubImage2D(target, 0, xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height);
        gl.bindTexture(target, null);
    };

    /**
     * DOC_TBA
     *
     * @memberof Texture
     *
     * @param {MipmapHint} hint optional.
     *
     * @exception {DeveloperError} Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
     * @exception {DeveloperError} hint is invalid.
     * @exception {DeveloperError} This texture's width must be a power of two to call generateMipmap().
     * @exception {DeveloperError} This texture's height must be a power of two to call generateMipmap().
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.generateMipmap = function(hint) {
        if (PixelFormat.isDepthFormat(this._pixelFormat)) {
            throw new DeveloperError('Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
        }

        if ((this._width > 1) && !CesiumMath.isPowerOfTwo(this._width)) {
            throw new DeveloperError('width must be a power of two to call generateMipmap().');
        } else if ((this._height > 1) && !CesiumMath.isPowerOfTwo(this._height)) {
            throw new DeveloperError('height must be a power of two to call generateMipmap().');
        }

        hint = hint || MipmapHint.DONT_CARE;
        if (!MipmapHint.validate(hint)) {
            throw new DeveloperError('hint is invalid.');
        }

        var gl = this._gl;
        var target = this._textureTarget;

        gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);
        gl.generateMipmap(target);
        gl.bindTexture(target, null);
    };

    /**
    * DOC_TBA
    *
    * @memberof Texture
    *
    * @param sampler optional.
    *
    * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
    */
    Texture.prototype.setSampler = function(sampler) {
        var s = sampler || {
            wrapS : TextureWrap.CLAMP,
            wrapT : TextureWrap.CLAMP,
            minificationFilter : TextureMinificationFilter.LINEAR,
            magnificationFilter : TextureMagnificationFilter.LINEAR,
            maximumAnisotropy : 1.0
        };

        var gl = this._gl;
        var target = this._textureTarget;

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);
        gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, s.minificationFilter);
        gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, s.magnificationFilter);
        gl.texParameteri(target, gl.TEXTURE_WRAP_S, s.wrapS);
        gl.texParameteri(target, gl.TEXTURE_WRAP_T, s.wrapT);
        if (this._textureFilterAnisotropic) {
            gl.texParameteri(target, this._textureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, s.maximumAnisotropy);
        }
        gl.bindTexture(target, null);

        this._sampler = {
            wrapS : s.wrapS,
            wrapT : s.wrapT,
            minificationFilter : s.minificationFilter,
            magnificationFilter : s.magnificationFilter,
            maximumAnisotropy : s.maximumAnisotropy
        };
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getSampler = function() {
        return this._sampler;
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getPixelFormat = function() {
        return this._pixelFormat;
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getPixelDatatype = function() {
        return this._pixelDatatype;
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getWidth = function() {
        return this._width;
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getDimensions = function() {
        return this._dimensions;
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getPreMultiplyAlpha = function() {
        return this._preMultiplyAlpha;
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getHeight = function() {
        return this._height;
    };

    Texture.prototype._getTexture = function() {
        return this._texture;
    };

    Texture.prototype._getTarget = function() {
        return this._textureTarget;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *

     * If this object was destroyed, it should not be used; calling any function other than
     * isDestroyed will result in a {@link DeveloperError} exception.
     *
     * @memberof Texture
     *
     * @return {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see Texture#destroy
     */
    Texture.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     *

     * Once an object is destroyed, it should not be used; calling any function other than
     * isDestroyed will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (undefined) to the object as done in the example.
     *
     * @memberof Texture
     *
     * @return {undefined}
     *
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     *
     * @see Texture#isDestroyed
     * @see glDeleteTextures
     *
     * @example
     * texture = texture && texture.destroy();
     */
    Texture.prototype.destroy = function() {
        this._gl.deleteTexture(this._texture);
        return destroyObject(this);
    };

    return Texture;
});<commit_msg>Revert changes to Texture.js, correctly this time.<commit_after>/*global define*/
define([
        '../Core/DeveloperError',
        '../Core/destroyObject',
        '../Core/Cartesian2',
        '../Core/Math',
        './PixelFormat',
        './MipmapHint',
        './TextureMagnificationFilter',
        './TextureMinificationFilter',
        './TextureWrap'
    ], function(
        DeveloperError,
        destroyObject,
        Cartesian2,
        CesiumMath,
        PixelFormat,
        MipmapHint,
        TextureMagnificationFilter,
        TextureMinificationFilter,
        TextureWrap) {
    "use strict";

    /**
     * DOC_TBA
     *
     * @alias Texture
     * @internalConstructor
     *
     * @see Context#createTexture2D
     * @see Context#createTexture2DFromFramebuffer
     */
    var Texture = function(gl, textureFilterAnisotropic, textureTarget, texture, pixelFormat, pixelDatatype, width, height, preMultiplyAlpha) {
        this._gl = gl;
        this._textureFilterAnisotropic = textureFilterAnisotropic;
        this._textureTarget = textureTarget;
        this._texture = texture;
        this._pixelFormat = pixelFormat;
        this._pixelDatatype = pixelDatatype;
        this._width = width;
        this._height = height;
        this._dimensions = new Cartesian2(width, height);
        this._preMultiplyAlpha = preMultiplyAlpha;
        this._sampler = undefined;

        this.setSampler();
    };

    /**
     * DOC_TBA
     *
     * @memberof Texture
     *
     * @param {Object} source The source {ImageData}, {HTMLImageElement}, {HTMLCanvasElement}, or {HTMLVideoElement}.
     * @param {Number} xOffset optional
     * @param {Number} yOffset optional
     *
     * @exception {DeveloperError} Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
     * @exception {DeveloperError} source is required.
     * @exception {DeveloperError} xOffset must be greater than or equal to zero.
     * @exception {DeveloperError} yOffset must be greater than or equal to zero.
     * @exception {DeveloperError} xOffset + source.width must be less than or equal to getWidth().
     * @exception {DeveloperError} yOffset + source.height must be less than or equal to getHeight().
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.copyFrom = function(source, xOffset, yOffset) {
        if (!source) {
            throw new DeveloperError('source is required.');
        }

        xOffset = xOffset || 0;
        yOffset = yOffset || 0;

        var width = source.width;
        var height = source.height;

        if (PixelFormat.isDepthFormat(this._pixelFormat)) {
            throw new DeveloperError('Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
        }

        if (xOffset < 0) {
            throw new DeveloperError('xOffset must be greater than or equal to zero.');
        }

        if (yOffset < 0) {
            throw new DeveloperError('yOffset must be greater than or equal to zero.');
        }

        if (xOffset + width > this._width) {
            throw new DeveloperError('xOffset + source.width must be less than or equal to getWidth().');
        }

        if (yOffset + height > this._height) {
            throw new DeveloperError('yOffset + source.height must be less than or equal to getHeight().');
        }

        var gl = this._gl;
        var target = this._textureTarget;

        // TODO: gl.pixelStorei(gl._UNPACK_ALIGNMENT, 4);
        gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this._preMultiplyAlpha);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);

        //Firefox bug: texSubImage2D has overloads and can't resolve our enums, so we use + to explicitly convert to a number.
        if (source.arrayBufferView) {
            gl.texSubImage2D(target, 0, xOffset, yOffset, width, height, +this._pixelFormat, +this._pixelDatatype, source.arrayBufferView);
        } else {
            gl.texSubImage2D(target, 0, xOffset, yOffset, this._pixelFormat, this._pixelDatatype, source);
        }

        gl.bindTexture(target, null);
    };

    /**
     * DOC_TBA
     *
     * @memberof Texture
     *
     * @param {Number} xOffset optional
     * @param {Number} yOffset optional
     * @param {Number} framebufferXOffset optional
     * @param {Number} framebufferYOffset optional
     * @param {Number} width optional
     * @param {Number} height optional
     *
     * @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     * @exception {DeveloperError} xOffset must be greater than or equal to zero.
     * @exception {DeveloperError} yOffset must be greater than or equal to zero.
     * @exception {DeveloperError} framebufferXOffset must be greater than or equal to zero.
     * @exception {DeveloperError} framebufferYOffset must be greater than or equal to zero.
     * @exception {DeveloperError} xOffset + source.width must be less than or equal to getWidth().
     * @exception {DeveloperError} yOffset + source.height must be less than or equal to getHeight().
     */
    Texture.prototype.copyFromFramebuffer = function(xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height) {
        xOffset = xOffset || 0;
        yOffset = yOffset || 0;
        framebufferXOffset = framebufferXOffset || 0;
        framebufferYOffset = framebufferYOffset || 0;
        width = width || this._width;
        height = height || this._height;

        if (PixelFormat.isDepthFormat(this._pixelFormat)) {
            throw new DeveloperError('Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
        }

        if (xOffset < 0) {
            throw new DeveloperError('xOffset must be greater than or equal to zero.');
        }

        if (yOffset < 0) {
            throw new DeveloperError('yOffset must be greater than or equal to zero.');
        }

        if (framebufferXOffset < 0) {
            throw new DeveloperError('framebufferXOffset must be greater than or equal to zero.');
        }

        if (framebufferYOffset < 0) {
            throw new DeveloperError('framebufferYOffset must be greater than or equal to zero.');
        }

        if (xOffset + width > this._width) {
            throw new DeveloperError('xOffset + source.width must be less than or equal to getWidth().');
        }

        if (yOffset + height > this._height) {
            throw new DeveloperError('yOffset + source.height must be less than or equal to getHeight().');
        }

        var gl = this._gl;
        var target = this._textureTarget;

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);
        gl.copyTexSubImage2D(target, 0, xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height);
        gl.bindTexture(target, null);
    };

    /**
     * DOC_TBA
     *
     * @memberof Texture
     *
     * @param {MipmapHint} hint optional.
     *
     * @exception {DeveloperError} Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
     * @exception {DeveloperError} hint is invalid.
     * @exception {DeveloperError} This texture's width must be a power of two to call generateMipmap().
     * @exception {DeveloperError} This texture's height must be a power of two to call generateMipmap().
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.generateMipmap = function(hint) {
        if (PixelFormat.isDepthFormat(this._pixelFormat)) {
            throw new DeveloperError('Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
        }

        if ((this._width > 1) && !CesiumMath.isPowerOfTwo(this._width)) {
            throw new DeveloperError('width must be a power of two to call generateMipmap().');
        } else if ((this._height > 1) && !CesiumMath.isPowerOfTwo(this._height)) {
            throw new DeveloperError('height must be a power of two to call generateMipmap().');
        }

        hint = hint || MipmapHint.DONT_CARE;
        if (!MipmapHint.validate(hint)) {
            throw new DeveloperError('hint is invalid.');
        }

        var gl = this._gl;
        var target = this._textureTarget;

        gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);
        gl.generateMipmap(target);
        gl.bindTexture(target, null);
    };

    /**
    * Sets the sampler to use when sampling this texture.
    *
    * @memberof Texture
    *
    * @param [sampler] The sampler to use.  Create a sampler by calling {@link Context#createSampler}.  If this
    *                  parameter is not specified, a default sampler is used.  The default sampler clamps texture
    *                  coordinates in both directions, uses linear filtering for both magnification and minifcation,
    *                  and uses a maximum anisotropy of 1.0.
    *
    * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
    *
    * @see Context#createSampler
    */
    Texture.prototype.setSampler = function(sampler) {
        var s = sampler || {
            wrapS : TextureWrap.CLAMP,
            wrapT : TextureWrap.CLAMP,
            minificationFilter : TextureMinificationFilter.LINEAR,
            magnificationFilter : TextureMagnificationFilter.LINEAR,
            maximumAnisotropy : 1.0
        };

        var gl = this._gl;
        var target = this._textureTarget;

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);
        gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, s.minificationFilter);
        gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, s.magnificationFilter);
        gl.texParameteri(target, gl.TEXTURE_WRAP_S, s.wrapS);
        gl.texParameteri(target, gl.TEXTURE_WRAP_T, s.wrapT);
        if (this._textureFilterAnisotropic) {
            gl.texParameteri(target, this._textureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, s.maximumAnisotropy);
        }
        gl.bindTexture(target, null);

        this._sampler = {
            wrapS : s.wrapS,
            wrapT : s.wrapT,
            minificationFilter : s.minificationFilter,
            magnificationFilter : s.magnificationFilter,
            maximumAnisotropy : s.maximumAnisotropy
        };
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getSampler = function() {
        return this._sampler;
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getPixelFormat = function() {
        return this._pixelFormat;
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getPixelDatatype = function() {
        return this._pixelDatatype;
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getWidth = function() {
        return this._width;
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getDimensions = function() {
        return this._dimensions;
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getPreMultiplyAlpha = function() {
        return this._preMultiplyAlpha;
    };

    /**
     * DOC_TBA
     * @memberof Texture
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.getHeight = function() {
        return this._height;
    };

    Texture.prototype._getTexture = function() {
        return this._texture;
    };

    Texture.prototype._getTarget = function() {
        return this._textureTarget;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *

     * If this object was destroyed, it should not be used; calling any function other than
     * isDestroyed will result in a {@link DeveloperError} exception.
     *
     * @memberof Texture
     *
     * @return {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see Texture#destroy
     */
    Texture.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     *

     * Once an object is destroyed, it should not be used; calling any function other than
     * isDestroyed will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (undefined) to the object as done in the example.
     *
     * @memberof Texture
     *
     * @return {undefined}
     *
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     *
     * @see Texture#isDestroyed
     * @see glDeleteTextures
     *
     * @example
     * texture = texture && texture.destroy();
     */
    Texture.prototype.destroy = function() {
        this._gl.deleteTexture(this._texture);
        return destroyObject(this);
    };

    return Texture;
});<|endoftext|>


<commit_before>import forEach from 'lodash.foreach'
import isArray from 'lodash.isarray'
import map from 'lodash.map'

import {
  ensureArray,
  extractProperty,
  parseXml
} from './utils'
import {
  isHostRunning,
  isVmRunning
} from './xapi'

// ===================================================================

function link (obj, prop) {
  const dynamicValue = obj[`$${prop}`]
  if (dynamicValue == null) {
    return dynamicValue // Properly handles null and undefined.
  }

  if (isArray(dynamicValue)) {
    return map(dynamicValue, '$id')
  }

  return dynamicValue.$id
}

// The JSON interface of XAPI format dates incorrectly.
const JSON_DATE_RE = /^(\d{4})(\d{2})(\d{2})T(.+)$/
function fixJsonDate (date) {
  const matches = JSON_DATE_RE.exec(date)

  if (!matches) {
    return date
  }

  const [, year, month, day, time] = matches
  return `${year}-${month}-${day}T${time}`
}

function toTimestamp (date) {
  if (!date) {
    return null
  }

  return Math.round(Date.parse(fixJsonDate(date)) / 1000)
}

// ===================================================================

export function pool (obj) {
  return {
    default_SR: link(obj, 'default_SR'),
    HA_enabled: Boolean(obj.ha_enabled),
    master: link(obj, 'master'),
    name_description: obj.name_description,
    name_label: obj.name_label || obj.$master.name_label

    // TODO
    // - ? networks = networksByPool.items[pool.id] (network.$pool.id)
    // - hosts = hostsByPool.items[pool.id] (host.$pool.$id)
    // - patches = poolPatchesByPool.items[pool.id] (poolPatch.$pool.id)
    // - SRs = srsByContainer.items[pool.id] (sr.$container.id)
    // - templates = vmTemplatesByContainer.items[pool.id] (vmTemplate.$container.$id)
    // - VMs = vmsByContainer.items[pool.id] (vm.$container.id)
    // - $running_hosts = runningHostsByPool.items[pool.id] (runningHost.$pool.id)
    // - $running_VMs = runningVmsByPool.items[pool.id] (runningHost.$pool.id)
    // - $VMs = vmsByPool.items[pool.id] (vm.$pool.id)
  }
}

// -------------------------------------------------------------------

export function host (obj) {
  const {
    $metrics: metrics,
    other_config: otherConfig
  } = obj

  const isRunning = isHostRunning(obj)

  return {
    address: obj.address,
    bios_strings: obj.bios_strings,
    build: obj.software_version.build_number,
    CPUs: obj.cpu_info,
    enabled: Boolean(obj.enabled),
    current_operations: obj.current_operations,
    hostname: obj.hostname,
    iSCSI_name: otherConfig.iscsi_iqn || null,
    name_description: obj.name_description,
    name_label: obj.name_label,
    memory: (function () {
      if (metrics) {
        const free = +metrics.memory_free
        const total = +metrics.memory_total

        return {
          usage: total - free,
          size: total
        }
      }

      return {
        usage: 0,
        total: 0
      }
    })(),
    patches: link(obj, 'patches'),
    power_state: isRunning ? 'Running' : 'Halted',
    version: obj.software_version.product_version,

    // TODO: dedupe.
    PIFs: link(obj, 'PIFs'),
    $PIFs: link(obj, 'PIFs'),
    PCIs: link(obj, 'PCIs'),
    $PCIs: link(obj, 'PCIs'),
    PGPUs: link(obj, 'PGPUs'),
    $PGPUs: link(obj, 'PGPUs'),

    $PBDs: link(obj, 'PBDs')

    // TODO:
    // - controller = vmControllersByContainer.items[host.id]
    // - SRs = srsByContainer.items[host.id]
    // - tasks = tasksByHost.items[host.id]
    // - templates = vmTemplatesByContainer.items[host.id]
    // - VMs = vmsByContainer.items[host.id]
    // - $vCPUs = sum(host.VMs, vm => host.CPUs.number)
  }
}

// -------------------------------------------------------------------

export function vm (obj) {
  const {
    $guest_metrics: guestMetrics,
    $metrics: metrics,
    other_config: otherConfig
  } = obj

  const isRunning = isVmRunning(obj)

  const vm = {
    // type is redefined after for controllers/, templates &
    // snapshots.
    type: 'VM',

    addresses: guestMetrics && guestMetrics.networks || null,
    auto_poweron: Boolean(otherConfig.auto_poweron),
    boot: obj.HVM_boot_params,
    CPUs: {
      max: +obj.VCPUs_max,
      number: (
        isRunning && metrics ?
          +metrics.VCPUs_number :
          +obj.VCPUs_at_startup
      )
    },
    current_operations: obj.current_operations,
    docker: (function () {
      const monitor = otherConfig['xscontainer-monitor']
      if (!monitor) {
        return
      }

      if (monitor === 'False') {
        return {
          enabled: false
        }
      }

      const {
        docker_ps: process,
        docker_info: info,
        docker_version: version
      } = otherConfig

      return {
        enabled: true,
        info: info && parseXml(info).docker_info,
        process: process && parseXml(process).docker_ps,
        version: version && parseXml(version).docker_version
      }
    })(),

    // TODO: there is two possible value: "best-effort" and "restart"
    high_availability: Boolean(obj.ha_restart_priority),

    memory: (function () {
      const dynamicMin = +obj.memory_dynamic_min
      const dynamicMax = +obj.memory_dynamic_max
      const staticMin = +obj.memory_static_min
      const staticMax = +obj.memory_static_max

      const memory = {
        dynamic: [ dynamicMin, dynamicMax ],
        static: [ staticMin, staticMax ]
      }

      const gmMemory = guestMetrics && guestMetrics.memory

      if (!isRunning) {
        memory.size = dynamicMax
      } else if (gmMemory && gmMemory.used) {
        memory.usage = +gmMemory.used
        memory.size = +gmMemory.total
      } else if (metrics) {
        memory.size = +metrics.memory_actual
      } else {
        memory.size = dynamicMax
      }

      return memory
    })(),
    name_description: obj.name_description,
    name_label: obj.name_label,
    other: otherConfig,
    os_version: guestMetrics && guestMetrics.os_version || null,
    power_state: obj.power_state,
    PV_drivers: Boolean(guestMetrics && guestMetrics.PV_drivers_up_to_date),
    snapshot_time: toTimestamp(obj.snapshot_time),
    snapshots: link(obj, 'snapshots'),
    VIFs: link(obj, 'VIFs'),

    $container: (
      isRunning ?
        link(obj, 'resident_on') :
        link(obj, 'pool') // TODO: handle local VMs (`VM.get_possible_hosts()`).
    ),
    $VBDs: link(obj, 'VBDs'),

    // TODO: dedupe
    VGPUs: link(obj, 'VGPUs'),
    $VGPUs: link(obj, 'VGPUs')
  }

  if (obj.is_control_domain) {
    vm.type += '-controller'
  } else if (obj.is_a_snapshot) {
    vm.type += '-snapshot'

    vm.$snapshot_of = link(obj, 'snapshot_of')
  } else if (obj.is_a_template) {
    vm.type += '-template'

    vm.CPUs.number = +obj.VCPUs_at_startup
    vm.template_info = {
      arch: otherConfig['install-arch'],
      disks: (function () {
        const {disks: xml} = otherConfig
        let data
        if (!xml || !(data = parseXml(xml)).provision) {
          return []
        }

        const disks = ensureArray(data.provision.disk)
        forEach(disks, function normalize (disk) {
          disk.bootable = disk.bootable === 'true'
          disk.size = +disk.size
          disk.SR = extractProperty(disk, 'sr')
        })

        return disks
      })(),
      install_methods: (function () {
        const {['install-methods']: methods} = otherConfig

        return methods ? methods.split(',') : []
      })()
    }
  }

  return vm
}

// -------------------------------------------------------------------

export function sr (obj) {
  return {
    type: 'SR',

    content_type: obj.content_type,
    name_description: obj.name_description,
    name_label: obj.name_label,
    physical_usage: +obj.physical_utilisation,
    size: +obj.physical_size,
    SR_type: obj.type,
    usage: +obj.virtual_allocation,
    VDIs: link(obj, 'VDIs'),

    $container: (
      obj.shared ?
        link(obj, 'pool') :
        obj.$PBDs[0] && link(obj.$PBDs[0], 'host')
    ),
    $PBDs: link(obj, 'PBDs')
  }
}

// -------------------------------------------------------------------

export function pbd (obj) {
  return {
    type: 'PBD',

    attached: obj.currently_attached,
    host: link(obj, 'host'),
    SR: link(obj, 'SR')
  }
}

// -------------------------------------------------------------------

export function pif (obj) {
  return {
    type: 'PIF',

    attached: Boolean(obj.currently_attached),
    device: obj.device,
    IP: obj.IP,
    MAC: obj.MAC,
    management: Boolean(obj.management), // TODO: find a better name.
    mode: obj.ip_configuration_mode,
    MTU: +obj.MTU,
    netmask: obj.netmask,
    vlan: +obj.VLAN,

    // TODO: What is it?
    //
    // Could it mean “is this a physical interface?”.
    // How could a PIF not be physical?
    // physical: obj.physical,

    $host: link(obj, 'host'),
    $network: link(obj, 'network')
  }
}

// -------------------------------------------------------------------

// TODO: should we have a VDI-snapshot type like we have with VMs?
export function vdi (obj) {
  return {
    type: 'VDI',

    name_description: obj.name_description,
    name_label: obj.name_label,
    size: +obj.virtual_size,
    snapshots: link(obj, 'snapshots'),
    snapshot_time: toTimestamp(obj.snapshot_time),
    usage: +obj.physical_utilisation,

    $snapshot_of: link(obj, 'snapshot_of'),
    $SR: link(obj, 'SR'),
    $VBDs: link(obj, 'VBDs')
  }
}

// -------------------------------------------------------------------

export function vbd (obj) {
  return {
    type: 'VBD',

    attached: Boolean(obj.currently_attached),
    bootable: Boolean(obj.bootable),
    is_cd_drive: obj.type === 'CD',
    position: obj.userdevice,
    read_only: obj.mode === 'RO',
    VDI: link(obj, 'VDI'),
    VM: link(obj, 'VM')
  }
}

// -------------------------------------------------------------------

export function vif (obj) {
  return {
    type: 'VIF',

    attached: Boolean(obj.currently_attached),
    device: obj.device, // TODO: should it be cast to a number?
    MAC: obj.MAC,
    MTU: +obj.MTU,

    $network: link(obj, 'network'),
    $VM: link(obj, 'VM')
  }
}

// -------------------------------------------------------------------

export function network (obj) {
  return {
    bridge: obj.bridge,
    MTU: +obj.MTU,
    name_description: obj.name_description,
    name_label: obj.name_label,
    PIFs: link(obj, 'PIFs'),
    VIFs: link(obj, 'VIFs')
  }
}

// -------------------------------------------------------------------

export function message (obj) {
  return {
    body: obj.body,
    name: obj.name,
    time: toTimestamp(obj.timestamp),

    $object: obj.obj_uuid // Special link as it is already an UUID.
  }
}

// -------------------------------------------------------------------

export function task (obj) {
  return {
    created: toTimestamp(obj.created),
    current_operations: obj.current_operations,
    finished: toTimestamp(obj.finished),
    name_description: obj.name_description,
    name_label: obj.name_label,
    progress: +obj.progress,
    result: obj.result,
    status: obj.status,

    $host: link(obj, 'resident_on')
  }
}

// -------------------------------------------------------------------

export function host_patch (obj) {
  return {
    applied: Boolean(obj.applied),
    time: toTimestamp(obj.timestamp_applied),
    pool_patch: link(obj, 'pool_patch'),

    $host: link(obj, 'host')
  }
}

// -------------------------------------------------------------------

export function pool_patch (obj) {
  return {
    applied: Boolean(obj.pool_applied),
    name_description: obj.name_description,
    name_label: obj.name_label,
    size: +obj.size,
    version: obj.version,

    // TODO: host.[$]pool_patches ←→ pool.[$]host_patches
    $host_patches: link(obj, 'host_patches')
  }
}

// -------------------------------------------------------------------

export function pci (obj) {
  return {
    type: 'PCI',

    class_name: obj.class_name,
    device_name: obj.device_name,
    pci_id: obj.pci_id,

    $host: link(obj, 'host')
  }
}

// -------------------------------------------------------------------

export function pgpu (obj) {
  return {
    type: 'PGPU',

    pci: link(obj, 'PCI'),

    // TODO: dedupe.
    host: link(obj, 'host'),
    $host: link(obj, 'host'),
    vgpus: link(obj, 'resident_VGPUs'),
    $vgpus: link(obj, 'resident_VGPUs')
  }
}

// -------------------------------------------------------------------

export function vgpu (obj) {
  return {
    type: 'VGPU',

    currentlyAttached: Boolean(obj.currently_attached),
    device: obj.device,
    resident_on: link(obj, 'resident_on'),
    vm: link(obj, 'VM')
  }
}
<commit_msg>Expose host.powerOnMode (fixes #77).<commit_after>import forEach from 'lodash.foreach'
import isArray from 'lodash.isarray'
import map from 'lodash.map'

import {
  ensureArray,
  extractProperty,
  parseXml
} from './utils'
import {
  isHostRunning,
  isVmRunning
} from './xapi'

// ===================================================================

function link (obj, prop) {
  const dynamicValue = obj[`$${prop}`]
  if (dynamicValue == null) {
    return dynamicValue // Properly handles null and undefined.
  }

  if (isArray(dynamicValue)) {
    return map(dynamicValue, '$id')
  }

  return dynamicValue.$id
}

// The JSON interface of XAPI format dates incorrectly.
const JSON_DATE_RE = /^(\d{4})(\d{2})(\d{2})T(.+)$/
function fixJsonDate (date) {
  const matches = JSON_DATE_RE.exec(date)

  if (!matches) {
    return date
  }

  const [, year, month, day, time] = matches
  return `${year}-${month}-${day}T${time}`
}

function toTimestamp (date) {
  if (!date) {
    return null
  }

  return Math.round(Date.parse(fixJsonDate(date)) / 1000)
}

// ===================================================================

export function pool (obj) {
  return {
    default_SR: link(obj, 'default_SR'),
    HA_enabled: Boolean(obj.ha_enabled),
    master: link(obj, 'master'),
    name_description: obj.name_description,
    name_label: obj.name_label || obj.$master.name_label

    // TODO
    // - ? networks = networksByPool.items[pool.id] (network.$pool.id)
    // - hosts = hostsByPool.items[pool.id] (host.$pool.$id)
    // - patches = poolPatchesByPool.items[pool.id] (poolPatch.$pool.id)
    // - SRs = srsByContainer.items[pool.id] (sr.$container.id)
    // - templates = vmTemplatesByContainer.items[pool.id] (vmTemplate.$container.$id)
    // - VMs = vmsByContainer.items[pool.id] (vm.$container.id)
    // - $running_hosts = runningHostsByPool.items[pool.id] (runningHost.$pool.id)
    // - $running_VMs = runningVmsByPool.items[pool.id] (runningHost.$pool.id)
    // - $VMs = vmsByPool.items[pool.id] (vm.$pool.id)
  }
}

// -------------------------------------------------------------------

export function host (obj) {
  const {
    $metrics: metrics,
    other_config: otherConfig
  } = obj

  const isRunning = isHostRunning(obj)

  return {
    address: obj.address,
    bios_strings: obj.bios_strings,
    build: obj.software_version.build_number,
    CPUs: obj.cpu_info,
    enabled: Boolean(obj.enabled),
    current_operations: obj.current_operations,
    hostname: obj.hostname,
    iSCSI_name: otherConfig.iscsi_iqn || null,
    name_description: obj.name_description,
    name_label: obj.name_label,
    memory: (function () {
      if (metrics) {
        const free = +metrics.memory_free
        const total = +metrics.memory_total

        return {
          usage: total - free,
          size: total
        }
      }

      return {
        usage: 0,
        total: 0
      }
    })(),
    patches: link(obj, 'patches'),
    powerOnMode: obj.power_on_mode,
    power_state: isRunning ? 'Running' : 'Halted',
    version: obj.software_version.product_version,

    // TODO: dedupe.
    PIFs: link(obj, 'PIFs'),
    $PIFs: link(obj, 'PIFs'),
    PCIs: link(obj, 'PCIs'),
    $PCIs: link(obj, 'PCIs'),
    PGPUs: link(obj, 'PGPUs'),
    $PGPUs: link(obj, 'PGPUs'),

    $PBDs: link(obj, 'PBDs')

    // TODO:
    // - controller = vmControllersByContainer.items[host.id]
    // - SRs = srsByContainer.items[host.id]
    // - tasks = tasksByHost.items[host.id]
    // - templates = vmTemplatesByContainer.items[host.id]
    // - VMs = vmsByContainer.items[host.id]
    // - $vCPUs = sum(host.VMs, vm => host.CPUs.number)
  }
}

// -------------------------------------------------------------------

export function vm (obj) {
  const {
    $guest_metrics: guestMetrics,
    $metrics: metrics,
    other_config: otherConfig
  } = obj

  const isRunning = isVmRunning(obj)

  const vm = {
    // type is redefined after for controllers/, templates &
    // snapshots.
    type: 'VM',

    addresses: guestMetrics && guestMetrics.networks || null,
    auto_poweron: Boolean(otherConfig.auto_poweron),
    boot: obj.HVM_boot_params,
    CPUs: {
      max: +obj.VCPUs_max,
      number: (
        isRunning && metrics ?
          +metrics.VCPUs_number :
          +obj.VCPUs_at_startup
      )
    },
    current_operations: obj.current_operations,
    docker: (function () {
      const monitor = otherConfig['xscontainer-monitor']
      if (!monitor) {
        return
      }

      if (monitor === 'False') {
        return {
          enabled: false
        }
      }

      const {
        docker_ps: process,
        docker_info: info,
        docker_version: version
      } = otherConfig

      return {
        enabled: true,
        info: info && parseXml(info).docker_info,
        process: process && parseXml(process).docker_ps,
        version: version && parseXml(version).docker_version
      }
    })(),

    // TODO: there is two possible value: "best-effort" and "restart"
    high_availability: Boolean(obj.ha_restart_priority),

    memory: (function () {
      const dynamicMin = +obj.memory_dynamic_min
      const dynamicMax = +obj.memory_dynamic_max
      const staticMin = +obj.memory_static_min
      const staticMax = +obj.memory_static_max

      const memory = {
        dynamic: [ dynamicMin, dynamicMax ],
        static: [ staticMin, staticMax ]
      }

      const gmMemory = guestMetrics && guestMetrics.memory

      if (!isRunning) {
        memory.size = dynamicMax
      } else if (gmMemory && gmMemory.used) {
        memory.usage = +gmMemory.used
        memory.size = +gmMemory.total
      } else if (metrics) {
        memory.size = +metrics.memory_actual
      } else {
        memory.size = dynamicMax
      }

      return memory
    })(),
    name_description: obj.name_description,
    name_label: obj.name_label,
    other: otherConfig,
    os_version: guestMetrics && guestMetrics.os_version || null,
    power_state: obj.power_state,
    PV_drivers: Boolean(guestMetrics && guestMetrics.PV_drivers_up_to_date),
    snapshot_time: toTimestamp(obj.snapshot_time),
    snapshots: link(obj, 'snapshots'),
    VIFs: link(obj, 'VIFs'),

    $container: (
      isRunning ?
        link(obj, 'resident_on') :
        link(obj, 'pool') // TODO: handle local VMs (`VM.get_possible_hosts()`).
    ),
    $VBDs: link(obj, 'VBDs'),

    // TODO: dedupe
    VGPUs: link(obj, 'VGPUs'),
    $VGPUs: link(obj, 'VGPUs')
  }

  if (obj.is_control_domain) {
    vm.type += '-controller'
  } else if (obj.is_a_snapshot) {
    vm.type += '-snapshot'

    vm.$snapshot_of = link(obj, 'snapshot_of')
  } else if (obj.is_a_template) {
    vm.type += '-template'

    vm.CPUs.number = +obj.VCPUs_at_startup
    vm.template_info = {
      arch: otherConfig['install-arch'],
      disks: (function () {
        const {disks: xml} = otherConfig
        let data
        if (!xml || !(data = parseXml(xml)).provision) {
          return []
        }

        const disks = ensureArray(data.provision.disk)
        forEach(disks, function normalize (disk) {
          disk.bootable = disk.bootable === 'true'
          disk.size = +disk.size
          disk.SR = extractProperty(disk, 'sr')
        })

        return disks
      })(),
      install_methods: (function () {
        const {['install-methods']: methods} = otherConfig

        return methods ? methods.split(',') : []
      })()
    }
  }

  return vm
}

// -------------------------------------------------------------------

export function sr (obj) {
  return {
    type: 'SR',

    content_type: obj.content_type,
    name_description: obj.name_description,
    name_label: obj.name_label,
    physical_usage: +obj.physical_utilisation,
    size: +obj.physical_size,
    SR_type: obj.type,
    usage: +obj.virtual_allocation,
    VDIs: link(obj, 'VDIs'),

    $container: (
      obj.shared ?
        link(obj, 'pool') :
        obj.$PBDs[0] && link(obj.$PBDs[0], 'host')
    ),
    $PBDs: link(obj, 'PBDs')
  }
}

// -------------------------------------------------------------------

export function pbd (obj) {
  return {
    type: 'PBD',

    attached: obj.currently_attached,
    host: link(obj, 'host'),
    SR: link(obj, 'SR')
  }
}

// -------------------------------------------------------------------

export function pif (obj) {
  return {
    type: 'PIF',

    attached: Boolean(obj.currently_attached),
    device: obj.device,
    IP: obj.IP,
    MAC: obj.MAC,
    management: Boolean(obj.management), // TODO: find a better name.
    mode: obj.ip_configuration_mode,
    MTU: +obj.MTU,
    netmask: obj.netmask,
    vlan: +obj.VLAN,

    // TODO: What is it?
    //
    // Could it mean “is this a physical interface?”.
    // How could a PIF not be physical?
    // physical: obj.physical,

    $host: link(obj, 'host'),
    $network: link(obj, 'network')
  }
}

// -------------------------------------------------------------------

// TODO: should we have a VDI-snapshot type like we have with VMs?
export function vdi (obj) {
  return {
    type: 'VDI',

    name_description: obj.name_description,
    name_label: obj.name_label,
    size: +obj.virtual_size,
    snapshots: link(obj, 'snapshots'),
    snapshot_time: toTimestamp(obj.snapshot_time),
    usage: +obj.physical_utilisation,

    $snapshot_of: link(obj, 'snapshot_of'),
    $SR: link(obj, 'SR'),
    $VBDs: link(obj, 'VBDs')
  }
}

// -------------------------------------------------------------------

export function vbd (obj) {
  return {
    type: 'VBD',

    attached: Boolean(obj.currently_attached),
    bootable: Boolean(obj.bootable),
    is_cd_drive: obj.type === 'CD',
    position: obj.userdevice,
    read_only: obj.mode === 'RO',
    VDI: link(obj, 'VDI'),
    VM: link(obj, 'VM')
  }
}

// -------------------------------------------------------------------

export function vif (obj) {
  return {
    type: 'VIF',

    attached: Boolean(obj.currently_attached),
    device: obj.device, // TODO: should it be cast to a number?
    MAC: obj.MAC,
    MTU: +obj.MTU,

    $network: link(obj, 'network'),
    $VM: link(obj, 'VM')
  }
}

// -------------------------------------------------------------------

export function network (obj) {
  return {
    bridge: obj.bridge,
    MTU: +obj.MTU,
    name_description: obj.name_description,
    name_label: obj.name_label,
    PIFs: link(obj, 'PIFs'),
    VIFs: link(obj, 'VIFs')
  }
}

// -------------------------------------------------------------------

export function message (obj) {
  return {
    body: obj.body,
    name: obj.name,
    time: toTimestamp(obj.timestamp),

    $object: obj.obj_uuid // Special link as it is already an UUID.
  }
}

// -------------------------------------------------------------------

export function task (obj) {
  return {
    created: toTimestamp(obj.created),
    current_operations: obj.current_operations,
    finished: toTimestamp(obj.finished),
    name_description: obj.name_description,
    name_label: obj.name_label,
    progress: +obj.progress,
    result: obj.result,
    status: obj.status,

    $host: link(obj, 'resident_on')
  }
}

// -------------------------------------------------------------------

export function host_patch (obj) {
  return {
    applied: Boolean(obj.applied),
    time: toTimestamp(obj.timestamp_applied),
    pool_patch: link(obj, 'pool_patch'),

    $host: link(obj, 'host')
  }
}

// -------------------------------------------------------------------

export function pool_patch (obj) {
  return {
    applied: Boolean(obj.pool_applied),
    name_description: obj.name_description,
    name_label: obj.name_label,
    size: +obj.size,
    version: obj.version,

    // TODO: host.[$]pool_patches ←→ pool.[$]host_patches
    $host_patches: link(obj, 'host_patches')
  }
}

// -------------------------------------------------------------------

export function pci (obj) {
  return {
    type: 'PCI',

    class_name: obj.class_name,
    device_name: obj.device_name,
    pci_id: obj.pci_id,

    $host: link(obj, 'host')
  }
}

// -------------------------------------------------------------------

export function pgpu (obj) {
  return {
    type: 'PGPU',

    pci: link(obj, 'PCI'),

    // TODO: dedupe.
    host: link(obj, 'host'),
    $host: link(obj, 'host'),
    vgpus: link(obj, 'resident_VGPUs'),
    $vgpus: link(obj, 'resident_VGPUs')
  }
}

// -------------------------------------------------------------------

export function vgpu (obj) {
  return {
    type: 'VGPU',

    currentlyAttached: Boolean(obj.currently_attached),
    device: obj.device,
    resident_on: link(obj, 'resident_on'),
    vm: link(obj, 'VM')
  }
}
<|endoftext|>


<commit_before>(function ($) {
    'use strict';

    $.fn.extend({
        personAutoComplete: function () {
            $(this).each(function () {
                var $element = $(this);
                var createUrl = $element.data('create-url');

                $element.dropdown('setting', 'allowAdditions', true);
                $element.dropdown('setting', 'hideAdditions', false);

                $element.dropdown('setting', 'onAdd', function (addedValue, addedText, $addedChoice) {
                    if ($addedChoice.hasClass('addition')) {
                        var lastName = getLastNameByConcatenatedNames(addedText);
                        var firstName = getFirstNameByConcatenatedNames(addedText);

                        postPerson($element, addedText, createUrl, lastName, firstName);

                        console.log(firstName);
                        console.log(lastName);
                    }
                });
            });
        }
    });

    function postPerson($element, addedText, url, lastName, firstName) {

        var $autoCompleteInput = $element.find('input.autocomplete');
        var values = $autoCompleteInput.val().split(',');

        $.ajax({
            type: "POST",
            url: url,
            data: {
                lastName: lastName,
                firstName: firstName
            },
            dataType: "json",
            accept: "application/json",
            success: function (data) {
                values.push(data.id);
                $autoCompleteInput.val(values.join(','));
            }
        });
    }

    /**
     * @param names
     *
     * @return string
     */
    function getLastNameByConcatenatedNames(names) {
        var nameParts = names.split(' ');

        if (nameParts.length === 1) {
            return nameParts[0];
        }

        nameParts.reverse();
        nameParts.pop();

        return nameParts.join(' ');
    }

    /**
     * @param names
     *
     * @return string|null
     */
    function getFirstNameByConcatenatedNames(names) {
        var lastName = getLastNameByConcatenatedNames(names);
        var firstName = names.replace(lastName, '').trim();

        return firstName !== "" ? firstName : null;
    }

})(jQuery);
<commit_msg>improve jsdoc for app-person-auto-complete<commit_after>(function ($) {
    'use strict';

    $.fn.extend({
        personAutoComplete: function () {
            $(this).each(function () {
                var $element = $(this);
                var createUrl = $element.data('create-url');

                $element.dropdown('setting', 'allowAdditions', true);
                $element.dropdown('setting', 'hideAdditions', false);

                $element.dropdown('setting', 'onAdd', function (addedValue, addedText, $addedChoice) {
                    if ($addedChoice.hasClass('addition')) {
                        var lastName = getLastNameByConcatenatedNames(addedText);
                        var firstName = getFirstNameByConcatenatedNames(addedText);

                        postPerson($element, addedText, createUrl, lastName, firstName);

                        console.log(firstName);
                        console.log(lastName);
                    }
                });
            });
        }
    });

    /**
     * @param {jQuery} $element
     * @param {string} addedText
     * @param {string} url
     * @param {string} lastName
     * @param {string} firstName
     */
    function postPerson($element, addedText, url, lastName, firstName) {

        var $autoCompleteInput = $element.find('input.autocomplete');
        var values = $autoCompleteInput.val().split(',');

        $.ajax({
            type: "POST",
            url: url,
            data: {
                lastName: lastName,
                firstName: firstName
            },
            dataType: "json",
            accept: "application/json",
            success: function (data) {
                values.push(data.id);
                $autoCompleteInput.val(values.join(','));
            }
        });
    }

    /**
     * @param {string} names
     *
     * @return {string}
     */
    function getLastNameByConcatenatedNames(names) {
        var nameParts = names.split(' ');

        if (nameParts.length === 1) {
            return nameParts[0];
        }

        nameParts.reverse();
        nameParts.pop();

        return nameParts.join(' ');
    }

    /**
     * @param {string} names
     *
     * @return {string|null}
     */
    function getFirstNameByConcatenatedNames(names) {
        var lastName = getLastNameByConcatenatedNames(names);
        var firstName = names.replace(lastName, '').trim();

        return firstName !== "" ? firstName : null;
    }

})(jQuery);
<|endoftext|>


<commit_before>define([
  'jquery',
  'underscore',
  'backbone'
], function($, _, Backbone){
  var Tweets = Backbone.Collection.extend({
  	initialize: function(caller){
  		this.caller = caller;
  		this.getData();
  	},
    url: function () {
      return 'http://twitter.com/statuses/user_timeline/ITS_A_NERD.json?callback=?'
    },
    getData: function(){
    	var that = this;
      $.ajax({

      	url: that.url(),
    type: 'GET',
    crossDomain: true,
    dataType: 'json',
    success: function(data) {
    	console.log(data);
       	that.twitterStream = data;
        that.caller.buildTweets(that.twitterStream);},
    error: function() { alert('Failed!'); }
    });

    },

    // Because twitter doesn't return an array of models by default we need
    // to point Backbone.js at the correct property
    parse: function(resp, xhr) {
      return resp.results;
    }
  });

  return Tweets;
});<commit_msg>nav to second mainView Example - Twitter<commit_after>define([
  'jquery',
  'underscore',
  'backbone'
], function($, _, Backbone){
  var Tweets = Backbone.Collection.extend({
  	initialize: function(caller){
  		this.caller = caller;
  		this.getData();
  	},
    url: function () {
      return 'http://twitter.com/statuses/user_timeline/ITS_A_NERD.json?callback=?'
    },
    getData: function(){
    	var that = this;
      $.ajax({
      	url: that.url(),
    type: 'GET',
    crossDomain: true,
    dataType: 'json',
    success: function(data) {
    	console.log(data);
       	that.twitterStream = data;
        that.caller.buildTweets(that.twitterStream);},
    error: function() { alert('Failed!'); }
    });

    },

    // Because twitter doesn't return an array of models by default we need
    // to point Backbone.js at the correct property
    parse: function(resp, xhr) {
      return resp.results;
    }
  });

  return Tweets;
});<|endoftext|>