<commit_before>/* Environment: Discord -- This environment connects to a Discord server/guild. */

const Environment = require('../Environment.js');

class EnvDiscord extends Environment {

    get requiredParams() { return [
        'token',                //Discord application token
        'servername',           //Server name to operate on (application must have been previously added to server)
        'defaultchannel',       //Default channel to operate on (must be a channel in the above server)
        'privatemessages'       //Environment will receive private messages to the bot
    ]; }

    get optionalParams() { return [
        'senddelay'             //Message queue send delay (ms)
    ]; }

    get sharedModules() { return [
        'DiscordClient'
    ]; }

    constructor(name) {
        super('Discord', name);

        this._params['senddelay'] = 500;

        this._localClient = null;  //DiscordClient (manages shared discord.js Client object)
        this._client = null;  //Actual discord.js Client object
        this._server = null;  //discord.js Guild object (formerly Server) for the server identified by this environment's 'servername'.
        this._channels = {};
    }

    initialize(sharedInstances) {
        if (!super.initialize(sharedInstances)) return false;
        this._localClient = sharedInstances.DiscordClient;
        return true;
    }

    connect() {

        let params = this.params;

        this.log(`Connecting to ${params.servername}`);

        return this._localClient.prepareClient(this, this.param('token'), this.param('sendDelay'))
            .then((client) => {
                this._client = client;
                this._server = client.guilds.cache.find(s => s.name == params.servername);

                if (!this._server) {
                    this.log('error', "Could not obtain server object.");
                }

                this._channels[params.defaultchannel] = this._server.channels.cache.filter(channel => channel.type == "text").find(channel => channel.name == params.defaultchannel);

                this._localClient.on("message", (message) => {
                    if (message.author.username == client.user.username) return;

                    let type = "regular";
                    let channelid = message.channel.id;

                    if (message.channel.type == "dm") {
                        if (!this.param('privatemessages')) return;
                        type = "private";
                        channelid = message.author.id;
                    } else {
                        if (message.channel.guild.id != this._server.id) return;
                    }

                    this.emit('message', this, type, message.content, message.author.id, channelid, message);
                });

                this._localClient.on("guildMemberAdd", (member) => {
                    if (member.guild.id != this._server.id) return;
                    let chans = this.findAccessChannels(member);
                    if (chans.length) {
                        this.triggerJoin(member.id, chans, {reason: "add"});
                    }
                    let roles = member.roles.cache.array();
                    for (let role of roles) {
                        this.emit('gotRole', this, member.id, role.id);
                    }
                });

                this._localClient.on("guildMemberRemove", (member) => {
                    if (member.user.presence.status == "offline") return;
                    if (member.guild.id != this._server.id) return;

                    let chans = this.findAccessChannels(member);
                    if (chans.length) {
                        this.triggerPart(member.id, chans, {reason: "remove"});
                    }
                    let roles = member.roles.cache.array();
                    for (let role of roles) {
                        this.emit('lostRole', this, member.id, role.id);
                    }
                });

                this._localClient.on("guildMemberUpdate", (oldMember, newMember) => {
                    if (newMember.user.presence.status == "offline") return;
                    if (newMember.guild.id != this._server.id) return;

                    //Channels

                    let had = {};
                    for (let chan of this.findAccessChannels(oldMember)) {
                        had[chan.id] = chan;
                    }

                    let tojoin = [];
                    let topart = [];

                    for (let chan of this.findAccessChannels(newMember)) {
                        if (!had[chan.id]) tojoin.push(chan);
                        else delete had[chan.id];
                    }
                    for (let chanid in had) {
                        topart.push(had[chanid]);
                    }

                    if (tojoin.length) {
                        this.triggerJoin(newMember.id, tojoin, {reason: "permissions"});
                    }
                    if (topart.length) {
                        this.triggerPart(oldMember.id, topart, {reason: "permissions"});
                    }

                    //Roles

                    had = {};
                    for (let role of oldMember.roles.cache) {
                        had[role.id] = role;
                    }

                    let toget = [];
                    let tolose = [];

                    for (let role of newMember.roles.cache) {
                        if (!had[role.id]) toget.push(role);
                        else delete had[role.id];
                    }
                    for (let roleid in had) {
                        tolose.push(had[roleid]);
                    }

                    for (let role of toget) {
                        this.emit('gotRole', this, newMember.id, role.id, null, true);
                    }

                    for (let role of tolose) {
                        this.emit('lostRole', this, newMember.id, role.id, null, true);
                    }

                });

                this._localClient.on("presenceUpdate", (oldPresence, newPresence) => {
                    let reason = null;

                    if (!oldPresence) return;

                    if (oldPresence.status == "offline" && newPresence.status != "offline") {
                        reason = "join";
                    }
                    if (oldPresence.status != "offline" && newPresence.status == "offline") {
                        reason = "part";
                    }
                    if (!reason) return;

                    let member = this._server.members.cache.get(newPresence.userID);
                    if (!member) return;
                    let chans = this.findAccessChannels(member);

                    if (reason == "join") {
                        this.triggerJoin(member.id, chans, {reason: reason, status: newPresence.status});
                    }
                    if (reason == "part") {
                        if (member) this.triggerPart(member.id, chans, {reason: reason, status: oldPresence.status});
                    }
                });

                this.log("Environment is now ready!");

                this._hasConnected = true;
                this.emit('connected', this);
            });
    }

    disconnect() {
        this._localClient.detachFromClient(this)
            .then(() => {
                this._client = null;
                this._server = null;
                this.log(`Disconnected from ${this._name}`);
                this.emit('disconnected', this);
            });
    }

    msg(targetid, msg) {
        let targetchan = this.getActualChanFromTarget(targetid);

        if (!targetchan) {
            targetchan = this._channels[this.param('defaultchannel')];
        }

        this._localClient.outbox(targetchan, msg);
    }

    notice(targetid, msg) {
        this.msg(targetid, msg);
    }

    idToDisplayName(id) {
        let member = this._server.members.cache.get(id);
        if (member) return (member.nickname ? member.nickname : member.user.username);
        return id;
    }

    displayNameToId(displayname) {
        let refuser = null;

        let parts = displayname.split("#");
        if (parts[1]) {
            refuser = this._server.members.cache.filter(member => member.user.username == parts[0]).find(member => member.user.discriminator == parts[1]);
        } else {
            let cache = this._server.members.cache.filter(member => member.user.username == displayname).array();
            if (cache.length == 1) {
                refuser = cache[0];
            } else {
                displayname = displayname.toLowerCase();
                refuser = this._server.members.cache.find(member => member.nickname && member.nickname.toLowerCase() == displayname);
            }
        }

        if (refuser) {
            return refuser.id;
        }

        return null;
    }

    idToMention(id) {
        return "<@" + id + ">";
    }

    idIsSecured(id) {
        return !!this._server.members.cache.get(id);
    }

    idIsAuthenticated(id) {
        return !!this._server.members.cache.get(id);
    }

    listUserIds(channel) {
        let targetchan = this.getActualChanFromTarget(channel);
        if (!targetchan) {
            targetchan = this._channels[this.param('defaultchannel')];
        }

        if (targetchan.type == "dm" || !targetchan.type) return [targetchan.recipient.id];

        let ids = [];
        if (targetchan.type == "text") {
            for (let member of targetchan.members.array()) {
                ids.push(member.id);
            }
        }

        return ids;
    }

    listUserRoles(id, channel) {
        let member = this._server.members.cache.get(id);
        if (!member) return [];
        let result = [];
        let roles = member.roles.cache.array().sort((a, b) => (b.position - a.position));
        for (let role of roles) {
            result.push(role.id);
        }
        return result;
    }

    channelIdToDisplayName(channelid) {
        let channel = this._server.channels.cache.get(channelid);
        if (channel) return channel.name;
        return channelid;
    }

    channelIdToType(channelid) {
        let chan = this.getActualChanFromTarget(channelid);
        if (!chan) return "unknown";
        if (!chan.type || chan.type == "dm") return "private";
        return "regular";
    }

    roleIdToDisplayName(roleid) {
        let role = this._server.roles.cache.get(roleid);
        if (role) return role.name;
        return roleid;
    }

    displayNameToRoleId(displayName) {
        if (!displayName) return null;
        let role = this._server.roles.cache.find(r => r.name.toLowerCase() == displayName.toLowerCase());
        if (role) return role.id;
        return null;
    }

    normalizeFormatting(text) {
        text = String(text).replace(/<@&([0-9]+)>/g, (match, id) => {
            let role = this._server.roles.cache.get(id);
            if (!role) return "";
            return "@" + role.name;
        });

        text = text.replace(/<@!?([0-9]+)>/g, (match, id) => {
            let user = this._server.members.cache.get(id);
            if (!user) return "";
            return "@" + (user.nickname ? user.nickname : user.user.username);
        });

        text = text.replace(/<#([0-9]+)>/g, (match, id) => {
            let chan = this._server.channels.cache.get(id);
            if (!chan) return "";
            return "#" + chan.name;
        });

        return text.replace(/~~(.*?)~~/g, "$1").replace(/(^|[^a-z0-9])_(.*?)_([^a-z0-9]|$)/gi, "$1*$2*$3");
    }

    applyFormatting(text) {
        //Normalized formatting is already fully compatible with Discord
        return String(text);
    }

    //Auxiliary methods

    get client() { return this._localClient; }
    get server() { return this._server; }

    getActualChanFromTarget(targetid) {
        let targetchan = null;

        if (typeof targetid == "string") {
            if (!this._channels[targetid]) {
                this._channels[targetid] = this._server.channels.cache.filter(channel => channel.type == "text").get(targetid);
            }
            if (!this._channels[targetid]) {
                this._channels[targetid] = this._server.members.cache.get(targetid);
            }
            if (!this._channels[targetid]) {
                this._channels[targetid] = this._server.channels.cache.filter(channel => channel.type == "text").find(channel => channel.name == targetid);
            }
            if (!this._channels[targetid]) {
                this._channels[targetid] = this._server.members.cache.find(channel => channel.name == targetid);
            }
            if (this._channels[targetid]) {
                targetchan = this._channels[targetid];
            }
        } else {
            targetchan = targetid;
        }

        return targetchan;
    }

    findAccessChannels(member) {
        let channels = [];
        if (!member) return channels;

        let allchannels = member.guild.channels.cache.array();
        for (let channel of allchannels) {
            let pfm = channel.permissionsFor(member);
            if (!pfm || !pfm.has) continue;
            if (pfm.has("VIEW_CHANNEL")) {
                channels.push(channel);
            }
        }

        return channels;
    }

    triggerJoin(authorid, channels, info) {
        if (!info) info = {};
        for (let channel of channels) {
            this.emit('join', this, authorid, channel.id, info);
        }
    }

    triggerPart(authorid, channels, info) {
        if (!info) info = {};
        for (let channel of channels) {
            this.emit('part', this, authorid, channel.id, info);
        }
    }

};

module.exports = EnvDiscord;
<commit_msg>Use user cache in addition to member cache to obtain a display name for Discord users<commit_after>/* Environment: Discord -- This environment connects to a Discord server/guild. */

const Environment = require('../Environment.js');

class EnvDiscord extends Environment {

    get requiredParams() { return [
        'token',                //Discord application token
        'servername',           //Server name to operate on (application must have been previously added to server)
        'defaultchannel',       //Default channel to operate on (must be a channel in the above server)
        'privatemessages'       //Environment will receive private messages to the bot
    ]; }

    get optionalParams() { return [
        'senddelay'             //Message queue send delay (ms)
    ]; }

    get sharedModules() { return [
        'DiscordClient'
    ]; }

    constructor(name) {
        super('Discord', name);

        this._params['senddelay'] = 500;

        this._localClient = null;  //DiscordClient (manages shared discord.js Client object)
        this._client = null;  //Actual discord.js Client object
        this._server = null;  //discord.js Guild object (formerly Server) for the server identified by this environment's 'servername'.
        this._channels = {};
    }

    initialize(sharedInstances) {
        if (!super.initialize(sharedInstances)) return false;
        this._localClient = sharedInstances.DiscordClient;
        return true;
    }

    connect() {

        let params = this.params;

        this.log(`Connecting to ${params.servername}`);

        return this._localClient.prepareClient(this, this.param('token'), this.param('sendDelay'))
            .then((client) => {
                this._client = client;
                this._server = client.guilds.cache.find(s => s.name == params.servername);

                if (!this._server) {
                    this.log('error', "Could not obtain server object.");
                }

                this._channels[params.defaultchannel] = this._server.channels.cache.filter(channel => channel.type == "text").find(channel => channel.name == params.defaultchannel);

                this._localClient.on("message", (message) => {
                    if (message.author.username == client.user.username) return;

                    let type = "regular";
                    let channelid = message.channel.id;

                    if (message.channel.type == "dm") {
                        if (!this.param('privatemessages')) return;
                        type = "private";
                        channelid = message.author.id;
                    } else {
                        if (message.channel.guild.id != this._server.id) return;
                    }

                    this.emit('message', this, type, message.content, message.author.id, channelid, message);
                });

                this._localClient.on("guildMemberAdd", (member) => {
                    if (member.guild.id != this._server.id) return;
                    let chans = this.findAccessChannels(member);
                    if (chans.length) {
                        this.triggerJoin(member.id, chans, {reason: "add"});
                    }
                    let roles = member.roles.cache.array();
                    for (let role of roles) {
                        this.emit('gotRole', this, member.id, role.id);
                    }
                });

                this._localClient.on("guildMemberRemove", (member) => {
                    if (member.user.presence.status == "offline") return;
                    if (member.guild.id != this._server.id) return;

                    let chans = this.findAccessChannels(member);
                    if (chans.length) {
                        this.triggerPart(member.id, chans, {reason: "remove"});
                    }
                    let roles = member.roles.cache.array();
                    for (let role of roles) {
                        this.emit('lostRole', this, member.id, role.id);
                    }
                });

                this._localClient.on("guildMemberUpdate", (oldMember, newMember) => {
                    if (newMember.user.presence.status == "offline") return;
                    if (newMember.guild.id != this._server.id) return;

                    //Channels

                    let had = {};
                    for (let chan of this.findAccessChannels(oldMember)) {
                        had[chan.id] = chan;
                    }

                    let tojoin = [];
                    let topart = [];

                    for (let chan of this.findAccessChannels(newMember)) {
                        if (!had[chan.id]) tojoin.push(chan);
                        else delete had[chan.id];
                    }
                    for (let chanid in had) {
                        topart.push(had[chanid]);
                    }

                    if (tojoin.length) {
                        this.triggerJoin(newMember.id, tojoin, {reason: "permissions"});
                    }
                    if (topart.length) {
                        this.triggerPart(oldMember.id, topart, {reason: "permissions"});
                    }

                    //Roles

                    had = {};
                    for (let role of oldMember.roles.cache) {
                        had[role.id] = role;
                    }

                    let toget = [];
                    let tolose = [];

                    for (let role of newMember.roles.cache) {
                        if (!had[role.id]) toget.push(role);
                        else delete had[role.id];
                    }
                    for (let roleid in had) {
                        tolose.push(had[roleid]);
                    }

                    for (let role of toget) {
                        this.emit('gotRole', this, newMember.id, role.id, null, true);
                    }

                    for (let role of tolose) {
                        this.emit('lostRole', this, newMember.id, role.id, null, true);
                    }

                });

                this._localClient.on("presenceUpdate", (oldPresence, newPresence) => {
                    let reason = null;

                    if (!oldPresence) return;

                    if (oldPresence.status == "offline" && newPresence.status != "offline") {
                        reason = "join";
                    }
                    if (oldPresence.status != "offline" && newPresence.status == "offline") {
                        reason = "part";
                    }
                    if (!reason) return;

                    let member = this._server.members.cache.get(newPresence.userID);
                    if (!member) return;
                    let chans = this.findAccessChannels(member);

                    if (reason == "join") {
                        this.triggerJoin(member.id, chans, {reason: reason, status: newPresence.status});
                    }
                    if (reason == "part") {
                        if (member) this.triggerPart(member.id, chans, {reason: reason, status: oldPresence.status});
                    }
                });

                this.log("Environment is now ready!");

                this._hasConnected = true;
                this.emit('connected', this);
            });
    }

    disconnect() {
        this._localClient.detachFromClient(this)
            .then(() => {
                this._client = null;
                this._server = null;
                this.log(`Disconnected from ${this._name}`);
                this.emit('disconnected', this);
            });
    }

    msg(targetid, msg) {
        let targetchan = this.getActualChanFromTarget(targetid);

        if (!targetchan) {
            targetchan = this._channels[this.param('defaultchannel')];
        }

        this._localClient.outbox(targetchan, msg);
    }

    notice(targetid, msg) {
        this.msg(targetid, msg);
    }

    idToDisplayName(id) {
        let member = this._server.members.cache.get(id);
        if (member) return (member.nickname ? member.nickname : member.user.username);
        let user = this._client.users.cache.get(id);
        if (user) return user.username;
        return id;
    }

    displayNameToId(displayname) {
        let refuser = null;

        let parts = displayname.split("#");

        if (parts[1]) {
            refuser = this._server.members.cache.filter(member => member.user.username == parts[0]).find(member => member.user.discriminator == parts[1]);
        } else {
            let cache = this._server.members.cache.filter(member => member.user.username == displayname).array();
            if (cache.length == 1) {
                refuser = cache[0];
            } else {
                displayname = displayname.toLowerCase();
                refuser = this._server.members.cache.find(member => member.nickname && member.nickname.toLowerCase() == displayname);
            }
        }

        if (refuser) {
            return refuser.id;
        }

        return null;
    }

    idToMention(id) {
        return "<@" + id + ">";
    }

    idIsSecured(id) {
        return !!this._server.members.cache.get(id);
    }

    idIsAuthenticated(id) {
        return !!this._server.members.cache.get(id);
    }

    listUserIds(channel) {
        let targetchan = this.getActualChanFromTarget(channel);
        if (!targetchan) {
            targetchan = this._channels[this.param('defaultchannel')];
        }

        if (targetchan.type == "dm" || !targetchan.type) return [targetchan.recipient.id];

        let ids = [];
        if (targetchan.type == "text") {
            for (let member of targetchan.members.array()) {
                ids.push(member.id);
            }
        }

        return ids;
    }

    listUserRoles(id, channel) {
        let member = this._server.members.cache.get(id);
        if (!member) return [];
        let result = [];
        let roles = member.roles.cache.array().sort((a, b) => (b.position - a.position));
        for (let role of roles) {
            result.push(role.id);
        }
        return result;
    }

    channelIdToDisplayName(channelid) {
        let channel = this._server.channels.cache.get(channelid);
        if (channel) return channel.name;
        return channelid;
    }

    channelIdToType(channelid) {
        let chan = this.getActualChanFromTarget(channelid);
        if (!chan) return "unknown";
        if (!chan.type || chan.type == "dm") return "private";
        return "regular";
    }

    roleIdToDisplayName(roleid) {
        let role = this._server.roles.cache.get(roleid);
        if (role) return role.name;
        return roleid;
    }

    displayNameToRoleId(displayName) {
        if (!displayName) return null;
        let role = this._server.roles.cache.find(r => r.name.toLowerCase() == displayName.toLowerCase());
        if (role) return role.id;
        return null;
    }

    normalizeFormatting(text) {
        text = String(text).replace(/<@&([0-9]+)>/g, (match, id) => {
            let role = this._server.roles.cache.get(id);
            if (!role) return "";
            return "@" + role.name;
        });

        text = text.replace(/<@!?([0-9]+)>/g, (match, id) => {
            let user = this._server.members.cache.get(id);
            if (!user) return "";
            return "@" + (user.nickname ? user.nickname : user.user.username);
        });

        text = text.replace(/<#([0-9]+)>/g, (match, id) => {
            let chan = this._server.channels.cache.get(id);
            if (!chan) return "";
            return "#" + chan.name;
        });

        return text.replace(/~~(.*?)~~/g, "$1").replace(/(^|[^a-z0-9])_(.*?)_([^a-z0-9]|$)/gi, "$1*$2*$3");
    }

    applyFormatting(text) {
        //Normalized formatting is already fully compatible with Discord
        return String(text);
    }

    //Auxiliary methods

    get client() { return this._localClient; }
    get server() { return this._server; }

    getActualChanFromTarget(targetid) {
        let targetchan = null;

        if (typeof targetid == "string") {
            if (!this._channels[targetid]) {
                this._channels[targetid] = this._server.channels.cache.filter(channel => channel.type == "text").get(targetid);
            }
            if (!this._channels[targetid]) {
                this._channels[targetid] = this._server.members.cache.get(targetid);
            }
            if (!this._channels[targetid]) {
                this._channels[targetid] = this._server.channels.cache.filter(channel => channel.type == "text").find(channel => channel.name == targetid);
            }
            if (!this._channels[targetid]) {
                this._channels[targetid] = this._server.members.cache.find(channel => channel.name == targetid);
            }
            if (this._channels[targetid]) {
                targetchan = this._channels[targetid];
            }
        } else {
            targetchan = targetid;
        }

        return targetchan;
    }

    findAccessChannels(member) {
        let channels = [];
        if (!member) return channels;

        let allchannels = member.guild.channels.cache.array();
        for (let channel of allchannels) {
            let pfm = channel.permissionsFor(member);
            if (!pfm || !pfm.has) continue;
            if (pfm.has("VIEW_CHANNEL")) {
                channels.push(channel);
            }
        }

        return channels;
    }

    triggerJoin(authorid, channels, info) {
        if (!info) info = {};
        for (let channel of channels) {
            this.emit('join', this, authorid, channel.id, info);
        }
    }

    triggerPart(authorid, channels, info) {
        if (!info) info = {};
        for (let channel of channels) {
            this.emit('part', this, authorid, channel.id, info);
        }
    }

};

module.exports = EnvDiscord;
<|endoftext|>


<commit_before>/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

'use strict';

var EXPORTED_SYMBOLS = ['PdfStreamConverter'];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
// True only if this is the version of pdf.js that is included with firefox.
const MOZ_CENTRAL = PDFJSSCRIPT_MOZ_CENTRAL;
const PDFJS_EVENT_ID = 'pdf.js.message';
const PDF_CONTENT_TYPE = 'application/pdf';
const PREF_PREFIX = 'PDFJSSCRIPT_PREF_PREFIX';
const MAX_DATABASE_LENGTH = 4096;
const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}';
const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}';

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/NetUtil.jsm');

let appInfo = Cc['@mozilla.org/xre/app-info;1']
                  .getService(Ci.nsIXULAppInfo);
let privateBrowsing, inPrivateBrowsing;
let Svc = {};
XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
                                   '@mozilla.org/mime;1',
                                   'nsIMIMEService');

if (appInfo.ID === FIREFOX_ID) {
  privateBrowsing = Cc['@mozilla.org/privatebrowsing;1']
                          .getService(Ci.nsIPrivateBrowsingService);
  inPrivateBrowsing = privateBrowsing.privateBrowsingEnabled;
} else if (appInfo.ID === SEAMONKEY_ID) {
  privateBrowsing = null;
  inPrivateBrowsing = false;
}

function getBoolPref(pref, def) {
  try {
    return Services.prefs.getBoolPref(pref);
  } catch (ex) {
    return def;
  }
}

function setStringPref(pref, value) {
  let str = Cc['@mozilla.org/supports-string;1']
              .createInstance(Ci.nsISupportsString);
  str.data = value;
  Services.prefs.setComplexValue(pref, Ci.nsISupportsString, str);
}

function getStringPref(pref, def) {
  try {
    return Services.prefs.getComplexValue(pref, Ci.nsISupportsString).data;
  } catch (ex) {
    return def;
  }
}

function log(aMsg) {
  if (!getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false))
    return;
  let msg = 'PdfStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
  Services.console.logStringMessage(msg);
  dump(msg + '\n');
}

function getDOMWindow(aChannel) {
  var requestor = aChannel.notificationCallbacks;
  var win = requestor.getInterface(Components.interfaces.nsIDOMWindow);
  return win;
}

function isEnabled() {
  if (MOZ_CENTRAL) {
    var disabled = getBoolPref(PREF_PREFIX + '.disabled', false);
    if (disabled)
      return false;
    // To also be considered enabled the "Preview in Firefox" option must be
    // selected in the Application preferences.
    var handlerInfo = Svc.mime
                         .getFromTypeAndExtension('application/pdf', 'pdf');
    return handlerInfo.alwaysAskBeforeHandling == false &&
           handlerInfo.preferredAction == Ci.nsIHandlerInfo.handleInternally;
  }
  // Always returns true for the extension since enabling/disabling is handled
  // by the add-on manager.
  return true;
}

function getLocalizedStrings(path) {
  var stringBundle = Cc['@mozilla.org/intl/stringbundle;1'].
      getService(Ci.nsIStringBundleService).
      createBundle('chrome://pdf.js/locale/' + path);

  var map = {};
  var enumerator = stringBundle.getSimpleEnumeration();
  while (enumerator.hasMoreElements()) {
    var string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
    var key = string.key, property = 'textContent';
    var i = key.lastIndexOf('.');
    if (i >= 0) {
      property = key.substring(i + 1);
      key = key.substring(0, i);
    }
    if (!(key in map))
      map[key] = {};
    map[key][property] = string.value;
  }
  return map;
}
function getLocalizedString(strings, id, property) {
  property = property || 'textContent';
  if (id in strings)
    return strings[id][property];
  return id;
}

// All the priviledged actions.
function ChromeActions(domWindow) {
  this.domWindow = domWindow;
}

ChromeActions.prototype = {
  download: function(data, sendResponse) {
    var originalUrl = data.originalUrl;
    // The data may not be downloaded so we need just retry getting the pdf with
    // the original url.
    var blobUrl = data.blobUrl || originalUrl;
    var originalUri = NetUtil.newURI(originalUrl);
    var blobUri = NetUtil.newURI(blobUrl);
    var extHelperAppSvc =
          Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
             getService(Ci.nsIExternalHelperAppService);
    var frontWindow = Cc['@mozilla.org/embedcomp/window-watcher;1'].
                         getService(Ci.nsIWindowWatcher).activeWindow;
    var ioService = Services.io;
    var channel = ioService.newChannel(originalUrl, null, null);

    NetUtil.asyncFetch(blobUri, function(aInputStream, aResult) {
      if (!Components.isSuccessCode(aResult)) {
        if (sendResponse)
          sendResponse(true);
        return;
      }
      // Create a nsIInputStreamChannel so we can set the url on the channel
      // so the filename will be correct.
      let channel = Cc['@mozilla.org/network/input-stream-channel;1'].
                       createInstance(Ci.nsIInputStreamChannel);
      channel.setURI(originalUri);
      channel.contentStream = aInputStream;
      channel.QueryInterface(Ci.nsIChannel);

      var listener = {
        extListener: null,
        onStartRequest: function(aRequest, aContext) {
          this.extListener = extHelperAppSvc.doContent('application/pdf',
                                aRequest, frontWindow, false);
          this.extListener.onStartRequest(aRequest, aContext);
        },
        onStopRequest: function(aRequest, aContext, aStatusCode) {
          if (this.extListener)
            this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
          // Notify the content code we're done downloading.
          if (sendResponse)
            sendResponse(false);
        },
        onDataAvailable: function(aRequest, aContext, aInputStream, aOffset,
                                  aCount) {
          this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
                                           aOffset, aCount);
        }
      };

      channel.asyncOpen(listener, null);
    });
  },
  setDatabase: function(data) {
    if (inPrivateBrowsing)
      return;
    // Protect against something sending tons of data to setDatabase.
    if (data.length > MAX_DATABASE_LENGTH)
      return;
    setStringPref(PREF_PREFIX + '.database', data);
  },
  getDatabase: function() {
    if (inPrivateBrowsing)
      return '{}';
    return getStringPref(PREF_PREFIX + '.database', '{}');
  },
  getLocale: function() {
    return getStringPref('general.useragent.locale', 'en-US');
  },
  getStrings: function(data) {
    try {
      // Lazy initialization of localizedStrings
      if (!('localizedStrings' in this))
        this.localizedStrings = getLocalizedStrings('viewer.properties');

      var result = this.localizedStrings[data];
      return JSON.stringify(result || null);
    } catch (e) {
      log('Unable to retrive localized strings: ' + e);
      return 'null';
    }
  },
  pdfBugEnabled: function() {
    return getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false);
  },
  searchEnabled: function() {
    return getBoolPref(PREF_PREFIX + '.searchEnabled', false);
  },
  fallback: function(url, sendResponse) {
    var self = this;
    var domWindow = this.domWindow;
    var strings = getLocalizedStrings('chrome.properties');
    var message = getLocalizedString(strings, 'unsupported_feature');

    var win = Services.wm.getMostRecentWindow('navigator:browser');
    var browser = win.gBrowser.getBrowserForDocument(domWindow.top.document);
    var notificationBox = win.gBrowser.getNotificationBox(browser);
    // Flag so we don't call the response callback twice, since if the user
    // clicks open with different viewer both the button callback and
    // eventCallback will be called.
    var sentResponse = false;
    var buttons = [{
      label: getLocalizedString(strings, 'open_with_different_viewer'),
      accessKey: getLocalizedString(strings, 'open_with_different_viewer',
                                    'accessKey'),
      callback: function() {
        sentResponse = true;
        sendResponse(true);
      }
    }];
    notificationBox.appendNotification(message, 'pdfjs-fallback', null,
                                       notificationBox.PRIORITY_WARNING_LOW,
                                       buttons,
                                       function eventsCallback(eventType) {
      // Currently there is only one event "removed" but if there are any other
      // added in the future we still only care about removed at the moment.
      if (eventType !== 'removed')
        return;
      // Don't send a response again if we already responded when the button was
      // clicked.
      if (!sentResponse)
        sendResponse(false);
    });
  }
};

// Event listener to trigger chrome privedged code.
function RequestListener(actions) {
  this.actions = actions;
}
// Receive an event and synchronously or asynchronously responds.
RequestListener.prototype.receive = function(event) {
  var message = event.target;
  var doc = message.ownerDocument;
  var action = message.getUserData('action');
  var data = message.getUserData('data');
  var sync = message.getUserData('sync');
  var actions = this.actions;
  if (!(action in actions)) {
    log('Unknown action: ' + action);
    return;
  }
  if (sync) {
    var response = actions[action].call(this.actions, data);
    message.setUserData('response', response, null);
  } else {
    var response;
    if (!message.getUserData('callback')) {
      doc.documentElement.removeChild(message);
      response = null;
    } else {
      response = function sendResponse(response) {
        message.setUserData('response', response, null);

        var listener = doc.createEvent('HTMLEvents');
        listener.initEvent('pdf.js.response', true, false);
        return message.dispatchEvent(listener);
      }
    }
    actions[action].call(this.actions, data, response);
  }
};

function PdfStreamConverter() {
}

PdfStreamConverter.prototype = {

  // properties required for XPCOM registration:
  classID: Components.ID('{PDFJSSCRIPT_STREAM_CONVERTER_ID}'),
  classDescription: 'pdf.js Component',
  contractID: '@mozilla.org/streamconv;1?from=application/pdf&to=*/*',

  QueryInterface: XPCOMUtils.generateQI([
      Ci.nsISupports,
      Ci.nsIStreamConverter,
      Ci.nsIStreamListener,
      Ci.nsIRequestObserver
  ]),

  /*
   * This component works as such:
   * 1. asyncConvertData stores the listener
   * 2. onStartRequest creates a new channel, streams the viewer and cancels
   *    the request so pdf.js can do the request
   * Since the request is cancelled onDataAvailable should not be called. The
   * onStopRequest does nothing. The convert function just returns the stream,
   * it's just the synchronous version of asyncConvertData.
   */

  // nsIStreamConverter::convert
  convert: function(aFromStream, aFromType, aToType, aCtxt) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  // nsIStreamConverter::asyncConvertData
  asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
    if (!isEnabled())
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;
    // Ignoring HTTP POST requests -- pdf.js has to repeat the request.
    var skipConversion = false;
    try {
      var request = aCtxt;
      request.QueryInterface(Ci.nsIHttpChannel);
      skipConversion = (request.requestMethod !== 'GET');
    } catch (e) {
      // Non-HTTP request... continue normally.
    }
    if (skipConversion)
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;

    // Store the listener passed to us
    this.listener = aListener;
  },

  // nsIStreamListener::onDataAvailable
  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
    // Do nothing since all the data loading is handled by the viewer.
    log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!');
  },

  // nsIRequestObserver::onStartRequest
  onStartRequest: function(aRequest, aContext) {

    // Setup the request so we can use it below.
    aRequest.QueryInterface(Ci.nsIChannel);
    // Cancel the request so the viewer can handle it.
    aRequest.cancel(Cr.NS_BINDING_ABORTED);

    // Create a new channel that is viewer loaded as a resource.
    var ioService = Services.io;
    var channel = ioService.newChannel(
                    'resource://pdf.js/web/viewer.html', null, null);

    var listener = this.listener;
    // Proxy all the request observer calls, when it gets to onStopRequest
    // we can get the dom window.
    var proxy = {
      onStartRequest: function() {
        listener.onStartRequest.apply(listener, arguments);
      },
      onDataAvailable: function() {
        listener.onDataAvailable.apply(listener, arguments);
      },
      onStopRequest: function() {
        var domWindow = getDOMWindow(channel);
        // Double check the url is still the correct one.
        if (domWindow.document.documentURIObject.equals(aRequest.URI)) {
          let requestListener = new RequestListener(
                                      new ChromeActions(domWindow));
          domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {
            requestListener.receive(event);
          }, false, true);
        }
        listener.onStopRequest.apply(listener, arguments);
      }
    };

    // Keep the URL the same so the browser sees it as the same.
    channel.originalURI = aRequest.URI;
    channel.asyncOpen(proxy, aContext);
  },

  // nsIRequestObserver::onStopRequest
  onStopRequest: function(aRequest, aContext, aStatusCode) {
    // Do nothing.
  }
};

var NSGetFactory = XPCOMUtils.generateNSGetFactory([PdfStreamConverter]);
<commit_msg>Address moz central comments for ff16.<commit_after>/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

'use strict';

var EXPORTED_SYMBOLS = ['PdfStreamConverter'];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
// True only if this is the version of pdf.js that is included with firefox.
const MOZ_CENTRAL = PDFJSSCRIPT_MOZ_CENTRAL;
const PDFJS_EVENT_ID = 'pdf.js.message';
const PDF_CONTENT_TYPE = 'application/pdf';
const PREF_PREFIX = 'PDFJSSCRIPT_PREF_PREFIX';
const MAX_DATABASE_LENGTH = 4096;
const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}';
const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}';

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/NetUtil.jsm');

let appInfo = Cc['@mozilla.org/xre/app-info;1']
                  .getService(Ci.nsIXULAppInfo);
let privateBrowsing, inPrivateBrowsing;
let Svc = {};
XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
                                   '@mozilla.org/mime;1',
                                   'nsIMIMEService');

if (appInfo.ID === FIREFOX_ID) {
  privateBrowsing = Cc['@mozilla.org/privatebrowsing;1']
                          .getService(Ci.nsIPrivateBrowsingService);
  inPrivateBrowsing = privateBrowsing.privateBrowsingEnabled;
} else if (appInfo.ID === SEAMONKEY_ID) {
  privateBrowsing = null;
  inPrivateBrowsing = false;
}

function getBoolPref(pref, def) {
  try {
    return Services.prefs.getBoolPref(pref);
  } catch (ex) {
    return def;
  }
}

function setStringPref(pref, value) {
  let str = Cc['@mozilla.org/supports-string;1']
              .createInstance(Ci.nsISupportsString);
  str.data = value;
  Services.prefs.setComplexValue(pref, Ci.nsISupportsString, str);
}

function getStringPref(pref, def) {
  try {
    return Services.prefs.getComplexValue(pref, Ci.nsISupportsString).data;
  } catch (ex) {
    return def;
  }
}

function log(aMsg) {
  if (!getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false))
    return;
  let msg = 'PdfStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
  Services.console.logStringMessage(msg);
  dump(msg + '\n');
}

function getDOMWindow(aChannel) {
  var requestor = aChannel.notificationCallbacks;
  var win = requestor.getInterface(Components.interfaces.nsIDOMWindow);
  return win;
}

function isEnabled() {
  if (MOZ_CENTRAL) {
    var disabled = getBoolPref(PREF_PREFIX + '.disabled', false);
    if (disabled)
      return false;
    // To also be considered enabled the "Preview in Firefox" option must be
    // selected in the Application preferences.
    var handlerInfo = Svc.mime
                         .getFromTypeAndExtension('application/pdf', 'pdf');
    return handlerInfo.alwaysAskBeforeHandling == false &&
           handlerInfo.preferredAction == Ci.nsIHandlerInfo.handleInternally;
  }
  // Always returns true for the extension since enabling/disabling is handled
  // by the add-on manager.
  return true;
}

function getLocalizedStrings(path) {
  var stringBundle = Cc['@mozilla.org/intl/stringbundle;1'].
      getService(Ci.nsIStringBundleService).
      createBundle('chrome://pdf.js/locale/' + path);

  var map = {};
  var enumerator = stringBundle.getSimpleEnumeration();
  while (enumerator.hasMoreElements()) {
    var string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
    var key = string.key, property = 'textContent';
    var i = key.lastIndexOf('.');
    if (i >= 0) {
      property = key.substring(i + 1);
      key = key.substring(0, i);
    }
    if (!(key in map))
      map[key] = {};
    map[key][property] = string.value;
  }
  return map;
}
function getLocalizedString(strings, id, property) {
  property = property || 'textContent';
  if (id in strings)
    return strings[id][property];
  return id;
}

// All the priviledged actions.
function ChromeActions(domWindow) {
  this.domWindow = domWindow;
}

ChromeActions.prototype = {
  download: function(data, sendResponse) {
    var originalUrl = data.originalUrl;
    // The data may not be downloaded so we need just retry getting the pdf with
    // the original url.
    var originalUri = NetUtil.newURI(data.originalUrl);
    var blobUri = data.blobUrl ? NetUtil.newURI(data.blobUrl) : originalUri;
    var extHelperAppSvc =
          Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
             getService(Ci.nsIExternalHelperAppService);
    var frontWindow = Cc['@mozilla.org/embedcomp/window-watcher;1'].
                         getService(Ci.nsIWindowWatcher).activeWindow;

    NetUtil.asyncFetch(blobUri, function(aInputStream, aResult) {
      if (!Components.isSuccessCode(aResult)) {
        if (sendResponse)
          sendResponse(true);
        return;
      }
      // Create a nsIInputStreamChannel so we can set the url on the channel
      // so the filename will be correct.
      let channel = Cc['@mozilla.org/network/input-stream-channel;1'].
                       createInstance(Ci.nsIInputStreamChannel);
      channel.setURI(originalUri);
      channel.contentStream = aInputStream;
      channel.QueryInterface(Ci.nsIChannel);

      var listener = {
        extListener: null,
        onStartRequest: function(aRequest, aContext) {
          this.extListener = extHelperAppSvc.doContent('application/pdf',
                                aRequest, frontWindow, false);
          this.extListener.onStartRequest(aRequest, aContext);
        },
        onStopRequest: function(aRequest, aContext, aStatusCode) {
          if (this.extListener)
            this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
          // Notify the content code we're done downloading.
          if (sendResponse)
            sendResponse(false);
        },
        onDataAvailable: function(aRequest, aContext, aInputStream, aOffset,
                                  aCount) {
          this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
                                           aOffset, aCount);
        }
      };

      channel.asyncOpen(listener, null);
    });
  },
  setDatabase: function(data) {
    if (inPrivateBrowsing)
      return;
    // Protect against something sending tons of data to setDatabase.
    if (data.length > MAX_DATABASE_LENGTH)
      return;
    setStringPref(PREF_PREFIX + '.database', data);
  },
  getDatabase: function() {
    if (inPrivateBrowsing)
      return '{}';
    return getStringPref(PREF_PREFIX + '.database', '{}');
  },
  getLocale: function() {
    return getStringPref('general.useragent.locale', 'en-US');
  },
  getStrings: function(data) {
    try {
      // Lazy initialization of localizedStrings
      if (!('localizedStrings' in this))
        this.localizedStrings = getLocalizedStrings('viewer.properties');

      var result = this.localizedStrings[data];
      return JSON.stringify(result || null);
    } catch (e) {
      log('Unable to retrive localized strings: ' + e);
      return 'null';
    }
  },
  pdfBugEnabled: function() {
    return getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false);
  },
  searchEnabled: function() {
    return getBoolPref(PREF_PREFIX + '.searchEnabled', false);
  },
  fallback: function(url, sendResponse) {
    var self = this;
    var domWindow = this.domWindow;
    var strings = getLocalizedStrings('chrome.properties');
    var message = getLocalizedString(strings, 'unsupported_feature');

    var win = Services.wm.getMostRecentWindow('navigator:browser');
    var browser = win.gBrowser.getBrowserForDocument(domWindow.top.document);
    var notificationBox = win.gBrowser.getNotificationBox(browser);
    // Flag so we don't call the response callback twice, since if the user
    // clicks open with different viewer both the button callback and
    // eventCallback will be called.
    var sentResponse = false;
    var buttons = [{
      label: getLocalizedString(strings, 'open_with_different_viewer'),
      accessKey: getLocalizedString(strings, 'open_with_different_viewer',
                                    'accessKey'),
      callback: function() {
        sentResponse = true;
        sendResponse(true);
      }
    }];
    notificationBox.appendNotification(message, 'pdfjs-fallback', null,
                                       notificationBox.PRIORITY_WARNING_LOW,
                                       buttons,
                                       function eventsCallback(eventType) {
      // Currently there is only one event "removed" but if there are any other
      // added in the future we still only care about removed at the moment.
      if (eventType !== 'removed')
        return;
      // Don't send a response again if we already responded when the button was
      // clicked.
      if (!sentResponse)
        sendResponse(false);
    });
  }
};

// Event listener to trigger chrome privedged code.
function RequestListener(actions) {
  this.actions = actions;
}
// Receive an event and synchronously or asynchronously responds.
RequestListener.prototype.receive = function(event) {
  var message = event.target;
  var doc = message.ownerDocument;
  var action = message.getUserData('action');
  var data = message.getUserData('data');
  var sync = message.getUserData('sync');
  var actions = this.actions;
  if (!(action in actions)) {
    log('Unknown action: ' + action);
    return;
  }
  if (sync) {
    var response = actions[action].call(this.actions, data);
    message.setUserData('response', response, null);
  } else {
    var response;
    if (!message.getUserData('callback')) {
      doc.documentElement.removeChild(message);
      response = null;
    } else {
      response = function sendResponse(response) {
        message.setUserData('response', response, null);

        var listener = doc.createEvent('HTMLEvents');
        listener.initEvent('pdf.js.response', true, false);
        return message.dispatchEvent(listener);
      }
    }
    actions[action].call(this.actions, data, response);
  }
};

function PdfStreamConverter() {
}

PdfStreamConverter.prototype = {

  // properties required for XPCOM registration:
  classID: Components.ID('{PDFJSSCRIPT_STREAM_CONVERTER_ID}'),
  classDescription: 'pdf.js Component',
  contractID: '@mozilla.org/streamconv;1?from=application/pdf&to=*/*',

  QueryInterface: XPCOMUtils.generateQI([
      Ci.nsISupports,
      Ci.nsIStreamConverter,
      Ci.nsIStreamListener,
      Ci.nsIRequestObserver
  ]),

  /*
   * This component works as such:
   * 1. asyncConvertData stores the listener
   * 2. onStartRequest creates a new channel, streams the viewer and cancels
   *    the request so pdf.js can do the request
   * Since the request is cancelled onDataAvailable should not be called. The
   * onStopRequest does nothing. The convert function just returns the stream,
   * it's just the synchronous version of asyncConvertData.
   */

  // nsIStreamConverter::convert
  convert: function(aFromStream, aFromType, aToType, aCtxt) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  // nsIStreamConverter::asyncConvertData
  asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
    if (!isEnabled())
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;
    // Ignoring HTTP POST requests -- pdf.js has to repeat the request.
    var skipConversion = false;
    try {
      var request = aCtxt;
      request.QueryInterface(Ci.nsIHttpChannel);
      skipConversion = (request.requestMethod !== 'GET');
    } catch (e) {
      // Non-HTTP request... continue normally.
    }
    if (skipConversion)
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;

    // Store the listener passed to us
    this.listener = aListener;
  },

  // nsIStreamListener::onDataAvailable
  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
    // Do nothing since all the data loading is handled by the viewer.
    log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!');
  },

  // nsIRequestObserver::onStartRequest
  onStartRequest: function(aRequest, aContext) {

    // Setup the request so we can use it below.
    aRequest.QueryInterface(Ci.nsIChannel);
    // Cancel the request so the viewer can handle it.
    aRequest.cancel(Cr.NS_BINDING_ABORTED);

    // Create a new channel that is viewer loaded as a resource.
    var ioService = Services.io;
    var channel = ioService.newChannel(
                    'resource://pdf.js/web/viewer.html', null, null);

    var listener = this.listener;
    // Proxy all the request observer calls, when it gets to onStopRequest
    // we can get the dom window.
    var proxy = {
      onStartRequest: function() {
        listener.onStartRequest.apply(listener, arguments);
      },
      onDataAvailable: function() {
        listener.onDataAvailable.apply(listener, arguments);
      },
      onStopRequest: function() {
        var domWindow = getDOMWindow(channel);
        // Double check the url is still the correct one.
        if (domWindow.document.documentURIObject.equals(aRequest.URI)) {
          let requestListener = new RequestListener(
                                      new ChromeActions(domWindow));
          domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {
            requestListener.receive(event);
          }, false, true);
        }
        listener.onStopRequest.apply(listener, arguments);
      }
    };

    // Keep the URL the same so the browser sees it as the same.
    channel.originalURI = aRequest.URI;
    channel.asyncOpen(proxy, aContext);
  },

  // nsIRequestObserver::onStopRequest
  onStopRequest: function(aRequest, aContext, aStatusCode) {
    // Do nothing.
  }
};

var NSGetFactory = XPCOMUtils.generateNSGetFactory([PdfStreamConverter]);
<|endoftext|>


<commit_before>/*!
 * Module dependencies.
 */

var _ = require('underscore'),
	keystone = require('../../../'),
	util = require('util'),
	cloudinary = require('cloudinary'),
	MPromise = require('mpromise'), // jshint ignore:line
	utils = require('keystone-utils'),
	super_ = require('../Type');

/**
 * CloudinaryImage FieldType Constructor
 * @extends Field
 * @api public
 */

function cloudinaryimage(list, path, options) {

	this._underscoreMethods = ['format'];
	this._fixedSize = 'full';

	// TODO: implement filtering, usage disabled for now
	options.nofilter = true;

	// TODO: implement initial form, usage disabled for now
	if (options.initial) {
		throw new Error(
			'Invalid Configuration\n\n' +
			'CloudinaryImage fields (' + list.key + '.' + path + ') do not currently support being used as initial fields.\n'
		);
	}

	cloudinaryimage.super_.call(this, list, path, options);

	// validate cloudinary config
	if (!keystone.get('cloudinary config')) {
		throw new Error(
			'Invalid Configuration\n\n' +
			'CloudinaryImage fields (' + list.key + '.' + this.path + ') require the "cloudinary config" option to be set.\n\n' +
			'See http://keystonejs.com/docs/configuration/#services-cloudinary for more information.\n'
		);
	}

}

/*!
 * Inherit from Field
 */

util.inherits(cloudinaryimage, super_);

/**
 * Registers the field on the List's Mongoose Schema.
 *
 * @api public
 */

cloudinaryimage.prototype.addToSchema = function() {

	var field = this,
		schema = this.list.schema;

	var paths = this.paths = {
		// cloudinary fields
		public_id: 		this._path.append('.public_id'),
		version: 		this._path.append('.version'),
		signature: 		this._path.append('.signature'),
		format: 		this._path.append('.format'),
		resource_type: 	this._path.append('.resource_type'),
		url: 			this._path.append('.url'),
		width: 			this._path.append('.width'),
		height: 		this._path.append('.height'),
		secure_url: 	this._path.append('.secure_url'),
		// virtuals
		exists: 		this._path.append('.exists'),
		folder: 		this._path.append('.folder'),
		// form paths
		upload: 		this._path.append('_upload'),
		action: 		this._path.append('_action'),
		select: 		this._path.append('_select')
	};

	var schemaPaths = this._path.addTo({}, {
		public_id:		String,
		version:		Number,
		signature:		String,
		format:			String,
		resource_type:	String,
		url:			String,
		width:			Number,
		height:			Number,
		secure_url:		String
	});

	schema.add(schemaPaths);

	var exists = function(item) {
		return (item.get(paths.public_id) ? true : false);
	};

	// The .exists virtual indicates whether an image is stored
	schema.virtual(paths.exists).get(function() {
		return schemaMethods.exists.apply(this);
	});

	var folder = function(item) {
		var folderValue = null;

		if (keystone.get('cloudinary folders')) {
			if (field.options.folder) {
				folderValue = field.options.folder;
			} else {
				var folderList = keystone.get('cloudinary prefix') ? [keystone.get('cloudinary prefix')] : [];
				folderList.push(field.list.path);
				folderList.push(field.path);
				folderValue = folderList.join('/');
			}
		}

		return folderValue;
	};

	// The .folder virtual returns the cloudinary folder used to upload/select images
	schema.virtual(paths.folder).get(function() {
		return schemaMethods.folder.apply(this);
	});

	var src = function(item, options) {

		if (!exists(item)) {
			return '';
		}

		options = ('object' === typeof options) ? options : {};

		if (!('fetch_format' in options) && keystone.get('cloudinary webp') !== false) {
			options.fetch_format = 'auto';
		}

		if (!('progressive' in options) && keystone.get('cloudinary progressive') !== false) {
			options.progressive = true;
		}

		if (!('secure' in options) && keystone.get('cloudinary secure')) {
			options.secure = true;
		}

		options.version = item.get(paths.version);

		return cloudinary.url(item.get(paths.public_id) + '.' + item.get(paths.format), options);

	};

	var reset = function(item) {
		item.set(field.path, {
			public_id: '',
			version: 0,
			signature: '',
			format: '',
			resource_type: '',
			url: '',
			width: 0,
			height: 0,
			secure_url: ''
		});
	};

	var addSize = function(options, width, height, other) {
		if (width) options.width = width;
		if (height) options.height = height;
		if ('object' === typeof other) {
			_.extend(options, other);
		}
		return options;
	};

	var schemaMethods = {
		exists: function() {
			return exists(this);
		},
		folder: function() {
			return folder(this);
		},
		src: function(options) {
			return src(this, options);
		},
		tag: function(options) {
			return exists(this) ? cloudinary.image(this.get(field.path), options) : '';
		},
		scale: function(width, height, options) {
			return src(this, addSize({ crop: 'scale' }, width, height, options));
		},
		fill: function(width, height, options) {
			return src(this, addSize({ crop: 'fill', gravity: 'faces' }, width, height, options));
		},
		lfill: function(width, height, options) {
			return src(this, addSize({ crop: 'lfill', gravity: 'faces' }, width, height, options));
		},
		fit: function(width, height, options) {
			return src(this, addSize({ crop: 'fit' }, width, height, options));
		},
		limit: function(width, height, options) {
			return src(this, addSize({ crop: 'limit' }, width, height, options));
		},
		pad: function(width, height, options) {
			return src(this, addSize({ crop: 'pad' }, width, height, options));
		},
		lpad: function(width, height, options) {
			return src(this, addSize({ crop: 'lpad' }, width, height, options));
		},
		crop: function(width, height, options) {
			return src(this, addSize({ crop: 'crop', gravity: 'faces' }, width, height, options));
		},
		thumbnail: function(width, height, options) {
			return src(this, addSize({ crop: 'thumb', gravity: 'faces' }, width, height, options));
		},
		/**
		 * Resets the value of the field
		 *
		 * @api public
		 */
		reset: function() {
			reset(this);
		},
		/**
		 * Deletes the image from Cloudinary and resets the field
		 *
		 * @api public
		 */
		delete: function() {
			var promise = new MPromise();

			cloudinary.uploader.destroy(this.get(paths.public_id), function(result) {
				promise.fulfill(result);
			});
			reset(this);

			return promise;
		},
		/**
		 * Uploads the image to Cloudinary
		 *
		 * @api public
		 */
		upload: function(file, options) {
			var promise = new MPromise();

			cloudinary.uploader.upload(file, function(result) {
				promise.fulfill(result);
			}, options);

			return promise;
		}
	};

	_.each(schemaMethods, function(fn, key) {
		field.underscoreMethod(key, fn);
	});

	// expose a method on the field to call schema methods
	this.apply = function(item, method) {
		return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2));
	};

	this.bindUnderscoreMethods();
};

/**
 * Formats the field value
 *
 * @api public
 */

cloudinaryimage.prototype.format = function(item) {
	return item.get(this.paths.url);
};

/**
 * Detects whether the field has been modified
 *
 * @api public
 */

cloudinaryimage.prototype.isModified = function(item) {
	return item.isModified(this.paths.url);
};

/**
 * Validates that a value for this field has been provided in a data object
 *
 * @api public
 */

cloudinaryimage.prototype.validateInput = function(data) {
	// TODO - how should image field input be validated?
	return true;
};

/**
 * Updates the value for this field in the item from a data object
 *
 * @api public
 */

cloudinaryimage.prototype.updateItem = function(item, data) {
	var paths = this.paths;

	var setValue = function(key) {
		if (paths[key]) {
			var index = paths[key].indexOf('.');
			var field = paths[key].substr(0, index);
			// Note we allow implicit conversion here so that numbers submitted as strings in the data object
			// aren't treated as different values to the stored Number values
			if (data[field] && data[field][key] && data[field][key] != item.get(paths[key])) { // jshint ignore:line
				item.set(paths[key], data[field][key] || null);
			}
		}
	};

	_.each(['public_id', 'version', 'signature', 'format', 'resource_type', 'url', 'width', 'height', 'secure_url'], setValue);
};

/**
 * Returns a callback that handles a standard form submission for the field
 *
 * Expected form parts are
 * - `field.paths.action` in `req.body` (`clear` or `delete`)
 * - `field.paths.upload` in `req.files` (uploads the image to cloudinary)
 *
 * @api public
 */

cloudinaryimage.prototype.getRequestHandler = function(item, req, paths, callback) {

	var field = this;

	if (utils.isFunction(paths)) {
		callback = paths;
		paths = field.paths;
	} else if (!paths) {
		paths = field.paths;
	}

	callback = callback || function() {};

	return function() {

		if (req.body) {
			var action = req.body[paths.action];

			if (/^(delete|reset)$/.test(action)) {
				field.apply(item, action);
			}
		}

		if (req.body && req.body[paths.select]) {

			cloudinary.api.resource(req.body[paths.select], function(result) {
				if (result.error) {
					callback(result.error);
				} else {
					item.set(field.path, result);
					callback();
				}
			});

		} else if (req.files && req.files[paths.upload] && req.files[paths.upload].size) {

			var tp = keystone.get('cloudinary prefix') || '';
			var imageDelete;

			if (tp.length) {
				tp += '_';
			}

			var uploadOptions = {
				tags: [tp + field.list.path + '_' + field.path, tp + field.list.path + '_' + field.path + '_' + item.id]
			};

			if (keystone.get('cloudinary folders')) {
				uploadOptions.folder = item.get(paths.folder);
			}

			if (keystone.get('cloudinary prefix')) {
				uploadOptions.tags.push(keystone.get('cloudinary prefix'));
			}

			if (keystone.get('env') !== 'production') {
				uploadOptions.tags.push(tp + 'dev');
			}
			console.log(field.options)
			if (field.options.publicID) {
				var publicIdValue = item.get(field.options.publicID);
				if (publicIdValue) {
					uploadOptions.public_id = publicIdValue;
				}
			}

			if (field.options.autoCleanup && item.get(field.paths.exists)) {
				// capture image delete promise
				imageDelete = field.apply(item, 'delete');
			}

			// callback to be called upon completion of the 'upload' method
			var uploadComplete = function(result) {
				if (result.error) {
					callback(result.error);
				} else {
					item.set(field.path, result);
					callback();
				}
			};

			// upload immediately if image is not being delete
			if (typeof imageDelete === 'undefined') {
				field.apply(item, 'upload', req.files[paths.upload].path, uploadOptions).onFulfill(uploadComplete);
			} else {
				// otherwise wait until image is deleted before uploading
				// this avoids problems when deleting/uploading images with the same public_id (issue #598)
				imageDelete.onFulfill(function(result) {
					if (result.error) {
						callback(result.error);
					} else {
						field.apply(item, 'upload', req.files[paths.upload].path, uploadOptions).onFulfill(uploadComplete);
					}
				});
			}

		} else {
			callback();
		}

	};

};

/**
 * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
 *
 * @api public
 */

cloudinaryimage.prototype.handleRequest = function(item, req, paths, callback) {
	this.getRequestHandler(item, req, paths, callback)();
};

/*!
 * Export class
 */

exports = module.exports = cloudinaryimage;
<commit_msg>remove log<commit_after>/*!
 * Module dependencies.
 */

var _ = require('underscore'),
	keystone = require('../../../'),
	util = require('util'),
	cloudinary = require('cloudinary'),
	MPromise = require('mpromise'), // jshint ignore:line
	utils = require('keystone-utils'),
	super_ = require('../Type');

/**
 * CloudinaryImage FieldType Constructor
 * @extends Field
 * @api public
 */

function cloudinaryimage(list, path, options) {

	this._underscoreMethods = ['format'];
	this._fixedSize = 'full';

	// TODO: implement filtering, usage disabled for now
	options.nofilter = true;

	// TODO: implement initial form, usage disabled for now
	if (options.initial) {
		throw new Error(
			'Invalid Configuration\n\n' +
			'CloudinaryImage fields (' + list.key + '.' + path + ') do not currently support being used as initial fields.\n'
		);
	}

	cloudinaryimage.super_.call(this, list, path, options);

	// validate cloudinary config
	if (!keystone.get('cloudinary config')) {
		throw new Error(
			'Invalid Configuration\n\n' +
			'CloudinaryImage fields (' + list.key + '.' + this.path + ') require the "cloudinary config" option to be set.\n\n' +
			'See http://keystonejs.com/docs/configuration/#services-cloudinary for more information.\n'
		);
	}

}

/*!
 * Inherit from Field
 */

util.inherits(cloudinaryimage, super_);

/**
 * Registers the field on the List's Mongoose Schema.
 *
 * @api public
 */

cloudinaryimage.prototype.addToSchema = function() {

	var field = this,
		schema = this.list.schema;

	var paths = this.paths = {
		// cloudinary fields
		public_id: 		this._path.append('.public_id'),
		version: 		this._path.append('.version'),
		signature: 		this._path.append('.signature'),
		format: 		this._path.append('.format'),
		resource_type: 	this._path.append('.resource_type'),
		url: 			this._path.append('.url'),
		width: 			this._path.append('.width'),
		height: 		this._path.append('.height'),
		secure_url: 	this._path.append('.secure_url'),
		// virtuals
		exists: 		this._path.append('.exists'),
		folder: 		this._path.append('.folder'),
		// form paths
		upload: 		this._path.append('_upload'),
		action: 		this._path.append('_action'),
		select: 		this._path.append('_select')
	};

	var schemaPaths = this._path.addTo({}, {
		public_id:		String,
		version:		Number,
		signature:		String,
		format:			String,
		resource_type:	String,
		url:			String,
		width:			Number,
		height:			Number,
		secure_url:		String
	});

	schema.add(schemaPaths);

	var exists = function(item) {
		return (item.get(paths.public_id) ? true : false);
	};

	// The .exists virtual indicates whether an image is stored
	schema.virtual(paths.exists).get(function() {
		return schemaMethods.exists.apply(this);
	});

	var folder = function(item) {
		var folderValue = null;

		if (keystone.get('cloudinary folders')) {
			if (field.options.folder) {
				folderValue = field.options.folder;
			} else {
				var folderList = keystone.get('cloudinary prefix') ? [keystone.get('cloudinary prefix')] : [];
				folderList.push(field.list.path);
				folderList.push(field.path);
				folderValue = folderList.join('/');
			}
		}

		return folderValue;
	};

	// The .folder virtual returns the cloudinary folder used to upload/select images
	schema.virtual(paths.folder).get(function() {
		return schemaMethods.folder.apply(this);
	});

	var src = function(item, options) {

		if (!exists(item)) {
			return '';
		}

		options = ('object' === typeof options) ? options : {};

		if (!('fetch_format' in options) && keystone.get('cloudinary webp') !== false) {
			options.fetch_format = 'auto';
		}

		if (!('progressive' in options) && keystone.get('cloudinary progressive') !== false) {
			options.progressive = true;
		}

		if (!('secure' in options) && keystone.get('cloudinary secure')) {
			options.secure = true;
		}

		options.version = item.get(paths.version);

		return cloudinary.url(item.get(paths.public_id) + '.' + item.get(paths.format), options);

	};

	var reset = function(item) {
		item.set(field.path, {
			public_id: '',
			version: 0,
			signature: '',
			format: '',
			resource_type: '',
			url: '',
			width: 0,
			height: 0,
			secure_url: ''
		});
	};

	var addSize = function(options, width, height, other) {
		if (width) options.width = width;
		if (height) options.height = height;
		if ('object' === typeof other) {
			_.extend(options, other);
		}
		return options;
	};

	var schemaMethods = {
		exists: function() {
			return exists(this);
		},
		folder: function() {
			return folder(this);
		},
		src: function(options) {
			return src(this, options);
		},
		tag: function(options) {
			return exists(this) ? cloudinary.image(this.get(field.path), options) : '';
		},
		scale: function(width, height, options) {
			return src(this, addSize({ crop: 'scale' }, width, height, options));
		},
		fill: function(width, height, options) {
			return src(this, addSize({ crop: 'fill', gravity: 'faces' }, width, height, options));
		},
		lfill: function(width, height, options) {
			return src(this, addSize({ crop: 'lfill', gravity: 'faces' }, width, height, options));
		},
		fit: function(width, height, options) {
			return src(this, addSize({ crop: 'fit' }, width, height, options));
		},
		limit: function(width, height, options) {
			return src(this, addSize({ crop: 'limit' }, width, height, options));
		},
		pad: function(width, height, options) {
			return src(this, addSize({ crop: 'pad' }, width, height, options));
		},
		lpad: function(width, height, options) {
			return src(this, addSize({ crop: 'lpad' }, width, height, options));
		},
		crop: function(width, height, options) {
			return src(this, addSize({ crop: 'crop', gravity: 'faces' }, width, height, options));
		},
		thumbnail: function(width, height, options) {
			return src(this, addSize({ crop: 'thumb', gravity: 'faces' }, width, height, options));
		},
		/**
		 * Resets the value of the field
		 *
		 * @api public
		 */
		reset: function() {
			reset(this);
		},
		/**
		 * Deletes the image from Cloudinary and resets the field
		 *
		 * @api public
		 */
		delete: function() {
			var promise = new MPromise();

			cloudinary.uploader.destroy(this.get(paths.public_id), function(result) {
				promise.fulfill(result);
			});
			reset(this);

			return promise;
		},
		/**
		 * Uploads the image to Cloudinary
		 *
		 * @api public
		 */
		upload: function(file, options) {
			var promise = new MPromise();

			cloudinary.uploader.upload(file, function(result) {
				promise.fulfill(result);
			}, options);

			return promise;
		}
	};

	_.each(schemaMethods, function(fn, key) {
		field.underscoreMethod(key, fn);
	});

	// expose a method on the field to call schema methods
	this.apply = function(item, method) {
		return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2));
	};

	this.bindUnderscoreMethods();
};

/**
 * Formats the field value
 *
 * @api public
 */

cloudinaryimage.prototype.format = function(item) {
	return item.get(this.paths.url);
};

/**
 * Detects whether the field has been modified
 *
 * @api public
 */

cloudinaryimage.prototype.isModified = function(item) {
	return item.isModified(this.paths.url);
};

/**
 * Validates that a value for this field has been provided in a data object
 *
 * @api public
 */

cloudinaryimage.prototype.validateInput = function(data) {
	// TODO - how should image field input be validated?
	return true;
};

/**
 * Updates the value for this field in the item from a data object
 *
 * @api public
 */

cloudinaryimage.prototype.updateItem = function(item, data) {
	var paths = this.paths;

	var setValue = function(key) {
		if (paths[key]) {
			var index = paths[key].indexOf('.');
			var field = paths[key].substr(0, index);
			// Note we allow implicit conversion here so that numbers submitted as strings in the data object
			// aren't treated as different values to the stored Number values
			if (data[field] && data[field][key] && data[field][key] != item.get(paths[key])) { // jshint ignore:line
				item.set(paths[key], data[field][key] || null);
			}
		}
	};

	_.each(['public_id', 'version', 'signature', 'format', 'resource_type', 'url', 'width', 'height', 'secure_url'], setValue);
};

/**
 * Returns a callback that handles a standard form submission for the field
 *
 * Expected form parts are
 * - `field.paths.action` in `req.body` (`clear` or `delete`)
 * - `field.paths.upload` in `req.files` (uploads the image to cloudinary)
 *
 * @api public
 */

cloudinaryimage.prototype.getRequestHandler = function(item, req, paths, callback) {

	var field = this;

	if (utils.isFunction(paths)) {
		callback = paths;
		paths = field.paths;
	} else if (!paths) {
		paths = field.paths;
	}

	callback = callback || function() {};

	return function() {

		if (req.body) {
			var action = req.body[paths.action];

			if (/^(delete|reset)$/.test(action)) {
				field.apply(item, action);
			}
		}

		if (req.body && req.body[paths.select]) {

			cloudinary.api.resource(req.body[paths.select], function(result) {
				if (result.error) {
					callback(result.error);
				} else {
					item.set(field.path, result);
					callback();
				}
			});

		} else if (req.files && req.files[paths.upload] && req.files[paths.upload].size) {

			var tp = keystone.get('cloudinary prefix') || '';
			var imageDelete;

			if (tp.length) {
				tp += '_';
			}

			var uploadOptions = {
				tags: [tp + field.list.path + '_' + field.path, tp + field.list.path + '_' + field.path + '_' + item.id]
			};

			if (keystone.get('cloudinary folders')) {
				uploadOptions.folder = item.get(paths.folder);
			}

			if (keystone.get('cloudinary prefix')) {
				uploadOptions.tags.push(keystone.get('cloudinary prefix'));
			}

			if (keystone.get('env') !== 'production') {
				uploadOptions.tags.push(tp + 'dev');
			}

			if (field.options.publicID) {
				var publicIdValue = item.get(field.options.publicID);
				if (publicIdValue) {
					uploadOptions.public_id = publicIdValue;
				}
			}

			if (field.options.autoCleanup && item.get(field.paths.exists)) {
				// capture image delete promise
				imageDelete = field.apply(item, 'delete');
			}

			// callback to be called upon completion of the 'upload' method
			var uploadComplete = function(result) {
				if (result.error) {
					callback(result.error);
				} else {
					item.set(field.path, result);
					callback();
				}
			};

			// upload immediately if image is not being delete
			if (typeof imageDelete === 'undefined') {
				field.apply(item, 'upload', req.files[paths.upload].path, uploadOptions).onFulfill(uploadComplete);
			} else {
				// otherwise wait until image is deleted before uploading
				// this avoids problems when deleting/uploading images with the same public_id (issue #598)
				imageDelete.onFulfill(function(result) {
					if (result.error) {
						callback(result.error);
					} else {
						field.apply(item, 'upload', req.files[paths.upload].path, uploadOptions).onFulfill(uploadComplete);
					}
				});
			}

		} else {
			callback();
		}

	};

};

/**
 * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
 *
 * @api public
 */

cloudinaryimage.prototype.handleRequest = function(item, req, paths, callback) {
	this.getRequestHandler(item, req, paths, callback)();
};

/*!
 * Export class
 */

exports = module.exports = cloudinaryimage;
<|endoftext|>


<commit_before>define(function(require) {
  var Sunstone = require('sunstone');
  var Notifier = require('utils/notifier');
  var Locale = require('utils/locale');
  var DataTable = require('./datatable');
  var OpenNebulaHost = require('opennebula/host');

  var TAB_ID = require('./tabId');
  var CREATE_DIALOG_ID = require('./form-panels/create/formPanelId');

  var _actions = {
    "Host.create" : {
      type: "create",
      call : OpenNebulaHost.create,
      callback : function(request, response) {
        Sunstone.resetFormPanel(TAB_ID, CREATE_DIALOG_ID);
        Sunstone.hideFormPanel($("#" + TAB_ID));
        Sunstone.getDataTable(TAB_ID).addElement(request, response);
      },
      error: function(request, response) {
        Sunstone.hideFormPanelLoading($("#" + TAB_ID));
        Notifier.onError(request, response);
      },
      notify: true
    },

    "Host.create_dialog" : {
      type: "custom",
      call: function() {
        Sunstone.showFormPanel(TAB_ID, CREATE_DIALOG_ID, "create");
      }
    },

    "Host.list" : {
      type: "list",
      call: OpenNebulaHost.list,
      callback: function(request, response) {
        Sunstone.getDataTable(TAB_ID).updateView(request, response);
      },
      error: Notifier.onError
    },

    "Host.show" : {
      type: "single",
      call: OpenNebulaHost.show,
      callback: function(request, response) {
        Sunstone.getDataTable(TAB_ID).updateElement(request, response);
        if (Sunstone.rightInfoVisible($('#' + TAB_ID))) {
          Sunstone.insertPanels(TAB_ID, response);
        }
      },
      error: Notifier.onError
    },

    "Host.refresh" : {
      type: "custom",
      call: function() {
        var tab = $('#' + TAB_ID);
        if (Sunstone.rightInfoVisible(tab)) {
          Sunstone.runAction("Host.show", Sunstone.rightInfoResourceId(tab))
        } else {
          Sunstone.getDataTable(TAB_ID).waitingNodes();
          Sunstone.runAction("Host.list", {force: true});
        }
      },
      error: Notifier.onError
    },

    "Host.enable" : {
      type: "multiple",
      call: OpenNebulaHost.enable,
      callback: function (req) {
        Sunstone.runAction("Host.show", req.request.data[0]);
      },
      elements: function() {
        return Sunstone.getDataTable(TAB_ID).elements();
      },
      error: Notifier.onError,
      notify: true
    },

    "Host.disable" : {
      type: "multiple",
      call: OpenNebulaHost.disable,
      callback: function (req) {
        Sunstone.runAction("Host.show", req.request.data[0]);
      },
      elements: function() {
        return Sunstone.getDataTable(TAB_ID).elements();
      },
      error: Notifier.onError,
      notify: true
    },

    "Host.delete" : {
      type: "multiple",
      call: OpenNebulaHost.del,
      callback : function(request, response) {
        Sunstone.getDataTable(TAB_ID).deleteElement(request, response);
      },
      elements: function() {
        return Sunstone.getDataTable(TAB_ID).elements();
      },
      error: Notifier.onError,
      notify: true
    },

    "Host.update_template" : {
      type: "single",
      call: OpenNebulaHost.update,
      callback: function(request) {
        Sunstone.runAction('Host.show', request.request.data[0][0]);
      },
      error: Notifier.onError
    },

    /* TODO "Host.addtocluster" : {
      type: "multiple",
      call: function(params) {
        var cluster = params.data.extra_param;
        var host = params.data.id;

        if (cluster == -1) {
          OpenNebulaHost.show({
            data : {
              id: host
            },
            success: function (request, host_info) {
              var current_cluster = host_info.HOST.CLUSTER_ID;

              if (current_cluster != -1) {
                OpenNebula.Cluster.delhost({
                  data: {
                    id: current_cluster,
                    extra_param: host
                  },
                  success: function() {
                    OpenNebula.Helper.clear_cache("HOST");
                    Sunstone.runAction('Host.show', host);
                  },
                  error: onError
                });
              } else {
                OpenNebula.Helper.clear_cache("HOST");
                Sunstone.runAction('Host.show', host);
              }
            },
            error: onError
          });
        } else {
          OpenNebula.Cluster.addhost({
            data: {
              id: cluster,
              extra_param: host
            },
            success: function() {
              OpenNebula.Helper.clear_cache("HOST");
              Sunstone.runAction('Host.show', host);
            },
            error: onError
          });
        }
      },
      elements: function() {
        return Sunstone.getDataTable(TAB_ID).elements();
      }
    },*/

    "Host.rename" : {
      type: "single",
      call: OpenNebulaHost.rename,
      callback: function(request) {
        Sunstone.runAction('Host.show', request.request.data[0][0]);
      },
      error: Notifier.onError,
      notify: true
    }
  };

  return _actions;
})
<commit_msg>Feature #3748: Host.addtocluster action<commit_after>define(function(require) {
  var Sunstone = require('sunstone');
  var Notifier = require('utils/notifier');
  var Locale = require('utils/locale');
  var DataTable = require('./datatable');
  var OpenNebulaHost = require('opennebula/host');
  var OpenNebulaCluster = require('opennebula/cluster');
  var OpenNebulaAction = require('opennebula/action');

  var TAB_ID = require('./tabId');
  var CREATE_DIALOG_ID = require('./form-panels/create/formPanelId');

  var _actions = {
    "Host.create" : {
      type: "create",
      call : OpenNebulaHost.create,
      callback : function(request, response) {
        Sunstone.resetFormPanel(TAB_ID, CREATE_DIALOG_ID);
        Sunstone.hideFormPanel($("#" + TAB_ID));
        Sunstone.getDataTable(TAB_ID).addElement(request, response);
      },
      error: function(request, response) {
        Sunstone.hideFormPanelLoading($("#" + TAB_ID));
        Notifier.onError(request, response);
      },
      notify: true
    },

    "Host.create_dialog" : {
      type: "custom",
      call: function() {
        Sunstone.showFormPanel(TAB_ID, CREATE_DIALOG_ID, "create");
      }
    },

    "Host.list" : {
      type: "list",
      call: OpenNebulaHost.list,
      callback: function(request, response) {
        Sunstone.getDataTable(TAB_ID).updateView(request, response);
      },
      error: Notifier.onError
    },

    "Host.show" : {
      type: "single",
      call: OpenNebulaHost.show,
      callback: function(request, response) {
        Sunstone.getDataTable(TAB_ID).updateElement(request, response);
        if (Sunstone.rightInfoVisible($('#' + TAB_ID))) {
          Sunstone.insertPanels(TAB_ID, response);
        }
      },
      error: Notifier.onError
    },

    "Host.refresh" : {
      type: "custom",
      call: function() {
        var tab = $('#' + TAB_ID);
        if (Sunstone.rightInfoVisible(tab)) {
          Sunstone.runAction("Host.show", Sunstone.rightInfoResourceId(tab))
        } else {
          Sunstone.getDataTable(TAB_ID).waitingNodes();
          Sunstone.runAction("Host.list", {force: true});
        }
      },
      error: Notifier.onError
    },

    "Host.enable" : {
      type: "multiple",
      call: OpenNebulaHost.enable,
      callback: function (req) {
        Sunstone.runAction("Host.show", req.request.data[0]);
      },
      elements: function() {
        return Sunstone.getDataTable(TAB_ID).elements();
      },
      error: Notifier.onError,
      notify: true
    },

    "Host.disable" : {
      type: "multiple",
      call: OpenNebulaHost.disable,
      callback: function (req) {
        Sunstone.runAction("Host.show", req.request.data[0]);
      },
      elements: function() {
        return Sunstone.getDataTable(TAB_ID).elements();
      },
      error: Notifier.onError,
      notify: true
    },

    "Host.delete" : {
      type: "multiple",
      call: OpenNebulaHost.del,
      callback : function(request, response) {
        Sunstone.getDataTable(TAB_ID).deleteElement(request, response);
      },
      elements: function() {
        return Sunstone.getDataTable(TAB_ID).elements();
      },
      error: Notifier.onError,
      notify: true
    },

    "Host.update_template" : {
      type: "single",
      call: OpenNebulaHost.update,
      callback: function(request) {
        Sunstone.runAction('Host.show', request.request.data[0][0]);
      },
      error: Notifier.onError
    },

    "Host.addtocluster" : {
      type: "multiple",
      call: function(params){
        var cluster = params.data.extra_param;
        var host = params.data.id;

        if (cluster == -1){
          OpenNebulaHost.show({
            data : {
              id: host
            },
            success: function (request, info){
              var element = info.HOST;

              var current_cluster = element.CLUSTER_ID;

              if(current_cluster != -1){
                OpenNebulaCluster.delhost({
                  data: {
                    id: current_cluster,
                    extra_param: host
                  },
                  success: function(){
                    OpenNebulaAction.clear_cache("HOST");
                    Sunstone.runAction('Host.show',host);
                  },
                  error: Notifier.onError
                });
              } else {
                OpenNebulaAction.clear_cache("HOST");
                Sunstone.runAction('Host.show',host);
              }
            },
            error: Notifier.onError
          });
        } else {
          OpenNebulaCluster.addhost({
            data: {
              id: cluster,
              extra_param: host
            },
            success: function(){
              OpenNebulaAction.clear_cache("HOST");
              Sunstone.runAction('Host.show',host);
            },
            error: Notifier.onError
          });
        }
      },
      elements: function() {
        return Sunstone.getDataTable(TAB_ID).elements();
      }
    },

    "Host.rename" : {
      type: "single",
      call: OpenNebulaHost.rename,
      callback: function(request) {
        Sunstone.runAction('Host.show', request.request.data[0][0]);
      },
      error: Notifier.onError,
      notify: true
    }
  };

  return _actions;
})
<|endoftext|>


<commit_before>import { Feature } from 'core/feature';
import {
  getCurrentRouteName,
  getToolkitStorageKey,
  removeToolkitStorageKey,
  setToolkitStorageKey
} from 'helpers/toolkit';

const RESIZABLES = [
  'ynab-grid-cell-date',
  'ynab-grid-cell-accountName',
  'ynab-grid-cell-payeeName',
  'ynab-grid-cell-subCategoryName',
  'ynab-grid-cell-memo',
  'ynab-grid-cell-toolkit-check-number',
  'ynab-grid-cell-outflow',
  'ynab-grid-cell-inflow',
  'ynab-grid-cell-toolkit-running-balance'
];

export class AdjustableColumnWidths extends Feature {
  injectCSS() { return require('./index.css'); }

  constructor() {
    super();
    this.elementWasDragged = false;
    this.isMouseDown = false;
    this.currentX = null;
  }

  shouldInvoke() {
    // don't say we should be invoked unless the feature is also active
    return getCurrentRouteName().indexOf('account') !== -1 && this.settings.enabled;
  }

  onMouseMove(event) {
    if (!this.isMouseDown) { return; }

    event.preventDefault();
    event.stopPropagation();

    Ember.run.debounce(this, () => {
      if (this.offTarget && !event.target.classList.contains('toolkit-draggable')) {
        return;
      }

      this.offTarget = false;
      const invertedDifference = this.currentX - event.clientX;
      const difference = invertedDifference * -1;

      const { isNeighborResizable, neighborCellName } = this.getNeighborOf(this.currentResizableClass);

      if (!isNeighborResizable) {
        return;
      }

      const $elementsOfTypeNeighbor = $(`.${neighborCellName}`);
      const neighborWidth = $elementsOfTypeNeighbor.width();
      const newNeighborWidth = neighborWidth - difference;

      const $elementsOfTypeCurrentResizable = $(`.${this.currentResizableClass}`);
      const currentResizableWidth = $elementsOfTypeCurrentResizable.width();
      const newCurrentResizableWidth = currentResizableWidth + difference;

      if (newNeighborWidth < 50 || newCurrentResizableWidth < 50) {
        this.offTarget = true;
        return;
      }

      this.currentX = event.clientX;
      $elementsOfTypeCurrentResizable.width(newCurrentResizableWidth);
      setToolkitStorageKey(`column-width-${this.currentResizableClass}`, newCurrentResizableWidth);

      $elementsOfTypeNeighbor.width(newNeighborWidth);
      setToolkitStorageKey(`column-width-${neighborCellName}`, newNeighborWidth);
    }, 100);
  }

  onMouseUp() {
    $('body').off('mousemove', this.bindOnMouseMove);
    $('body').off('mouseup', this.bindOnMouseUp);

    if (this.isMouseDown) {
      this.isMouseDown = false;
      this.elementWasDragged = true;
    }
  }

  getNeighborOf(neighborOf) {
    const $rightNeighbor = $(`.${neighborOf}`, '.ynab-grid-header').next();

    if (!$rightNeighbor.length) {
      return { isNeighborResizable: false };
    }

    const neighborCellName = $rightNeighbor.prop('class').match(/ynab-grid-cell.*/)[0];
    return {
      neighborCellName,
      isNeighborResizable: RESIZABLES.some((className) => className === neighborCellName)
    };
  }

  resetWidths() {
    RESIZABLES.forEach((resizableClass) => {
      $(`.${resizableClass}`).width('');
      removeToolkitStorageKey(`column-width-${resizableClass}`);
    });
  }

  invoke() {
    if (!$('.toolkit-reset-widths').length) {
      $('<button class="toolkit-reset-widths button">Reset Column Widths</button>')
        .click(this.resetWidths)
        .insertAfter($('.accounts-toolbar-all-dates', '.accounts-toolbar-right'));
    }

    RESIZABLES.forEach((resizableClass) => {
      const width = getToolkitStorageKey(`column-width-${resizableClass}`, 'number');
      if (width) {
        $(`.${resizableClass}`).width(width);
      }

      if (!this.getNeighborOf(resizableClass).isNeighborResizable) {
        return;
      }

      if ($(`.${resizableClass} .toolkit-draggable`, '.ynab-grid-header').length) {
        return;
      }

      $(`.${resizableClass}`, '.ynab-grid-header')
        .click((event) => {
          if (this.elementWasDragged) {
            event.preventDefault();
            event.stopPropagation();
            this.elementWasDragged = false;
          }
        })
        .css({ position: 'relative' })
        .append(
          $('

')
            .click((event) => event.stopPropagation())
            .mousedown((event) => {
              this.isMouseDown = true;
              this.currentX = event.clientX;
              this.currentResizableClass = resizableClass;

              this.bindOnMouseMove = this.onMouseMove.bind(this);
              this.bindOnMouseUp = this.onMouseUp.bind(this);
              $('body').on('mousemove', this.bindOnMouseMove);
              $('body').on('mouseup', this.bindOnMouseUp);
            })
          );
    });
  }

  onRouteChanged() {
    if (!this.shouldInvoke()) { return; }
    this.invoke();
  }
}
<commit_msg>Refactored adjustable column widths feature to use class properties. Also no longer checking for enabled in shouldInvoke as we now check outside the feature and this code is no longer needed.<commit_after>import { Feature } from 'core/feature';
import {
  getCurrentRouteName,
  getToolkitStorageKey,
  removeToolkitStorageKey,
  setToolkitStorageKey
} from 'helpers/toolkit';

const RESIZABLES = [
  'ynab-grid-cell-date',
  'ynab-grid-cell-accountName',
  'ynab-grid-cell-payeeName',
  'ynab-grid-cell-subCategoryName',
  'ynab-grid-cell-memo',
  'ynab-grid-cell-toolkit-check-number',
  'ynab-grid-cell-outflow',
  'ynab-grid-cell-inflow',
  'ynab-grid-cell-toolkit-running-balance'
];

export class AdjustableColumnWidths extends Feature {
  injectCSS() { return require('./index.css'); }

  elementWasDragged = false;
  isMouseDown = false;
  currentX = null;

  shouldInvoke() {
    return getCurrentRouteName().indexOf('account') !== -1;
  }

  onMouseMove(event) {
    if (!this.isMouseDown) { return; }

    event.preventDefault();
    event.stopPropagation();

    Ember.run.debounce(this, () => {
      if (this.offTarget && !event.target.classList.contains('toolkit-draggable')) {
        return;
      }

      this.offTarget = false;
      const invertedDifference = this.currentX - event.clientX;
      const difference = invertedDifference * -1;

      const { isNeighborResizable, neighborCellName } = this.getNeighborOf(this.currentResizableClass);

      if (!isNeighborResizable) {
        return;
      }

      const $elementsOfTypeNeighbor = $(`.${neighborCellName}`);
      const neighborWidth = $elementsOfTypeNeighbor.width();
      const newNeighborWidth = neighborWidth - difference;

      const $elementsOfTypeCurrentResizable = $(`.${this.currentResizableClass}`);
      const currentResizableWidth = $elementsOfTypeCurrentResizable.width();
      const newCurrentResizableWidth = currentResizableWidth + difference;

      if (newNeighborWidth < 50 || newCurrentResizableWidth < 50) {
        this.offTarget = true;
        return;
      }

      this.currentX = event.clientX;
      $elementsOfTypeCurrentResizable.width(newCurrentResizableWidth);
      setToolkitStorageKey(`column-width-${this.currentResizableClass}`, newCurrentResizableWidth);

      $elementsOfTypeNeighbor.width(newNeighborWidth);
      setToolkitStorageKey(`column-width-${neighborCellName}`, newNeighborWidth);
    }, 100);
  }

  onMouseUp() {
    $('body').off('mousemove', this.bindOnMouseMove);
    $('body').off('mouseup', this.bindOnMouseUp);

    if (this.isMouseDown) {
      this.isMouseDown = false;
      this.elementWasDragged = true;
    }
  }

  getNeighborOf(neighborOf) {
    const $rightNeighbor = $(`.${neighborOf}`, '.ynab-grid-header').next();

    if (!$rightNeighbor.length) {
      return { isNeighborResizable: false };
    }

    const neighborCellName = $rightNeighbor.prop('class').match(/ynab-grid-cell.*/)[0];
    return {
      neighborCellName,
      isNeighborResizable: RESIZABLES.some((className) => className === neighborCellName)
    };
  }

  resetWidths() {
    RESIZABLES.forEach((resizableClass) => {
      $(`.${resizableClass}`).width('');
      removeToolkitStorageKey(`column-width-${resizableClass}`);
    });
  }

  invoke() {
    if (!$('.toolkit-reset-widths').length) {
      $('<button class="toolkit-reset-widths button">Reset Column Widths</button>')
        .click(this.resetWidths)
        .insertAfter($('.accounts-toolbar-all-dates', '.accounts-toolbar-right'));
    }

    RESIZABLES.forEach((resizableClass) => {
      const width = getToolkitStorageKey(`column-width-${resizableClass}`, 'number');
      if (width) {
        $(`.${resizableClass}`).width(width);
      }

      if (!this.getNeighborOf(resizableClass).isNeighborResizable) {
        return;
      }

      if ($(`.${resizableClass} .toolkit-draggable`, '.ynab-grid-header').length) {
        return;
      }

      $(`.${resizableClass}`, '.ynab-grid-header')
        .click((event) => {
          if (this.elementWasDragged) {
            event.preventDefault();
            event.stopPropagation();
            this.elementWasDragged = false;
          }
        })
        .css({ position: 'relative' })
        .append(
          $('

')
            .click((event) => event.stopPropagation())
            .mousedown((event) => {
              this.isMouseDown = true;
              this.currentX = event.clientX;
              this.currentResizableClass = resizableClass;

              this.bindOnMouseMove = this.onMouseMove.bind(this);
              this.bindOnMouseUp = this.onMouseUp.bind(this);
              $('body').on('mousemove', this.bindOnMouseMove);
              $('body').on('mouseup', this.bindOnMouseUp);
            })
          );
    });
  }

  onRouteChanged() {
    if (!this.shouldInvoke()) { return; }
    this.invoke();
  }
}
<|endoftext|>