<commit_before>define(function(require, exports, module) {
    main.consumes = [
        "Plugin", "ui", "layout", "commands", "Dialog", "menus",
        "dialog.alert", "tree.favorites", "tree", "c9", "fs.cache",
        "metrics", "c9.analytics", "error_handler", "preferences.experimental",
        "settings", "info"
    ];
    main.provides = ["mount", "MountTab"];
    return main;

    function main(options, imports, register) {
        var Plugin = imports.Plugin;
        var Dialog = imports.Dialog;
        var c9 = imports.c9;
        var ui = imports.ui;
        var tree = imports.tree;
        var info = imports.info;
        var commands = imports.commands;
        var menus = imports.menus;
        var favs = imports["tree.favorites"];
        var fsCache = imports["fs.cache"];
        var alert = imports["dialog.alert"].show;
        var errorHandler = imports.error_handler;
        var metrics = imports.metrics;
        var analytics = imports["c9.analytics"];
        var experimental = imports["preferences.experimental"];
        var basename = require("path").basename;
        var settings = imports.settings;
        var GRANDFATHERED_USER_DATE = new Date("2015-11-15").getTime();
        var _ = require("lodash");
        var oberr = require("c9/oberr");

        /***** Initialization *****/

        var handle = new Dialog("Ajax.org", main.consumes, {
            name: "dialog.mount",
            allowClose: true,
            dark: true,
            title: "Mount",
            width: 400,
            zindex: 100000,
            modal: true,
            elements: [
                { type: "filler" },
                { type: "button", id: "cancel", caption: "Cancel", onclick: cancel },
                { type: "button", id: "create", color: "green", caption: "Create", onclick: create }
            ]
        });

        var emit = handle.getEmitter();

        var CANCELERROR = -1;

        var sections = []; sections.lowest = 100000;
        var mounting = {};
        var container, box, active, loading;

        var drawn = false;
        function draw(options) {
            if (drawn) return;
            drawn = true;

            container = options.html;

            var bar = new ui.bar({
                left: "0",
                right: "0",
                top: "-15",
                zindex: 10,
                style: "text-align:center;pointer-events:none",
                childNodes: [
                    box = new ui.hbox({
                        class: "grouped_checkbox_holder with_caption mount",
                        align: "start",
                        height: "27",
                        style: "display:inline-block !important;pointer-events:all"
                    })
                ]
            });

            ui.insertCss(require("text!./mount.css"),
                options.staticPrefix, handle);

            options.aml.parentNode.appendChild(bar);

            container.innerHTML = '
Mounting...
';
            loading = container.firstChild;
            loading.style.display = "none";

            handle.on("hide", function(){
                loading.style.display = "none";
                handle.update([{ id:"cancel", zindex:"" }]);
            });
        }

        var loaded = false;
        function load() {
            if (loaded) return false;
            loaded = true;

            favs.on("favoriteRemove", function(e){
                if (e.node.mountType)
                    unmount(e.node.mountType, { path: e.node.path });
            });

            fsCache.on("readdir", function(e){
                var node = favs.isFavoritePath(e.path);
                if (node && node.mountType) {
                    if (!Object.keys(e.parent.map).length) {
                        var options = node.mountOptions;
                        var plugin = (sections[node.mountType] || 1).plugin;
                        if (!plugin) return; // Do Nothing

                        plugin.verify(options.mountpoint, function(err){
                            if (!err) return;

                            // Mount doesn't exist anymore, let's create it
                            mount(node.mountType, options, {remount: true}, function(err){
                                if (err) return;

                                tree.refresh([node], function(){
                                    tree.expand(node);
                                });
                            });
                        }, -1); // Only test once
                    }
                }
            });

            tree.on("menuUpdate", function(e){
                var caption = e.node && e.node.mountType
                    ? "Remove Mount"
                    : "Remove from Favorites";

                e.menu.childNodes.some(function(item) {
                    if (item.command == "removefavorite") {
                        item.setAttribute("caption", caption);
                        return true;
                    }
                });
            });

            // tree.getElement("mnuCtxTree", function(mnuCtxTree) {
            //     ui.insertByIndex(mnuCtxTree, new ui.item({
            //         match: "",
            //         caption: "Create a Mount",
            //         command: "mount"
            //     }), 1030, handle);
            // });
            loadUI();
        }

        function loadUI() {
            settings.on("read", function(){
                settings.setDefaults("user/mount", [
                    ["grandfathered-shown", false],
                ]);
            }, handle);

            var isGrandfatheredShown = settings.get("user/mount/grandfathered-shown");
            if (!isExperimentEnabled() && (!isGrandfatheredUser() || isGrandfatheredShown))
                return;

            commands.addCommand({
                name: "mount",
                bindKey: { mac: "Command-Option-B", win: "" },
                exec: function(editor, args) {
                    checkGrandfatheredUser(function() {
                        if (args.type) {
                            startMount(args.type, args);
                        }
                        else show();
                    });
                }
            }, handle);

            menus.addItemByPath("File/Mount FTP or SFTP server...", new ui.item({
                command: "mount"
            }), 1250, handle);
        }

        /***** Methods *****/

        function checkGrandfatheredUser(useFeature) {
            if (!isGrandfatheredUser() || isExperimentEnabled())
                return useFeature();

            // Disable the feature next session
            settings.set("user/mount/grandfathered-shown", true);

            alert(
                "Mount",
                "The Mount feature is considered experimental.",
                "We introduced a new preferences panel for experimental features! "
                + "To try the Mount feature or any other of our experiments, "
                + "please enable it feature first in Cloud9 > Preferences > Experimental.",
                null,
                { isHTML: true }
            );
        }

        function isGrandfatheredUser() {
            return info.getUser().date_add < GRANDFATHERED_USER_DATE;
        }

        function isExperimentEnabled() {
            return experimental.addExperiment("mount", false, "Files/(S)FTP Mounting");
        }

        function cancel(){
            handle.hide();
            active.plugin.cancel();
            emit("cancel", {});
        }

        function create(){
            if (active.plugin.validate())
                startMount(active.name);
        }

        function progress(options){
            if (!loading) return;

            loading.className = "mount-loading";
            loading.style.display = options.complete ? "none" : "block";

            handle.update([{ id:"cancel", zindex:10000 }]);

            if (options.caption)
                loading.lastChild.innerHTML = options.caption;
        }

        function error(options){
            loading.className = "mount-loading error";
            loading.style.display = "block";

            handle.update([{ id:"cancel", zindex:"" }]);

            if (options.caption)
                loading.lastChild.innerHTML = options.caption;
        }

        function startMount(type) {
            // Get the arguments here as we want whatever arguments were filled out passed to sftp then ftp.
            var plugin = sections[type].plugin;
            var args = plugin.getArgsFromUI();

            // If we are mounting ftp on port 21 attempt to mount sftp first, if that fails mount with ftp
            if (type == "ftp" && (args.port == 21 || args.port == 22)) {
                var sftpArgs = _.extend({}, args, {port: 22});
                mount("sftp", sftpArgs, {silent: true}, function (err) {
                    if (err) {
                        // sftp mounting failed, lets try again with our original mount type
                        mount(type, args);
                    }
                });
            }
            else {
                mount(type, args);
            }
        }

        function sanitizeLoginInformation(loginInformation) {
            return loginInformation.replace(/[@,]/g, encodeURIComponent);
        }

        function mount(type, args, options, callback){
            options = options || {};
            var remount = !!options.remount;
            var silent = !!options.silent;
            var section = sections[type];
            var plugin = section.plugin;

            if (!args)
                args = { fromUI : true };
            else if (mounting[args.mountpoint])
                return done(new Error("Already mounting this mountpoint"));

            mounting[args.mountpoint] = true;
            progress({ caption: "Mounting..." });

            function done(err){
                delete mounting[args.mountpoint];
                callback && callback(err);
            }

            args.user = sanitizeLoginInformation(args.user);
            args.pass = sanitizeLoginInformation(args.pass);

            plugin.mount(args, function(err, options){
                progress({ complete: true });

                if (err) {
                    if (err == CANCELERROR)
                        return done(err);

                    errorHandler.log(new Error("Failed to create mount"), {mountError: oberr(err), type: type, args: args, options: options})

                    var word = remount ? "refresh" : "create";
                    if (err.code == "EINSTALL") {
                        analytics.log("Was asked to install package before mounting");
                        !silent && alert("Failed to " + word + " an " + section.name + " Mount",
                            "Please install the " + err.message + " package",
                            "Install the package on an ubuntu system using "
                                + "sudo apt-get install " + err.message
                                + ". For other systems check out the "
                                + "documentation of your operating system. "
                                + "Please try again after installing this package.");
                    }
                    else if (err.code == "EACCESS") {
                        !silent && alert("Failed to " + word.toUpperCase() + " an " + section.name.toUpperCase() + " Mount",
                            "Please verify your Username/Password",
                            err.message);
                    }
                    else {
                        metrics.increment("mount.failed");
                        !silent && alert("Failed to " + word.toUpperCase() + " an " + section.name.toUpperCase() + " Mount",
                            "An error occurred while creating the mount:",
                            err.message);
                    }
                }
                else {
                    if (!remount) {
                        handle.hide();

                        // Clean up any old favorite
                        favs.removeFavorite(options.path);

                        // Create new favorite to mount point
                        var favNode = favs.addFavorite(options.path,
                            options.name + "/" + basename(options.path), true);
                        favNode.mountType = options.type;
                        favNode.mountOptions = options.args;
                        favNode.excludeFilelist = true;
                    }

                    metrics.increment("mount");
                    analytics.log("Mounted volume", {
                        path: options.path,
                        type: options.type
                    });

                    emit("mount", {});
                }

                done(err);
            });
        }

        function unmount(type, args, options, callback){
            var plugin = sections[type].plugin;

            plugin.unmount(args, function(){
                handle.hide();
                emit("unmount", {});
                callback && callback();
            });
        }

        function addSection(options, plugin, section){
            if (!section) {
                section = {
                    name: options.name || options.caption,
                    plugin: plugin
                };
                sections.push(section)
                sections[section.name] = section;
            };

            if (!drawn) {
                handle.on("draw", addSection.bind(null, options, plugin, section));
                return;
            }

            var btn = new ui.checkbox({
                label: options.caption,
                skin: "grouped_checkbox",
                "onmousedown": function(){
                    activate(section);
                    btn.$refKeyDown--;
                }
            });

            ui.insertByIndex(box, btn, options.index, plugin);

            section.button = btn;

            if (options.index < sections.lowest) {
                sections.lowest = options.index;
                activate(section);
            }

            return section;
        }

        function activate(section){
            if (typeof section == "string")
                section = sections[section];

            active = section;

            sections.forEach(function(s){
                if (!s.button) return;
                if (s != section)
                    s.button.uncheck();
                else
                    s.button.check();
            });

            emit("activate", { section: section });

            // Focus first element
            var nodes = section.plugin.aml.getElementsByTagName("*");
            nodes.some(function(node){
                if (node.focussable) {
                    node.focus();
                    return true;
                }
            });
        }

        function show(reset, options) {
            analytics.log("Opened mount dialog");

            if (!options)
                options = {};

            return handle.queue(function(){
                // if (reset || current == -1) {
                //     path = [startPage];
                //     current = 0;
                //     activate(startPage);
                // }

            }, true);
        }

        /***** Lifecycle *****/

        handle.on("draw", function(options) {
            draw(options);
        });
        handle.on("load", function() {
            load();
        });
        handle.on("enable", function() {

        });
        handle.on("disable", function() {

        });
        handle.on("unload", function() {
            loaded = false;
            drawn = false;
        });

        /***** Register and define API *****/

        /**
         *
         */
        handle.freezePublicAPI({
            CANCELERROR: CANCELERROR,

            events: [
                /**
                 *
                 */
                "cancel",
                /**
                 *
                 */
                "mount",
                /**
                 *
                 */
                "unmount",
                /**
                 *
                 */
                "activate",
            ],

            sanitizeLoginInformation: sanitizeLoginInformation,

            /**
             *
             */
            cancel: cancel,

            /**
             *
             */
            progress: progress,

            /**
             *
             */
            error: error
        });

        function MountTab(developer, consumes, options) {
            var plugin = new Plugin(developer, consumes);
            var emit = plugin.getEmitter();

            var name = options.name || options.caption;
            var htmlPage, aml;

            // Create UI Button
            handle.on("activate", function(e){
                if (e.section.name == name)
                    show();
                else
                    hide();
            });

            var drawn;
            function draw(){
                if (drawn) return;
                drawn = true;

                aml = new ui.bar({ htmlNode: container });
                htmlPage = aml.$ext;
                plugin.addOther(function(){
                    htmlPage.parentNode.removeChild(htmlPage);
                });

                htmlPage.className = "basic mount-container";

                emit.sticky("draw", { html: htmlPage, aml: aml });
            }

            /***** Methods *****/

            function hide(){
                if (!drawn || !htmlPage.parentNode)
                    return;

                htmlPage.parentNode.removeChild(htmlPage);
            }

            function show(options) {
                draw();
                container.host.appendChild(htmlPage.host);
            }

            /***** Register and define API *****/

            plugin.on("load", function(){
                addSection(options, plugin);
            });

            plugin.freezePublicAPI.baseclass();

            /**
             *
             */
            plugin.freezePublicAPI({
                /**
                 *
                 */
                get mountType(){ return name; },

                /**
                 *
                 */
                get aml(){ return aml; },

                /**
                 *
                 */
                get container(){ return htmlPage; },

                /**
                 *
                 */
                show: show,

                /**
                 *
                 */
                hide: hide
            });

            return plugin;
        }

        register(null, {
            mount: handle,
            MountTab: MountTab
        });
    }
});<commit_msg>Fix bug with containers not stopping when ftp mounts are dead, also fix bug with mounts not reloading when the container is restarted<commit_after>define(function(require, exports, module) {
    main.consumes = [
        "Plugin", "ui", "layout", "commands", "Dialog", "menus",
        "dialog.alert", "tree.favorites", "tree", "c9", "fs.cache",
        "metrics", "c9.analytics", "error_handler", "preferences.experimental",
        "settings", "info"
    ];
    main.provides = ["mount", "MountTab"];
    return main;

    function main(options, imports, register) {
        var Plugin = imports.Plugin;
        var Dialog = imports.Dialog;
        var c9 = imports.c9;
        var ui = imports.ui;
        var tree = imports.tree;
        var info = imports.info;
        var commands = imports.commands;
        var menus = imports.menus;
        var favs = imports["tree.favorites"];
        var fsCache = imports["fs.cache"];
        var alert = imports["dialog.alert"].show;
        var errorHandler = imports.error_handler;
        var metrics = imports.metrics;
        var analytics = imports["c9.analytics"];
        var experimental = imports["preferences.experimental"];
        var basename = require("path").basename;
        var settings = imports.settings;
        var GRANDFATHERED_USER_DATE = new Date("2015-11-15").getTime();
        var _ = require("lodash");
        var oberr = require("c9/oberr");

        /***** Initialization *****/

        var handle = new Dialog("Ajax.org", main.consumes, {
            name: "dialog.mount",
            allowClose: true,
            dark: true,
            title: "Mount",
            width: 400,
            zindex: 100000,
            modal: true,
            elements: [
                { type: "filler" },
                { type: "button", id: "cancel", caption: "Cancel", onclick: cancel },
                { type: "button", id: "create", color: "green", caption: "Create", onclick: create }
            ]
        });

        var emit = handle.getEmitter();

        var CANCELERROR = -1;

        var sections = []; sections.lowest = 100000;
        var mounting = {};
        var container, box, active, loading;

        var drawn = false;
        function draw(options) {
            if (drawn) return;
            drawn = true;

            container = options.html;

            var bar = new ui.bar({
                left: "0",
                right: "0",
                top: "-15",
                zindex: 10,
                style: "text-align:center;pointer-events:none",
                childNodes: [
                    box = new ui.hbox({
                        class: "grouped_checkbox_holder with_caption mount",
                        align: "start",
                        height: "27",
                        style: "display:inline-block !important;pointer-events:all"
                    })
                ]
            });

            ui.insertCss(require("text!./mount.css"),
                options.staticPrefix, handle);

            options.aml.parentNode.appendChild(bar);

            container.innerHTML = '
Mounting...
';
            loading = container.firstChild;
            loading.style.display = "none";

            handle.on("hide", function(){
                loading.style.display = "none";
                handle.update([{ id:"cancel", zindex:"" }]);
            });
        }

        var loaded = false;
        function load() {
            if (loaded) return false;
            loaded = true;

            favs.on("favoriteRemove", function(e){
                if (e.node.mountType)
                    unmount(e.node.mountType, { path: e.node.path });
            });

            favs.favorites.forEach(function (path) {
                var node = favs.isFavoritePath(path);
                if (node && node.mountType) {
                    var options = node.mountOptions;
                    var plugin = (sections[node.mountType] || 1).plugin;
                    if (!plugin) return; // Do Nothing

                    plugin.verify(options.mountpoint, function(err){
                        if (!err) return;

                        // Mount doesn't exist anymore, let's create it
                        mount(node.mountType, options, {remount: true}, function(err){
                            if (err) return;

                            tree.refresh([node], function(){
                                tree.expand(node);
                            });
                        });
                    }, -1); // Only test once
                }
            });

            tree.on("menuUpdate", function(e){
                var caption = e.node && e.node.mountType
                    ? "Remove Mount"
                    : "Remove from Favorites";

                e.menu.childNodes.some(function(item) {
                    if (item.command == "removefavorite") {
                        item.setAttribute("caption", caption);
                        return true;
                    }
                });
            });

            // tree.getElement("mnuCtxTree", function(mnuCtxTree) {
            //     ui.insertByIndex(mnuCtxTree, new ui.item({
            //         match: "",
            //         caption: "Create a Mount",
            //         command: "mount"
            //     }), 1030, handle);
            // });
            loadUI();
        }

        function loadUI() {
            settings.on("read", function(){
                settings.setDefaults("user/mount", [
                    ["grandfathered-shown", false],
                ]);
            }, handle);

            var isGrandfatheredShown = settings.get("user/mount/grandfathered-shown");
            if (!isExperimentEnabled() && (!isGrandfatheredUser() || isGrandfatheredShown))
                return;

            commands.addCommand({
                name: "mount",
                bindKey: { mac: "Command-Option-B", win: "" },
                exec: function(editor, args) {
                    checkGrandfatheredUser(function() {
                        if (args.type) {
                            startMount(args.type, args);
                        }
                        else show();
                    });
                }
            }, handle);

            menus.addItemByPath("File/Mount FTP or SFTP server...", new ui.item({
                command: "mount"
            }), 1250, handle);
        }

        /***** Methods *****/

        function checkGrandfatheredUser(useFeature) {
            if (!isGrandfatheredUser() || isExperimentEnabled())
                return useFeature();

            // Disable the feature next session
            settings.set("user/mount/grandfathered-shown", true);

            alert(
                "Mount",
                "The Mount feature is considered experimental.",
                "We introduced a new preferences panel for experimental features! "
                + "To try the Mount feature or any other of our experiments, "
                + "please enable it feature first in Cloud9 > Preferences > Experimental.",
                null,
                { isHTML: true }
            );
        }

        function isGrandfatheredUser() {
            return info.getUser().date_add < GRANDFATHERED_USER_DATE;
        }

        function isExperimentEnabled() {
            return experimental.addExperiment("mount", false, "Files/(S)FTP Mounting");
        }

        function cancel(){
            handle.hide();
            active.plugin.cancel();
            emit("cancel", {});
        }

        function create(){
            if (active.plugin.validate())
                startMount(active.name);
        }

        function progress(options){
            if (!loading) return;

            loading.className = "mount-loading";
            loading.style.display = options.complete ? "none" : "block";

            handle.update([{ id:"cancel", zindex:10000 }]);

            if (options.caption)
                loading.lastChild.innerHTML = options.caption;
        }

        function error(options){
            loading.className = "mount-loading error";
            loading.style.display = "block";

            handle.update([{ id:"cancel", zindex:"" }]);

            if (options.caption)
                loading.lastChild.innerHTML = options.caption;
        }

        function startMount(type) {
            // Get the arguments here as we want whatever arguments were filled out passed to sftp then ftp.
            var plugin = sections[type].plugin;
            var args = plugin.getArgsFromUI();

            // If we are mounting ftp on port 21 attempt to mount sftp first, if that fails mount with ftp
            if (type == "ftp" && (args.port == 21 || args.port == 22)) {
                var sftpArgs = _.extend({}, args, {port: 22});
                mount("sftp", sftpArgs, {silent: true}, function (err) {
                    if (err) {
                        // sftp mounting failed, lets try again with our original mount type
                        mount(type, args);
                    }
                });
            }
            else {
                mount(type, args);
            }
        }

        function sanitizeLoginInformation(loginInformation) {
            return loginInformation.replace(/[@,]/g, encodeURIComponent);
        }

        function mount(type, args, options, callback){
            options = options || {};
            var remount = !!options.remount;
            var silent = !!options.silent;
            var section = sections[type];
            var plugin = section.plugin;

            if (!args)
                args = { fromUI : true };
            else if (mounting[args.mountpoint])
                return done(new Error("Already mounting this mountpoint"));

            mounting[args.mountpoint] = true;
            progress({ caption: "Mounting..." });

            function done(err){
                delete mounting[args.mountpoint];
                callback && callback(err);
            }

            args.user = sanitizeLoginInformation(args.user);
            args.pass = sanitizeLoginInformation(args.pass);

            plugin.mount(args, function(err, options){
                progress({ complete: true });

                if (err) {
                    if (err == CANCELERROR)
                        return done(err);

                    errorHandler.log(new Error("Failed to create mount"), {mountError: oberr(err), type: type, args: args, options: options})

                    var word = remount ? "refresh" : "create";
                    if (err.code == "EINSTALL") {
                        analytics.log("Was asked to install package before mounting");
                        !silent && alert("Failed to " + word + " an " + section.name + " Mount",
                            "Please install the " + err.message + " package",
                            "Install the package on an ubuntu system using "
                                + "sudo apt-get install " + err.message
                                + ". For other systems check out the "
                                + "documentation of your operating system. "
                                + "Please try again after installing this package.");
                    }
                    else if (err.code == "EACCESS") {
                        !silent && alert("Failed to " + word.toUpperCase() + " an " + section.name.toUpperCase() + " Mount",
                            "Please verify your Username/Password",
                            err.message);
                    }
                    else {
                        metrics.increment("mount.failed");
                        !silent && alert("Failed to " + word.toUpperCase() + " an " + section.name.toUpperCase() + " Mount",
                            "An error occurred while creating the mount:",
                            err.message);
                    }
                }
                else {
                    if (!remount) {
                        handle.hide();

                        // Clean up any old favorite
                        favs.removeFavorite(options.path);

                        // Create new favorite to mount point
                        var favNode = favs.addFavorite(options.path,
                            options.name + "/" + basename(options.path), true);
                        favNode.mountType = options.type;
                        favNode.mountOptions = options.args;
                        favNode.excludeFilelist = true;
                    }

                    metrics.increment("mount");
                    analytics.log("Mounted volume", {
                        path: options.path,
                        type: options.type
                    });

                    emit("mount", {});
                }

                done(err);
            });
        }

        function unmount(type, args, options, callback){
            var plugin = sections[type].plugin;

            plugin.unmount(args, function(){
                handle.hide();
                emit("unmount", {});
                callback && callback();
            });
        }

        function addSection(options, plugin, section){
            if (!section) {
                section = {
                    name: options.name || options.caption,
                    plugin: plugin
                };
                sections.push(section)
                sections[section.name] = section;
            };

            if (!drawn) {
                handle.on("draw", addSection.bind(null, options, plugin, section));
                return;
            }

            var btn = new ui.checkbox({
                label: options.caption,
                skin: "grouped_checkbox",
                "onmousedown": function(){
                    activate(section);
                    btn.$refKeyDown--;
                }
            });

            ui.insertByIndex(box, btn, options.index, plugin);

            section.button = btn;

            if (options.index < sections.lowest) {
                sections.lowest = options.index;
                activate(section);
            }

            return section;
        }

        function activate(section){
            if (typeof section == "string")
                section = sections[section];

            active = section;

            sections.forEach(function(s){
                if (!s.button) return;
                if (s != section)
                    s.button.uncheck();
                else
                    s.button.check();
            });

            emit("activate", { section: section });

            // Focus first element
            var nodes = section.plugin.aml.getElementsByTagName("*");
            nodes.some(function(node){
                if (node.focussable) {
                    node.focus();
                    return true;
                }
            });
        }

        function show(reset, options) {
            analytics.log("Opened mount dialog");

            if (!options)
                options = {};

            return handle.queue(function(){
                // if (reset || current == -1) {
                //     path = [startPage];
                //     current = 0;
                //     activate(startPage);
                // }

            }, true);
        }

        /***** Lifecycle *****/

        handle.on("draw", function(options) {
            draw(options);
        });
        handle.on("load", function() {
            load();
        });
        handle.on("enable", function() {

        });
        handle.on("disable", function() {

        });
        handle.on("unload", function() {
            loaded = false;
            drawn = false;
        });

        /***** Register and define API *****/

        /**
         *
         */
        handle.freezePublicAPI({
            CANCELERROR: CANCELERROR,

            events: [
                /**
                 *
                 */
                "cancel",
                /**
                 *
                 */
                "mount",
                /**
                 *
                 */
                "unmount",
                /**
                 *
                 */
                "activate",
            ],

            sanitizeLoginInformation: sanitizeLoginInformation,

            /**
             *
             */
            cancel: cancel,

            /**
             *
             */
            progress: progress,

            /**
             *
             */
            error: error
        });

        function MountTab(developer, consumes, options) {
            var plugin = new Plugin(developer, consumes);
            var emit = plugin.getEmitter();

            var name = options.name || options.caption;
            var htmlPage, aml;

            // Create UI Button
            handle.on("activate", function(e){
                if (e.section.name == name)
                    show();
                else
                    hide();
            });

            var drawn;
            function draw(){
                if (drawn) return;
                drawn = true;

                aml = new ui.bar({ htmlNode: container });
                htmlPage = aml.$ext;
                plugin.addOther(function(){
                    htmlPage.parentNode.removeChild(htmlPage);
                });

                htmlPage.className = "basic mount-container";

                emit.sticky("draw", { html: htmlPage, aml: aml });
            }

            /***** Methods *****/

            function hide(){
                if (!drawn || !htmlPage.parentNode)
                    return;

                htmlPage.parentNode.removeChild(htmlPage);
            }

            function show(options) {
                draw();
                container.host.appendChild(htmlPage.host);
            }

            /***** Register and define API *****/

            plugin.on("load", function(){
                addSection(options, plugin);
            });

            plugin.freezePublicAPI.baseclass();

            /**
             *
             */
            plugin.freezePublicAPI({
                /**
                 *
                 */
                get mountType(){ return name; },

                /**
                 *
                 */
                get aml(){ return aml; },

                /**
                 *
                 */
                get container(){ return htmlPage; },

                /**
                 *
                 */
                show: show,

                /**
                 *
                 */
                hide: hide
            });

            return plugin;
        }

        register(null, {
            mount: handle,
            MountTab: MountTab
        });
    }
});<|endoftext|>


<commit_before>module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'+
        '/*\n' +
        '* @description Google Chart Api Directive Module for AngularJS\n'+
        '* @version 0.0.11\n'+
        '* @author Nicolas Bouillon <nicolas@bouil.org>\n'+
        '* @author GitHub contributors\n'+
        '* @license MIT\n'+
        '* @year 2013\n'+
        '*/\n',
        sourceMap: true
      },
      build: {
        src: ['src/googlechart.module.js', 'src/*.js'],
        dest: 'ng-google-chart.js'
      }
    },
    jshint: {
      options:{
        jshintrc: true
      },
      all: ['Gruntfile.js', 'sample.js', 'partials/**/*.js', 'src/**/*.js']
    },
    uglify: {
      options:{
        sourceMapIn: 'ng-google-chart.js.map',
        sourceMap: true
      },
      build:{
        files: {
          'ng-google-chart.min.js': ['ng-google-chart.js']
        }
      }
    },
    karma: {
      unit: {
        configFile: 'karma.conf.js',
        singleRun: true
      }
    },
    watch: {
      files: ['src/**.js'],
      tasks:['jshint:all', 'karma:unit']
    }
  });

  // Load the plugin that provides the "uglify" task.
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-karma');

  // Default task(s).
  grunt.registerTask('default', ['jshint:all', 'concat', 'uglify']);

};<commit_msg>chore: add spec files in test directory to watch task<commit_after>module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'+
        '/*\n' +
        '* @description Google Chart Api Directive Module for AngularJS\n'+
        '* @version 0.0.11\n'+
        '* @author Nicolas Bouillon <nicolas@bouil.org>\n'+
        '* @author GitHub contributors\n'+
        '* @license MIT\n'+
        '* @year 2013\n'+
        '*/\n',
        sourceMap: true
      },
      build: {
        src: ['src/googlechart.module.js', 'src/*.js'],
        dest: 'ng-google-chart.js'
      }
    },
    jshint: {
      options:{
        jshintrc: true
      },
      all: ['Gruntfile.js', 'sample.js', 'partials/**/*.js', 'src/**/*.js']
    },
    uglify: {
      options:{
        sourceMapIn: 'ng-google-chart.js.map',
        sourceMap: true
      },
      build:{
        files: {
          'ng-google-chart.min.js': ['ng-google-chart.js']
        }
      }
    },
    karma: {
      unit: {
        configFile: 'karma.conf.js',
        singleRun: true
      }
    },
    watch: {
      files: ['src/**.js','test/**/*.spec.js'],
      tasks:['jshint:all', 'karma:unit']
    }
  });

  // Load the plugin that provides the "uglify" task.
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-karma');

  // Default task(s).
  grunt.registerTask('default', ['jshint:all', 'concat', 'uglify']);

};<|endoftext|>


<commit_before><commit_msg>#5409: redirect changes<commit_after><|endoftext|>


<commit_before>var outputDisplay; // defaults are the first valid options in the displays array
var outputAudio; // set in main.js
var displayBlanked = false;
var canGetVolume = false;
var sliderBuilt = false;
var selectedColor = "#a8a8a8";

function setDisplayOutput(device, e) {
	console.log("set display output to:", device);
	outputDisplay = device;

	// re-highlight the previous input
	if (displayInputs[outputDisplay].length != 0) {
		console.log("checking displayInputs[" + outputDisplay + "]. It's current value is " + displayInputs[outputDisplay]);
		// find the correct input button to re highlight
		$('.display-input-button').each(function(i, obj) {
			obj.style.backgroundColor = "white"; // set all to white
			if(obj.innerHTML == displayInputs[outputDisplay]) {
				console.log(obj);
				obj.style.backgroundColor = selectedColor;
			}
		});
	} else {
		$('.display-input-button').each(function(i, obj) {
	    	obj.style.backgroundColor = "white"; // set all to white
		});
	}

	// remove color from all display output buttons
	$('.display-output-button').each(function(i, obj) {
    	obj.style.backgroundColor = "white";
	});

	// change visual for active device
	e.style.backgroundColor = selectedColor;
}

function setAudioOutput(device, e) {
	console.log("set audio output to:", device);
	outputAudio = device;

	// if output device isn't an RPC device, create a slider to control it with
	for (var i in devices) {
		if (devices[i].name == outputAudio) {
			for (var j in devices[i].roles) {
				for (var k in devices[i].commands) {
					if (devices[i].commands[k].name == "SetVolume") {
						canGetVolume = true;
						break;
					}
				}

				if (devices[i].roles[j] == "AudioOut" && canGetVolume) {
					if (!sliderBuilt) {
						slider = document.createElement("INPUT");
						slider.setAttribute("id", "slider");
						slider.setAttribute("type", "range");
						// edit volume dynamically as the slider changes
						slider.onchange = function() {
							setVolume(slider)
						};

						console.log("adding a slider");
						document.getElementById("vol-slider").appendChild(slider);
						sliderBuilt = true;
					}

					$("#vol-up").hide();
					$("#vol-down").hide();
					$("#vol-slider").show();
				} else {
					// change it back to buttons if it's RPC
					$("#vol-slider").hide();
					$("#vol-up").show();
					$("#vol-down").show();
				}
			}
		}
	}

	// re-highlight the previous input
	if (audioInputs[outputAudio].length != 0) {
		console.log("checking displayInputs[" + outputAudio + "]. It's current value is " + audioInputs[outputAudio]);
		// find the correct input button to re highlight
		$('.audio-input-button').each(function(i, obj) {
			obj.style.backgroundColor = "white"; // set all to white
			if(obj.innerHTML == audioInputs[outputAudio]) {
				console.log(obj);
				obj.style.backgroundColor = selectedColor;
			}
		});
	} else {
		$('.audio-input-button').each(function(i, obj) {
	    	obj.style.backgroundColor = "white"; // set all to white
		});
	}

	// remove color from all audio output buttons
	$('.audio-output-button').each(function(i, obj) {
    	obj.style.backgroundColor = "white";
	});

	// change visual for active device
	e.style.backgroundColor = selectedColor;
}

function switchDisplayInput(input, e) {
	console.log("switching display input of", outputDisplay, "to", input);

	// set previous input into the map
	console.log("mapped displayInputs[" + outputDisplay + "] to " + input);
	displayInputs[outputDisplay] = input;

	// remove color from all display input buttons
	$('.display-input-button').each(function(i, obj) {
    	obj.style.backgroundColor = "white";
	});

	// change visual for active device
	e.style.backgroundColor = selectedColor;

	var body = {};

	body = {
		displays: [{
			name: outputDisplay,
			input: input
		}],
	};
	put(body);
}

function switchAudioInput(input, e) {
	console.log("switching audio input of", outputAudio, "to", input);

	// remove color from all display output buttons
	$('.audio-input-button').each(function(i, obj) {
    	obj.style.backgroundColor = "white";
	});

	// change visual for active device
	e.style.backgroundColor = selectedColor;

	var body = {};

	body = {
		audioDevices: [{
			name: outputAudio,
			input: input
		}]
	};
	put(body);
}

function blankDisplay(e) {
	console.log("blank/unblank display");

	var body = {};

	body = {
		displays: [{
			name: outputDisplay,
			blanked: true
		}]
	};

	if (displayBlanked == true) {
		body.displays[0].blanked = false;
		displayBlanked = false;
		// set button to say "Blank"
		e.innerHTML = "Blank";
		e.style.backgroundColor = "white";
	} else {
		displayBlanked = true;
		// set button to say "Unblank"
		e.innerHTML = "Unblank";
		e.style.backgroundColor = selectedColor;
	}
	put(body);
}

var volumeIncrement = 1;

function increaseVolume() {
	if (volume == "MUTED") {
		volume = previousVolume;
	}

	if (volume < 100) {
		volume += volumeIncrement;
	}

	console.log("pressed volume up");

	var body = {
		audioDevices: [{
			name: outputAudio,
			volume: volume
		}]
	};
	quietPut(body);
}

function decreaseVolume() {
	if (volume == "MUTED") {
		volume = previousVolume;
	}

	if (volume > 0) {
		volume -= volumeIncrement;
	}

	console.log("pressed volume down");

	var body = {
		audioDevices: [{
			name: outputAudio,
			volume: volume
		}]
	};
	quietPut(body);
}

function muteVolume() {
	console.log("mute/unmute volume");

	var body = {
		audioDevices: [{
			name: outputAudio,
			muted: true
		}]
	};

	if (volume == "MUTED") {
		volume = previousVolume;
		body.audioDevices[0].muted = false;
	} else {
		previousVolume = volume;
		volume = "MUTED";
	}
	put(body);
}

function setVolume(e) {
	var vol = document.getElementById("slider").value;
	if (volume == "MUTED") {
		volume = previousVolume;
	} else {
		volume = vol;
	}

	console.log("moved volume slider to", vol);
	e.style.background = 'linear-gradient(to right, blue, #527090 ' + vol + '%, #e0e0e0)';

	var body = {
		audioDevices: [{
			name: outputAudio,
			volume: parseInt(vol)
		}]
	};
	quietPut(body);
}

function powerOnRoom() {
	console.log("turning room on");
	var body = {
		power: "on",
		displays: [{
			name: outputDisplay,
			power: "on",
		}]
	};

	// make it take longer to turn on so that input can change
	$.ajax({
		type: "PUT",
		url: url,
		async: false,
		headers: {
			'Access-Control-Allow-Origin': '*'
		},
		data: JSON.stringify(body),
		contentType: "application/json; charset=utf-8"
	});
	quietPut(body);
}

function powerOffRoom() {
	console.log("powering off room");
	// just to power down one (the current) device
	var body = {
		power: "standby"
	};

	quietPut(body);
}

function put(body) {
	console.log("put", body);
	$.ajax({
		type: "PUT",
		url: url,
		headers: {
			'Access-Control-Allow-Origin': '*'
		},
		data: JSON.stringify(body),
		success: pleaseWait(),
		contentType: "application/json; charset=utf-8"
	});
}

function quietPut(body) {
	console.log("quietPut", body);
	$.ajax({
		type: "PUT",
		url: url,
		headers: {
			'Access-Control-Allow-Origin': '*'
		},
		data: JSON.stringify(body),
		contentType: "application/json; charset=utf-8"
	});
}
<commit_msg>changed PUT body for RPC devices<commit_after>var outputDisplay; // defaults are the first valid options in the displays array
var outputAudio; // set in main.js
var displayBlanked = false;
var canGetVolume = false;
var sliderBuilt = false;
var selectedColor = "#a8a8a8";

function setDisplayOutput(device, e) {
	console.log("set display output to:", device);
	outputDisplay = device;

	// re-highlight the previous input
	if (displayInputs[outputDisplay].length != 0) {
		console.log("checking displayInputs[" + outputDisplay + "]. It's current value is " + displayInputs[outputDisplay]);
		// find the correct input button to re highlight
		$('.display-input-button').each(function(i, obj) {
			obj.style.backgroundColor = "white"; // set all to white
			if(obj.innerHTML == displayInputs[outputDisplay]) {
				console.log(obj);
				obj.style.backgroundColor = selectedColor;
			}
		});
	} else {
		$('.display-input-button').each(function(i, obj) {
	    	obj.style.backgroundColor = "white"; // set all to white
		});
	}

	// remove color from all display output buttons
	$('.display-output-button').each(function(i, obj) {
    	obj.style.backgroundColor = "white";
	});

	// change visual for active device
	e.style.backgroundColor = selectedColor;
}

function setAudioOutput(device, e) {
	console.log("set audio output to:", device);
	outputAudio = device;

	// if output device isn't an RPC device, create a slider to control it with
	for (var i in devices) {
		if (devices[i].name == outputAudio) {
			for (var j in devices[i].roles) {
				for (var k in devices[i].commands) {
					if (devices[i].commands[k].name == "SetVolume") {
						canGetVolume = true;
						break;
					}
				}

				if (devices[i].roles[j] == "AudioOut" && canGetVolume) {
					if (!sliderBuilt) {
						slider = document.createElement("INPUT");
						slider.setAttribute("id", "slider");
						slider.setAttribute("type", "range");
						// edit volume dynamically as the slider changes
						slider.onchange = function() {
							setVolume(slider)
						};

						console.log("adding a slider");
						document.getElementById("vol-slider").appendChild(slider);
						sliderBuilt = true;
					}

					$("#vol-up").hide();
					$("#vol-down").hide();
					$("#vol-slider").show();
				} else {
					// change it back to buttons if it's RPC
					$("#vol-slider").hide();
					$("#vol-up").show();
					$("#vol-down").show();
				}
			}
		}
	}

	// re-highlight the previous input
	if (audioInputs[outputAudio].length != 0) {
		console.log("checking displayInputs[" + outputAudio + "]. It's current value is " + audioInputs[outputAudio]);
		// find the correct input button to re highlight
		$('.audio-input-button').each(function(i, obj) {
			obj.style.backgroundColor = "white"; // set all to white
			if(obj.innerHTML == audioInputs[outputAudio]) {
				console.log(obj);
				obj.style.backgroundColor = selectedColor;
			}
		});
	} else {
		$('.audio-input-button').each(function(i, obj) {
	    	obj.style.backgroundColor = "white"; // set all to white
		});
	}

	// remove color from all audio output buttons
	$('.audio-output-button').each(function(i, obj) {
    	obj.style.backgroundColor = "white";
	});

	// change visual for active device
	e.style.backgroundColor = selectedColor;
}

function switchDisplayInput(input, e) {
	console.log("switching display input of", outputDisplay, "to", input);

	// set previous input into the map
	console.log("mapped displayInputs[" + outputDisplay + "] to " + input);
	displayInputs[outputDisplay] = input;

	// remove color from all display input buttons
	$('.display-input-button').each(function(i, obj) {
    	obj.style.backgroundColor = "white";
	});

	// change visual for active device
	e.style.backgroundColor = selectedColor;

	var body = {};

	body = {
		displays: [{
			name: outputDisplay,
			input: input
		}],
	};
	put(body);
}

function switchAudioInput(input, e) {
	console.log("switching audio input of", outputAudio, "to", input);

	// remove color from all display output buttons
	$('.audio-input-button').each(function(i, obj) {
    	obj.style.backgroundColor = "white";
	});

	// change visual for active device
	e.style.backgroundColor = selectedColor;

	var body = {};

	body = {
		audioDevices: [{
			name: outputAudio,
			input: input
		}]
	};
	put(body);
}

function blankDisplay(e) {
	console.log("blank/unblank display");

	var body = {};

	body = {
		displays: [{
			name: outputDisplay,
			blanked: true
		}]
	};

	if (displayBlanked == true) {
		body.displays[0].blanked = false;
		displayBlanked = false;
		// set button to say "Blank"
		e.innerHTML = "Blank";
		e.style.backgroundColor = "white";
	} else {
		displayBlanked = true;
		// set button to say "Unblank"
		e.innerHTML = "Unblank";
		e.style.backgroundColor = selectedColor;
	}
	put(body);
}

var volumeIncrement = 1;

function increaseVolume() {
	if (volume == "MUTED") {
		volume = previousVolume;
	}

	if (volume < 100) {
		volume += volumeIncrement;
	}

	console.log("pressed volume up");

	var body;
	// if its a RPC devices, make a specific command
	if (roomData.configuration.name == "TecLite") {
		console.log("RPC Command");
		body = {
			rpcDevices: [{
				name: outputAudio,
				commands: [{
					name: "VolumeUp"
				}]
			}]
		};
	} else {
		console.log("Regular Command");
		body = {
			audioDevices: [{
				name: outputAudio,
				volume: volume
			}]
		};
	}

	quietPut(body);
}

function decreaseVolume() {
	if (volume == "MUTED") {
		volume = previousVolume;
	}

	if (volume > 0) {
		volume -= volumeIncrement;
	}

	console.log("pressed volume down");

	var body;
	// if its a RPC devices, make a specific command
	if (roomData.configuration.name == "TecLite") {
		console.log("RPC Command");
		body = {
			rpcDevices: [{
				name: outputAudio,
				commands: [{
					name: "VolumeDown"
				}]
			}]
		};
	} else {
		console.log("Regular Command");
		body = {
			audioDevices: [{
				name: outputAudio,
				volume: volume
			}]
		};
	}

	quietPut(body);
}

function muteVolume() {
	console.log("mute/unmute volume");

	var body;
	// if its a RPC devices, make a specific command
	if (roomData.configuration.name == "TecLite") {
		console.log("RPC Command");
		body = {
			rpcDevices: [{
				name: outputAudio,
				commands: [{
					name: "Mute"
				}]
			}]
		};
		if (volume == "MUTED") {
			volume = previousVolume;
			body.rpcDevices[0].commands[0].name = "UnMute";
		} else {
			previousVolume = volume;
			volume = "MUTED";
		}

		console.log(body);
	} else {
		console.log("Regular Command");
		body = {
			audioDevices: [{
				name: outputAudio,
				muted: true
			}]
		};

		if (volume == "MUTED") {
			volume = previousVolume;
			body.audioDevices[0].muted = false;
		} else {
			previousVolume = volume;
			volume = "MUTED";
		}
	}

	put(body);
}

function setVolume(e) {
	var vol = document.getElementById("slider").value;
	if (volume == "MUTED") {
		volume = previousVolume;
	} else {
		volume = vol;
	}

	console.log("moved volume slider to", vol);
	e.style.background = 'linear-gradient(to right, blue, #527090 ' + vol + '%, #e0e0e0)';

	var body = {
		audioDevices: [{
			name: outputAudio,
			volume: parseInt(vol)
		}]
	};
	quietPut(body);
}

function powerOnRoom() {
	console.log("turning room on");
	var body = {
		power: "on",
		displays: [{
			name: outputDisplay,
			power: "on",
		}]
	};

	// make it take longer to turn on so that input can change
	$.ajax({
		type: "PUT",
		url: url,
		async: false,
		headers: {
			'Access-Control-Allow-Origin': '*'
		},
		data: JSON.stringify(body),
		contentType: "application/json; charset=utf-8"
	});
	quietPut(body);
}

function powerOffRoom() {
	console.log("powering off room");
	// just to power down one (the current) device
	var body = {
		power: "standby"
	};

	quietPut(body);
}

function put(body) {
	console.log("put", body);
	$.ajax({
		type: "PUT",
		url: url,
		headers: {
			'Access-Control-Allow-Origin': '*'
		},
		data: JSON.stringify(body),
		success: pleaseWait(),
		contentType: "application/json; charset=utf-8"
	});
}

function quietPut(body) {
	console.log("quietPut", body);
	$.ajax({
		type: "PUT",
		url: url,
		headers: {
			'Access-Control-Allow-Origin': '*'
		},
		data: JSON.stringify(body),
		contentType: "application/json; charset=utf-8"
	});
}
<|endoftext|>


<commit_before>/*! React Starter Kit | MIT License | http://www.reactstarterkit.com/ */

import React, { PropTypes } from 'react';
import styles from './PostPage.css';
import withStyles from '../../decorators/withStyles';
import env from '../../core/env';
import http from '../../core/http';

@withStyles(styles)
class PostPage {

  static propTypes = {
    path: PropTypes.string.isRequired,
    content: PropTypes.string.isRequired,
    title: PropTypes.string
  };

  static contextTypes = {
    onSetTitle: PropTypes.func.isRequired
  };

  render() {
    console.log(env.urlFor.content('posts', this.props.postId))
    this.context.onSetTitle(this.props.title);
    return (

          {
            this.props.path === '/' ? null :
{this.props.title}

          }

    );
  }

}

export default PostPage;
<commit_msg>Post page requirements<commit_after>/*! React Starter Kit | MIT License | http://www.reactstarterkit.com/ */

import React, { PropTypes } from 'react';
import styles from './PostPage.css';
import withStyles from '../../decorators/withStyles';
import Link from '../../utils/Link';

@withStyles(styles)
class PostPage {

  static propTypes = {
    post: PropTypes.object.isRequired
  };

  static contextTypes = {
    onSetTitle: PropTypes.func.isRequired
  };

  render() {
    this.context.onSetTitle(this.props.post.title);
    return (

{this.props.post.title}

          post 2

    );
  }

}

export default PostPage;
<|endoftext|>


<commit_before>'use strict'

var net = require('net');

/**
 * Constructor
 * @param {Object} httpAdapter Http Adapter
 * @param {Object} options     Options (language, client_id, client_secret)
 */
var AGOLGeocoder = function AGOLGeocoder(httpAdapter, options) {

  if (!httpAdapter || httpAdapter == 'undefined') {
    throw new Error('ArcGis Online Geocoder requires a httpAdapter to be defined');
  }

  if (!options || options == 'undefined') {
    options = {};
  }

  if (!options.client_id || options.client_id == 'undefined') {
    options.client_id = null;
  }

  if (!options.client_secret || options.client_secret == 'undefined') {
    options.client_secret = null;
  }

  if (!options.client_secret || !options.client_id) {

    throw new Error('You must specify the client_id and the client_secret');
  }

  this.options = options;

  this.httpAdapter = httpAdapter;
  this.cache = {};
};

AGOLGeocoder.prototype._authEndpoint = 'https://www.arcgis.com/sharing/oauth2/token';
AGOLGeocoder.prototype._endpoint = 'http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find';
AGOLGeocoder.prototype._reverseEndpoint = 'http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode';

//Cached vars

AGOLGeocoder.prototype._cachedToken = {
  'now': function() {
    return (new Date()).getTime();
  },
  'put': function(token, experation,cache) {
    cache.token = token;
    //Shave 30 secs off experation to ensure that we expire slightly before the actual expiration
    cache.tokenExp = this.now() + (experation - 30);
  },
  'get' : function(cache) {
    if(!cache) {
      return null;
    }

    if(this.now() <= cache.tokenExp) {
      return cache.token;
    } else {
      return null;
    }
  }
};

AGOLGeocoder.prototype._getToken = function(callback) {
  var _this = this;

  if(_this._cachedToken.get(_this.cache) !== null) {
    callback(_this._cachedToken.get());
    return;
    }

  var params = {
    'grant_type': 'client_credentials',
    'client_id': _this.options.client_id,
    'client_secret': _this.options.client_secret
  };

  _this.httpAdapter.get(_this._authEndpoint, params, function(err, result) {
    if (err) {
      return callback(err);
    } else {
      result = JSON.parse(result);
      var tokenExpiration = (new Date()).getTime() + result.expires_in;
      var token = result.access_token;
      _this._cachedToken.put(token,tokenExpiration,_this.cache);

      callback(false, token);
    }
  });
};

/**
 * Geocode
 * @param {String}   value    Value to geocode (Address)
 * @param {Function} callback Callback method
 */
AGOLGeocoder.prototype.geocode = function(value, callback) {
  var _this = this;

  if (net.isIP(value)) {
    throw new Error('The AGOL geocoder does not support IP addresses');
  }

  if (value instanceof Array) {
    //As defined in http://resources.arcgis.com/en/help/arcgis-rest-api/#/Batch_geocoding/02r300000003000000/
    throw new Error('An ArcGIS Online organizational account is required to use the batch geocoding functionality');
  }

  var execute = function (value,token,callback) {
    var params = {
      'token':token,
      'f':'json',
      'text':value,
      'outFields': 'AddNum,StPreDir,StName,StType,City,Postal,Region,Country'
    };

    _this.httpAdapter.get(_this._endpoint, params, function(err, result) {
      result = JSON.parse(result);
        if (err) {
          return callback(err);
        } else {
          //This is to work around ESRI's habit of returning 200 OK for failures such as lack of authentication
          if(result.error){
            callback(result.error);

            return null;
          }

          var results = [];
          for(var i = 0; i < result.locations.length; i++) {
            results.push(_this._formatResult(result.locations[i]));
          }

          results.raw = result;
          callback(false, results);
        }
    });
  };

  this._getToken(function(err,token) {
    if (err) {
      return callback(err);
    } else {
      execute(value,token,callback);
    }
  });
};

AGOLGeocoder.prototype._formatResult = function(result) {
  if(result.address){
    return {
      'latitude' : result.location.y,
      'longitude' : result.location.x,
      'country' : result.address.CountryCode,
      'city' : result.address.City,
      'state' : result.address.Region,
      'zipcode' : result.address.Postal,
      'countryCode' : result.address.CountryCode,
      'address': result.address.Address,
      'neighborhood': result.address.Neighborhood,
      'loc_name': result.address.Loc_name
    };
  }

  var country = null;
  var countryCode = null;
  var city = null;
  var state = null;
  var stateCode = null;
  var zipcode = null;
  var streetPreDir = null;
  var streetType = null;
  var streetName = null;
  var streetNumber = null;

  var attributes = result.feature.attributes;
  for (var property in attributes) {
    if (attributes.hasOwnProperty(property)) {
      if(property == 'City') {
        city = attributes[property];
      }
      if(property == 'Postal') {
        zipcode = attributes[property];
      }
      if(property == 'Region') {
        state = attributes[property];
      }
      if(property == 'StPreDir') {
        streetPreDir = attributes[property];
      }
      if(property == 'AddNum') {
        streetNumber = attributes[property];
      }
      if(property == 'StName') {
        streetName = attributes[property];
      }
      if(property == 'StType') {
        streetType = attributes[property];
      }
      if(property == 'Country') {
        countryCode = attributes[property];
        country = attributes[property];
      }
    }
  }

  return {
    'latitude' : result.feature.geometry.y,
    'longitude' : result.feature.geometry.x,
    'country' : country,
    'city' : city,
    'state' : state,
    'stateCode' : stateCode,
    'zipcode' : zipcode,
    'streetName': streetPreDir + ' ' + streetName + ' ' + streetType,
    'streetNumber' : streetNumber,
    'countryCode' : countryCode
  };
};

/**
 * Reverse geocoding
 * @param {lat:<number>,lon:<number>}  lat: Latitude, lon: Longitude
 * @param {function} callback Callback method
 */
AGOLGeocoder.prototype.reverse = function(query, callback) {
  var lat = query.lat;
  var long = query.lon;

  var _this = this;

  var execute = function (lat,long,token,callback) {
    var params = {
      'token':token,
      'f':'json',
      'location' : long + ',' + lat,
      'outFields': 'AddrNum,StPreDir,StName,StType,City,Postal,Region,Country'
    };

    _this.httpAdapter.get(_this._reverseEndpoint, params, function(err, result) {
      result = JSON.parse(result);
      if (err) {
        return callback(err);
      } else {
        //This is to work around ESRI's habit of returning 200 OK for failures such as lack of authentication
        if(result.error){
          callback(result.error,{raw:result});
          return null;
        }

        var results = [];
        results.push(_this._formatResult(result));

        results.raw = result;
        callback(false, results);
      }
    });
  };

  this._getToken(function(err,token) {
    if (err) {
      return callback(err);
    } else {
      execute(lat,long,token,callback);
    }
  });
};

module.exports = AGOLGeocoder;
<commit_msg>fix linting<commit_after>'use strict';

var net = require('net');

/**
 * Constructor
 * @param {Object} httpAdapter Http Adapter
 * @param {Object} options     Options (language, client_id, client_secret)
 */
var AGOLGeocoder = function AGOLGeocoder(httpAdapter, options) {

  if (!httpAdapter || httpAdapter == 'undefined') {
    throw new Error('ArcGis Online Geocoder requires a httpAdapter to be defined');
  }

  if (!options || options == 'undefined') {
    options = {};
  }

  if (!options.client_id || options.client_id == 'undefined') {
    options.client_id = null;
  }

  if (!options.client_secret || options.client_secret == 'undefined') {
    options.client_secret = null;
  }

  if (!options.client_secret || !options.client_id) {

    throw new Error('You must specify the client_id and the client_secret');
  }

  this.options = options;

  this.httpAdapter = httpAdapter;
  this.cache = {};
};

AGOLGeocoder.prototype._authEndpoint = 'https://www.arcgis.com/sharing/oauth2/token';
AGOLGeocoder.prototype._endpoint = 'http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find';
AGOLGeocoder.prototype._reverseEndpoint = 'http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode';

//Cached vars

AGOLGeocoder.prototype._cachedToken = {
  'now': function() {
    return (new Date()).getTime();
  },
  'put': function(token, experation,cache) {
    cache.token = token;
    //Shave 30 secs off experation to ensure that we expire slightly before the actual expiration
    cache.tokenExp = this.now() + (experation - 30);
  },
  'get' : function(cache) {
    if(!cache) {
      return null;
    }

    if(this.now() <= cache.tokenExp) {
      return cache.token;
    } else {
      return null;
    }
  }
};

AGOLGeocoder.prototype._getToken = function(callback) {
  var _this = this;

  if(_this._cachedToken.get(_this.cache) !== null) {
    callback(_this._cachedToken.get());
    return;
    }

  var params = {
    'grant_type': 'client_credentials',
    'client_id': _this.options.client_id,
    'client_secret': _this.options.client_secret
  };

  _this.httpAdapter.get(_this._authEndpoint, params, function(err, result) {
    if (err) {
      return callback(err);
    } else {
      result = JSON.parse(result);
      var tokenExpiration = (new Date()).getTime() + result.expires_in;
      var token = result.access_token;
      _this._cachedToken.put(token,tokenExpiration,_this.cache);

      callback(false, token);
    }
  });
};

/**
 * Geocode
 * @param {String}   value    Value to geocode (Address)
 * @param {Function} callback Callback method
 */
AGOLGeocoder.prototype.geocode = function(value, callback) {
  var _this = this;

  if (net.isIP(value)) {
    throw new Error('The AGOL geocoder does not support IP addresses');
  }

  if (value instanceof Array) {
    //As defined in http://resources.arcgis.com/en/help/arcgis-rest-api/#/Batch_geocoding/02r300000003000000/
    throw new Error('An ArcGIS Online organizational account is required to use the batch geocoding functionality');
  }

  var execute = function (value,token,callback) {
    var params = {
      'token':token,
      'f':'json',
      'text':value,
      'outFields': 'AddNum,StPreDir,StName,StType,City,Postal,Region,Country'
    };

    _this.httpAdapter.get(_this._endpoint, params, function(err, result) {
      result = JSON.parse(result);
        if (err) {
          return callback(err);
        } else {
          //This is to work around ESRI's habit of returning 200 OK for failures such as lack of authentication
          if(result.error){
            callback(result.error);

            return null;
          }

          var results = [];
          for(var i = 0; i < result.locations.length; i++) {
            results.push(_this._formatResult(result.locations[i]));
          }

          results.raw = result;
          callback(false, results);
        }
    });
  };

  this._getToken(function(err,token) {
    if (err) {
      return callback(err);
    } else {
      execute(value,token,callback);
    }
  });
};

AGOLGeocoder.prototype._formatResult = function(result) {
  if(result.address){
    return {
      'latitude' : result.location.y,
      'longitude' : result.location.x,
      'country' : result.address.CountryCode,
      'city' : result.address.City,
      'state' : result.address.Region,
      'zipcode' : result.address.Postal,
      'countryCode' : result.address.CountryCode,
      'address': result.address.Address,
      'neighborhood': result.address.Neighborhood,
      'loc_name': result.address.Loc_name
    };
  }

  var country = null;
  var countryCode = null;
  var city = null;
  var state = null;
  var stateCode = null;
  var zipcode = null;
  var streetPreDir = null;
  var streetType = null;
  var streetName = null;
  var streetNumber = null;

  var attributes = result.feature.attributes;
  for (var property in attributes) {
    if (attributes.hasOwnProperty(property)) {
      if(property == 'City') {
        city = attributes[property];
      }
      if(property == 'Postal') {
        zipcode = attributes[property];
      }
      if(property == 'Region') {
        state = attributes[property];
      }
      if(property == 'StPreDir') {
        streetPreDir = attributes[property];
      }
      if(property == 'AddNum') {
        streetNumber = attributes[property];
      }
      if(property == 'StName') {
        streetName = attributes[property];
      }
      if(property == 'StType') {
        streetType = attributes[property];
      }
      if(property == 'Country') {
        countryCode = attributes[property];
        country = attributes[property];
      }
    }
  }

  return {
    'latitude' : result.feature.geometry.y,
    'longitude' : result.feature.geometry.x,
    'country' : country,
    'city' : city,
    'state' : state,
    'stateCode' : stateCode,
    'zipcode' : zipcode,
    'streetName': streetPreDir + ' ' + streetName + ' ' + streetType,
    'streetNumber' : streetNumber,
    'countryCode' : countryCode
  };
};

/**
 * Reverse geocoding
 * @param {lat:<number>,lon:<number>}  lat: Latitude, lon: Longitude
 * @param {function} callback Callback method
 */
AGOLGeocoder.prototype.reverse = function(query, callback) {
  var lat = query.lat;
  var long = query.lon;

  var _this = this;

  var execute = function (lat,long,token,callback) {
    var params = {
      'token':token,
      'f':'json',
      'location' : long + ',' + lat,
      'outFields': 'AddrNum,StPreDir,StName,StType,City,Postal,Region,Country'
    };

    _this.httpAdapter.get(_this._reverseEndpoint, params, function(err, result) {
      result = JSON.parse(result);
      if (err) {
        return callback(err);
      } else {
        //This is to work around ESRI's habit of returning 200 OK for failures such as lack of authentication
        if(result.error){
          callback(result.error,{raw:result});
          return null;
        }

        var results = [];
        results.push(_this._formatResult(result));

        results.raw = result;
        callback(false, results);
      }
    });
  };

  this._getToken(function(err,token) {
    if (err) {
      return callback(err);
    } else {
      execute(lat,long,token,callback);
    }
  });
};

module.exports = AGOLGeocoder;
<|endoftext|>


<commit_before>var typing_events = (function () {
var exports = {};

// This code handles the inbound side of typing notifications.
// When another user is typing, we process the events here.
//
// We also handle the local event of re-narrowing.
// (For the outbound code, see typing.js.)

// How long before we assume a client has gone away
// and expire its typing status
var TYPING_STARTED_EXPIRY_PERIOD = 15000; // 15s

// Note!: There are also timing constants in typing_status.js
// that make typing indicators work.

function get_users_typing_for_narrow() {
    if (!narrow_state.narrowed_to_pms()) {
        // Narrow is neither pm-with nor is: private
        return [];
    }

    var first_term = narrow_state.operators()[0];
    if (first_term.operator === 'pm-with') {
        // Get list of users typing in this conversation
        var narrow_emails_string = first_term.operand;
        // TODO: Create people.emails_strings_to_user_ids.
        var narrow_user_ids_string = people.emails_strings_to_user_ids_string(narrow_emails_string);
        var narrow_user_ids = narrow_user_ids_string.split(',').map(function (user_id_string) {
            return parseInt(user_id_string, 10);
        });
        var group = narrow_user_ids.concat([page_params.user_id]);
        return typing_data.get_group_typists(group);
    }
    // Get all users typing (in all private conversations with current user)
    return typing_data.get_all_typists();
}

function render_notifications_for_narrow() {
    var user_ids = get_users_typing_for_narrow();
    var users_typing = user_ids.map(people.get_person_from_user_id);
    if (users_typing.length === 0) {
        $('#typing_notifications').hide();
    } else {
        $('#typing_notifications').html(templates.render('typing_notifications', {users: users_typing}));
        $('#typing_notifications').show();
    }
}

exports.hide_notification = function (event) {
    var recipients = event.recipients.map(function (user) {
        return user.user_id;
    });
    recipients.sort();

    typing_data.clear_inbound_timer(recipients);

    var removed = typing_data.remove_typist(recipients, event.sender.user_id);

    if (removed) {
        render_notifications_for_narrow();
    }
};

exports.display_notification = function (event) {
    var recipients = event.recipients.map(function (user) {
        return user.user_id;
    });
    recipients.sort();

    var sender_id = event.sender.user_id;
    event.sender.name = people.get_person_from_user_id(sender_id).full_name;

    typing_data.add_typist(recipients, sender_id);

    render_notifications_for_narrow();

    typing_data.kickstart_inbound_timer(
        recipients,
        TYPING_STARTED_EXPIRY_PERIOD,
        function () {
            exports.hide_notification(event);
        }
    );
};

$(document).on('narrow_activated.zulip', render_notifications_for_narrow);
$(document).on('narrow_deactivated.zulip', render_notifications_for_narrow);

return exports;
}());

if (typeof module !== 'undefined') {
    module.exports = typing_events;
}
<commit_msg>Add error checking to get_users_typing_for_narrow().<commit_after>var typing_events = (function () {
var exports = {};

// This code handles the inbound side of typing notifications.
// When another user is typing, we process the events here.
//
// We also handle the local event of re-narrowing.
// (For the outbound code, see typing.js.)

// How long before we assume a client has gone away
// and expire its typing status
var TYPING_STARTED_EXPIRY_PERIOD = 15000; // 15s

// Note!: There are also timing constants in typing_status.js
// that make typing indicators work.

function get_users_typing_for_narrow() {
    if (!narrow_state.narrowed_to_pms()) {
        // Narrow is neither pm-with nor is: private
        return [];
    }

    var first_term = narrow_state.operators()[0];
    if (first_term.operator === 'pm-with') {
        // Get list of users typing in this conversation
        var narrow_emails_string = first_term.operand;
        // TODO: Create people.emails_strings_to_user_ids.
        var narrow_user_ids_string = people.emails_strings_to_user_ids_string(narrow_emails_string);
        if (!narrow_user_ids_string) {
            blueslip.warn('Bad narrow for typing indicators: ' + narrow_emails_string);
            return [];
        }
        var narrow_user_ids = narrow_user_ids_string.split(',').map(function (user_id_string) {
            return parseInt(user_id_string, 10);
        });
        var group = narrow_user_ids.concat([page_params.user_id]);
        return typing_data.get_group_typists(group);
    }
    // Get all users typing (in all private conversations with current user)
    return typing_data.get_all_typists();
}

function render_notifications_for_narrow() {
    var user_ids = get_users_typing_for_narrow();
    var users_typing = user_ids.map(people.get_person_from_user_id);
    if (users_typing.length === 0) {
        $('#typing_notifications').hide();
    } else {
        $('#typing_notifications').html(templates.render('typing_notifications', {users: users_typing}));
        $('#typing_notifications').show();
    }
}

exports.hide_notification = function (event) {
    var recipients = event.recipients.map(function (user) {
        return user.user_id;
    });
    recipients.sort();

    typing_data.clear_inbound_timer(recipients);

    var removed = typing_data.remove_typist(recipients, event.sender.user_id);

    if (removed) {
        render_notifications_for_narrow();
    }
};

exports.display_notification = function (event) {
    var recipients = event.recipients.map(function (user) {
        return user.user_id;
    });
    recipients.sort();

    var sender_id = event.sender.user_id;
    event.sender.name = people.get_person_from_user_id(sender_id).full_name;

    typing_data.add_typist(recipients, sender_id);

    render_notifications_for_narrow();

    typing_data.kickstart_inbound_timer(
        recipients,
        TYPING_STARTED_EXPIRY_PERIOD,
        function () {
            exports.hide_notification(event);
        }
    );
};

$(document).on('narrow_activated.zulip', render_notifications_for_narrow);
$(document).on('narrow_deactivated.zulip', render_notifications_for_narrow);

return exports;
}());

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