<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 });
            });

            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
        });
    }
});<commit_msg>Initialize mounts after sections have loaded<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 existingMounts = [];

        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;

                    existingMounts.push(node);
                }
            });

            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;
            };

            initializeExistingMounts(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 initializeExistingMounts(section) {
            existingMounts = existingMounts.filter(function (node) {
                if (node.mountType != section.name) return true;

                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
            });
        }

        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>'use strict';

const os = require('os');
const dialog = require('electron').remote.dialog;
const exec = require('child_process').exec;
const path = require('path');

const videoExtensions = [
  "3g2",
  "3gp",
  "aaf",
  "asf",
  "avchd",
  "avi",
  "drc",
  "flv",
  "m2v",
  "m4p",
  "m4v",
  "mkv",
  "mng",
  "mov",
  "mp2",
  "mp4",
  "mpe",
  "mpeg",
  "mpg",
  "mpv",
  "mxf",
  "nsv",
  "ogg",
  "ogv",
  "qt",
  "rm",
  "rmvb",
  "roq",
  "svi",
  "vob",
  "webm",
  "wmv",
  "yuv"
]

const videoClip = {
  video: '',
  initWidth: 0,
  initHeight: 0,
  width: 0,
  height: 0,
  start: '0',
  initStart: '0',
  end: '0',
  initEnd: '0',
  Duration: function() {
    return this.ParseTimeToSeconds(this.end) - this.ParseTimeToSeconds(this.start);
  },
  mirror: false,
  scale: '',
  fps: 0.0,
  initFps: 0.0,
  speed: 1.0,
  ratio: 0.0, // initWidth / initHeight.
  initRatio: 0.0,
  platform: os.platform(),
  ffmpeg: os.platform() === 'win32' ? '.\\bin\\ffmpeg.exe' : './bin/ffmpeg',
  lastGIFPath: '.',
  lastVideoPath: '.',
  LoadVideo: function (path) {
    this.video = path;
    if (this.platform === 'win32') {this.video = '"' + this.video + '"';}
    this.GetVideoInfo();
  },
  GetVideoInfo: function(){
    // Get detailed video info by calling ffmpeg.
    // STDERR example:
    // "  Duration: 03:09:49.62, start: 0.000000, bitrate: 1328 kb/s"
    // "    Stream #0:1(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 856x480 [SAR 1:1 DAR 107:60], 1199 kb/s, 29.97 fps, 29.97 tbr, 60k tbn, 59.94 tbc (default)"
    const _this = this;

    exec(this.ffmpeg + ' -i ' + this.video, function callback(error, stdout, stderr){
      const output = stderr.split('\n');
      let _width, _height, _fps, _start, _duration;

      for (let i=output.length - 1; i>=0; --i) {
        // Find Video Stream description line.
        if (output[i].indexOf('Stream') >= 0 &&
          output[i].indexOf('Video:') >=0 &&
          output[i+1].indexOf('Metadata:') >= 0) {
          const streamDetails = output[i].split(',');
          const sizeList = streamDetails[streamDetails.length - 6].split(' ');
          const fpsList = streamDetails[streamDetails.length - 4].split(' ');
          _width = sizeList[1].split('x')[0];
          _height = sizeList[1].split('x')[1];
          _fps = fpsList[1];
        }
        // Find time details line.
        if (output[i].indexOf('Duration:') >= 0 &&
          output[i].indexOf('start:') >= 0) {
          const timeDetails = output[i].split(',');
          const durationList = timeDetails[0].split(' ');
          const startList = timeDetails[1].split(' ');
          _start = startList[startList.length - 1];
          _duration = durationList[durationList.length - 1];
        }
      }
      _this.width = _width;
      _this.height = _height;
      _this.initWidth = _width;
      _this.initHeight = _height;
      _this.ratio = _this.initWidth / _this.initHeight;
      _this.initRatio = _this.initWidth / _this.initHeight;
      _this.start = _start;
      _this.initStart = _start;
      _this.end = _duration;
      _this.initEnd = _duration;
      _this.fps = _fps;
      _this.initFps = _fps;
    })
  },
  SetWidth: function(w) {
    // Set width to w and keep aspect ratio unchanged.
    this.width = w;
    this.height = this.width / this.ratio;
    return this;
  },
  SetHeight: function(h) {
    // Set Height to h and keep aspect ratio unchanged.
    this.height = h;
    this.weight = this.height * this.ratio;
    return this;
  },
  SetScale: function(s) {
    this.scale = s;
    this.width = this.initWidth * this.scale;
    this.height = this.initHeight * this.scale;
    return this;
  },
  UpdateWithScale: function(){
    if (this.scale) {
      this.width = this.initWidth * this.scale;
      this.height = this.initHeight * this.scale;
    }
    return this;
  },
  Reset: function(){
    this.height = this.initHeight;
    this.width = this.initWidth;
    this.start = this.initStart;
    this.end = this.initEnd;
    this.ratio = this.initRatio;
    this.fps = this.initFps;
    this.mirror = false;
    this.scale = '';
    this.speed = 1;
  },
  OpenVideoDialog: function(callback) {
    const _this = this;
    dialog.showOpenDialog({
      title: 'Select Video',
      defaultPath: _this.lastVideoPath,
      filters: [
      {name: 'Videos', extensions: videoExtensions},
      {name: 'All Files', extensions: ['*']}
      ]
    }, function(videoName){
      if (!videoName) return;
      _this.lastVideoPath = path.dirname(videoName);
      _this.LoadVideo(videoName);
      if (typeof callback === "function") {
        callback(videoName);
      }
    });
  },
  MakeGIF: function(callback) {
    // ffmpeg
    // -ss [start] -t [duration]
    // -i [input video]
    // -vf fps=[fps],scale=[width]:[height]:flags=lanczos,setpts=1/[speed]*PTS
    // output.gif
    const _this = this;
    dialog.showSaveDialog({
      title: 'hehe',
      defaultPath: _this.lastGIFPath,
      filters: [
      {name: 'GIFs', extensions: ['gif']},
      {name: 'All Files', extensions: ['*']}
      ],
    }, function(gifname) {
      if (!gifname) {return;}
      _this.lastGIFPath = path.dirname(gifname);
      // gifname may contain spaces. Need to wrap quotes around it.
      let gifnameCMD;
      if (_this.platform == 'win32') {gifnameCMD = '"' + gifname + '"';}
      const options = ' -y -ss ' + _this.start + ' -t ' + _this.Duration()
        + ' -i ' + _this.video + ' -vf ' + 'fps=' + _this.fps + ',scale='
        + _this.width + ':' + _this.height + ':flags=lanczos,'
        + 'setpts=1/' + _this.speed + '*PTS ' + gifnameCMD;
      console.log(options);
      exec(_this.ffmpeg + options, function (error, stdout, stderr) {
        console.log('ERROR: ', error);
        console.log('STDOUT: ', stdout);
        console.log('STDERR: ', stderr);
        console.log('GIF convert finished!');
        if (typeof callback === "function") {
          callback(gifname);
        }
      });
    });
  },
  ParseTimeToSeconds: function(t){
    const ts = t.toString();
    const tsList = ts.split(':');
    let seconds = 0;
    for (let i=tsList.length-1; i>=0; --i) {
      seconds += tsList[i] * Math.pow(60, tsList.length - 1 - i);
    }
    return seconds;
  },
  ParseSecondsToTime: function(t){
    let seconds = 0;
    let minutes = 0;
    let hours = 0;
    seconds = (t % 60).toFixed(3);
    minutes = Math.floor(t / 60) % 60;
    hours = Math.floor(t / 60 / 60) % 60;
    if (hours) {
      return hours + ':' + minutes + ':' + seconds;
    } else if (minutes) {
      return minutes + ':' + seconds;
    } else {
      return seconds;
    }
  },
};

module.exports = videoClip;
<commit_msg>Make gifnameCMD work in non-win platforms<commit_after>'use strict';

const os = require('os');
const dialog = require('electron').remote.dialog;
const exec = require('child_process').exec;
const path = require('path');

const videoExtensions = [
  "3g2",
  "3gp",
  "aaf",
  "asf",
  "avchd",
  "avi",
  "drc",
  "flv",
  "m2v",
  "m4p",
  "m4v",
  "mkv",
  "mng",
  "mov",
  "mp2",
  "mp4",
  "mpe",
  "mpeg",
  "mpg",
  "mpv",
  "mxf",
  "nsv",
  "ogg",
  "ogv",
  "qt",
  "rm",
  "rmvb",
  "roq",
  "svi",
  "vob",
  "webm",
  "wmv",
  "yuv"
]

const videoClip = {
  video: '',
  initWidth: 0,
  initHeight: 0,
  width: 0,
  height: 0,
  start: '0',
  initStart: '0',
  end: '0',
  initEnd: '0',
  Duration: function() {
    return this.ParseTimeToSeconds(this.end) - this.ParseTimeToSeconds(this.start);
  },
  mirror: false,
  scale: '',
  fps: 0.0,
  initFps: 0.0,
  speed: 1.0,
  ratio: 0.0, // initWidth / initHeight.
  initRatio: 0.0,
  platform: os.platform(),
  ffmpeg: os.platform() === 'win32' ? '.\\bin\\ffmpeg.exe' : './bin/ffmpeg',
  lastGIFPath: '.',
  lastVideoPath: '.',
  LoadVideo: function (path) {
    this.video = path;
    if (this.platform === 'win32') {this.video = '"' + this.video + '"';}
    this.GetVideoInfo();
  },
  GetVideoInfo: function(){
    // Get detailed video info by calling ffmpeg.
    // STDERR example:
    // "  Duration: 03:09:49.62, start: 0.000000, bitrate: 1328 kb/s"
    // "    Stream #0:1(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 856x480 [SAR 1:1 DAR 107:60], 1199 kb/s, 29.97 fps, 29.97 tbr, 60k tbn, 59.94 tbc (default)"
    const _this = this;

    exec(this.ffmpeg + ' -i ' + this.video, function callback(error, stdout, stderr){
      const output = stderr.split('\n');
      let _width, _height, _fps, _start, _duration;

      for (let i=output.length - 1; i>=0; --i) {
        // Find Video Stream description line.
        if (output[i].indexOf('Stream') >= 0 &&
          output[i].indexOf('Video:') >=0 &&
          output[i+1].indexOf('Metadata:') >= 0) {
          const streamDetails = output[i].split(',');
          const sizeList = streamDetails[streamDetails.length - 6].split(' ');
          const fpsList = streamDetails[streamDetails.length - 4].split(' ');
          _width = sizeList[1].split('x')[0];
          _height = sizeList[1].split('x')[1];
          _fps = fpsList[1];
        }
        // Find time details line.
        if (output[i].indexOf('Duration:') >= 0 &&
          output[i].indexOf('start:') >= 0) {
          const timeDetails = output[i].split(',');
          const durationList = timeDetails[0].split(' ');
          const startList = timeDetails[1].split(' ');
          _start = startList[startList.length - 1];
          _duration = durationList[durationList.length - 1];
        }
      }
      _this.width = _width;
      _this.height = _height;
      _this.initWidth = _width;
      _this.initHeight = _height;
      _this.ratio = _this.initWidth / _this.initHeight;
      _this.initRatio = _this.initWidth / _this.initHeight;
      _this.start = _start;
      _this.initStart = _start;
      _this.end = _duration;
      _this.initEnd = _duration;
      _this.fps = _fps;
      _this.initFps = _fps;
    })
  },
  SetWidth: function(w) {
    // Set width to w and keep aspect ratio unchanged.
    this.width = w;
    this.height = this.width / this.ratio;
    return this;
  },
  SetHeight: function(h) {
    // Set Height to h and keep aspect ratio unchanged.
    this.height = h;
    this.weight = this.height * this.ratio;
    return this;
  },
  SetScale: function(s) {
    this.scale = s;
    this.width = this.initWidth * this.scale;
    this.height = this.initHeight * this.scale;
    return this;
  },
  UpdateWithScale: function(){
    if (this.scale) {
      this.width = this.initWidth * this.scale;
      this.height = this.initHeight * this.scale;
    }
    return this;
  },
  Reset: function(){
    this.height = this.initHeight;
    this.width = this.initWidth;
    this.start = this.initStart;
    this.end = this.initEnd;
    this.ratio = this.initRatio;
    this.fps = this.initFps;
    this.mirror = false;
    this.scale = '';
    this.speed = 1;
  },
  OpenVideoDialog: function(callback) {
    const _this = this;
    dialog.showOpenDialog({
      title: 'Select Video',
      defaultPath: _this.lastVideoPath,
      filters: [
      {name: 'Videos', extensions: videoExtensions},
      {name: 'All Files', extensions: ['*']}
      ]
    }, function(videoName){
      if (!videoName) return;
      _this.lastVideoPath = path.dirname(videoName);
      _this.LoadVideo(videoName);
      if (typeof callback === "function") {
        callback(videoName);
      }
    });
  },
  MakeGIF: function(callback) {
    // ffmpeg
    // -ss [start] -t [duration]
    // -i [input video]
    // -vf fps=[fps],scale=[width]:[height]:flags=lanczos,setpts=1/[speed]*PTS
    // output.gif
    const _this = this;
    dialog.showSaveDialog({
      title: 'hehe',
      defaultPath: _this.lastGIFPath,
      filters: [
      {name: 'GIFs', extensions: ['gif']},
      {name: 'All Files', extensions: ['*']}
      ],
    }, function(gifname) {
      if (!gifname) {return;}
      _this.lastGIFPath = path.dirname(gifname);
      // gifname may contain spaces. Need to wrap quotes around it.
      let gifnameCMD = gifname;
      if (_this.platform == 'win32') {gifnameCMD = '"' + gifname + '"';}
      const options = ' -y -ss ' + _this.start + ' -t ' + _this.Duration()
        + ' -i ' + _this.video + ' -vf ' + 'fps=' + _this.fps + ',scale='
        + _this.width + ':' + _this.height + ':flags=lanczos,'
        + 'setpts=1/' + _this.speed + '*PTS ' + gifnameCMD;
      console.log(options);
      exec(_this.ffmpeg + options, function (error, stdout, stderr) {
        console.log('ERROR: ', error);
        console.log('STDOUT: ', stdout);
        console.log('STDERR: ', stderr);
        console.log('GIF convert finished!');
        if (typeof callback === "function") {
          callback(gifname);
        }
      });
    });
  },
  ParseTimeToSeconds: function(t){
    const ts = t.toString();
    const tsList = ts.split(':');
    let seconds = 0;
    for (let i=tsList.length-1; i>=0; --i) {
      seconds += tsList[i] * Math.pow(60, tsList.length - 1 - i);
    }
    return seconds;
  },
  ParseSecondsToTime: function(t){
    let seconds = 0;
    let minutes = 0;
    let hours = 0;
    seconds = (t % 60).toFixed(3);
    minutes = Math.floor(t / 60) % 60;
    hours = Math.floor(t / 60 / 60) % 60;
    if (hours) {
      return hours + ':' + minutes + ':' + seconds;
    } else if (minutes) {
      return minutes + ':' + seconds;
    } else {
      return seconds;
    }
  },
};

module.exports = videoClip;
<|endoftext|>


<commit_before>var mongoose = require("mongoose");
var q = require("q");
var _ = require("underscore");

// Enable mpath structures on any `schema`.
var enable = module.exports.enable = function(schema) {
    schema.plugin(function() {
        schema.add({
            mpath:{
                type:String,
                index:true
            },
            parentId:{
                type:String,
                index:true
            }
        });
    });
}

// Attach `child` to `parent`.  Passing true in `isLeaf` improves performance for leaf nodes because no check needs
// to be done to rewrite the paths of child nodes.  When a `parent` is not passed, it implies the `child` is to be made
// a root.
var attach = module.exports.attach = function(child, parent, isLeaf) {
    var childPath;
    // Compute the mpath for the `child` node.  Is there is no `parent`, the mpath is empty, if the `parent` is a root, then
    // the mpath is equal to the `parent` id, otherwise it is the `parent` mpath plus it's id
    if (!parent) {
        childPath = "";
    }
    else if (!parent.mpath) {
        childPath = "/"+parent.id;
    }
    else {
        childPath = parent.mpath+"/"+parent.id;
    }
    // If the `child` is a leaf, just set the mpath, save it, and complete the promise
    if (isLeaf) {
        child.mpath = childPath;
        child.parentId = parent?parent.id:undefined;
        return q.ninvoke(child, "save").then(function() {
            return child;
        });

    }
    else {
        // If the `child` is not a leaf, then find all it's descendants and recompute their mpaths, but still return
        // the `child` once all the save promises are resolved.
        return getDescendants(child)
            .then(function(descendants) {
                var promises = descendants.map(function(descendant) {
                    descendant.mpath = descendant.mpath.replace(child.mpath, childPath);
                    return q.ninvoke(descendant, "save");
                });
                child.mpath = childPath;
                child.parentId = parent?parent.id:undefined;
                promises.push(q.ninvoke(child, "save"));
                return q.all(promises);
            })
            .then(function() {
                return child;
            });

    }
};

// Detach a `child` from it's parent.  This is done by simply "attaching" it to a undefined parent
var detach = module.exports.detach = function(child) {
    return attach(child);
};

// Make a copy of `root`, assigning the copy to the same parent.  Note that `root` does not actually need to be a root
// node, it is just the root of the tree of nodes that should be copied.
var copy = module.exports.copy = function(root, options) {
    var objects = [];
    // Convert the `node` to a deep POJO representing this tree.  This function is called recursively to process the
    // entire tree rooted at `node`
    function toDeepObject(node) {
        var copy = new node.constructor(node.toObject());
        copy._id = new mongoose.Types.ObjectId();
        var object = copy.toObject();
        object.children = {};
        for (var id in node.children) {
            var child = node.children[id];
            if (options.filterChild(child)) {
                var childObject = toDeepObject(child);
                childObject.parent = object;
                object.children[id] = childObject;
            }
        }
        return object;
    }
    // Reset the parent id and mpaths of `object` and all it's child objects.  This method is intended to be called with
    // the output of toDeepObject
    function reId(object) {
        if (object.parent) {
            object.parentId = object.parent._id.toString();
            if (object.parent.mpath) {
                object.mpath = object.parent.mpath+"/"+object.parent._id.toString();
            }
            else {
                object.mpath = "/"+object.parent._id.toString();
            }
        }
        objects.push(object);
        for (var id in object.children) {
            reId(object.children[id]);
        }
    }
    // Start the promise chaining by getting the descendant tree of `root`
    return buildDescendantTree(root)
        .then(function(root) {
            // Convert `root` to a deep object and reset the parent id and mpath values
            reId(toDeepObject(root));
            // Delete the child and parent properties of the deep `object`.  Once parent id and mpath properties have
            // been computed, there is no further need to maintain the references, in fact they need to be removed
            // before bulk insert.
            objects.forEach(function(object) {
                delete object.children;
                delete object.parent;
            })
            // Bulk insert the `objects` using mongo's direct driver call.  This is efficient, but will not run any
            // middleware
            return q
                .ninvoke(root.constructor.collection, "insert", objects)
                .then(function(copies) {
                    return mongoose
                        .model(root.constructor.modelName)
                        .findOne(
                            {
                                _id:copies[0]._id.toString()
                            },
                            "")
                        .exec();
                })
                .then(function(rootCopy) {
                    return buildDescendantTree(rootCopy);
                })
        })
}

// Find all descendants of `root`.  This does not return a tree structure, just the nodes that would comprise such a tree
var getDescendants = module.exports.getDescendants = function(root) {
    return root.constructor
        .find({
            mpath:new RegExp("^"+(root.mpath?root.mpath:"")+"\/"+root.id)
        })
        .exec();
}

// Delete all descendants of `root`
var removeDescendants = module.exports.removeDescendants = function(root) {
    return root.constructor
        .find({
            mpath:new RegExp("^"+(root.mpath?root.mpath:"")+"\/"+root.id)
        })
        .remove()
        .exec();
}

// Get all children of `root`
var getChildren = module.exports.getChildren = function(root) {
    return root.constructor
        .find({
            parentId:root.id
        })
        .exec();
}

// Given an array of `nodes`, construct the set of trees containing all the `nodes` such that the number of roots is
// minimized.  In the common case, this is used to convert the output of `getDescendants()` into a tree structure
var buildTrees = module.exports.buildTree = function(nodes) {
    var nodeMap = {};
    nodes.forEach(function(node) {
        nodeMap[node.id] = node;
        node.children = {};
    });
    nodes.forEach(function(node) {
        var parent = nodeMap[node.parentId];
        if (parent) {
            parent.children[node.id] = node;
            node.parent = parent;
        }
    });
    nodes.forEach(function(node) {
        if (node.parent) {
            delete nodeMap[node.id];
        }
    });
    return _.values(nodeMap);
}

// Get all descendants of `root` structured as a tree
var buildDescendantTree = module.exports.buildDescendantTree = function(root) {
    return getDescendants(root)
        .then(function(documents) {
            documents.push(root);
            buildTrees(documents);
            return root;
        })
}

// Get all children of `root` structured as a tree
var buildChildrenTree = module.exports.buildChildrenTree = function(root) {
    return getChildren(root)
        .then(function(documents) {
            documents.push(root);
            buildTrees(documents);
            return root;
        })
}

// Get all ancestors of all of the `documents` structured as a set of trees.  Note that the number of returned trees may
// be less than the number of passed `documents` because different child nodes may have common ancestors.  In a single root
// hierarchy (where all paths lead to the same root), this method will always return one tree.  If any of the `documents`
// have children, or descendants, integrate these into the overall ancestor tree, but do not include any children of
// any of the ancestors of the `documents`
var buildAncestorTree = module.exports.buildAncestorTree = function(documents) {
    var idSet = {};
    var descendants = [];
    if (!documents) {
        return [];
    }
    documents = [].concat(documents);
    if (documents.length==0) {
        return [];
    }
    function getDescendants(document) {
        if (document.children) {
            _.values(document.children).forEach(function(child) {
                descendants.push(child);
                getDescendants(child);
            })
        }
    }
    documents.forEach(function(document) {
        getDescendants(document);
        if (document.mpath) {
            document.mpath
                .split("/")
                .forEach(function(id) {
                    idSet[id] = true;
                });
        }
    })
    delete idSet[""];

    return documents[0].constructor
        .find({
            _id:{
                $in:_.keys(idSet)
            }
        })
        .exec()
        .then(function(ancestors) {
            documents = documents.concat(descendants);
            documents = documents.concat(ancestors);
            return buildTrees(documents);
        })
}
<commit_msg>Add options for refining child and descendant queries<commit_after>var mongoose = require("mongoose");
var q = require("q");
var _ = require("underscore");

// Enable mpath structures on any `schema`.
var enable = module.exports.enable = function(schema) {
    schema.plugin(function() {
        schema.add({
            mpath:{
                type:String,
                index:true
            },
            parentId:{
                type:String,
                index:true
            }
        });
    });
}

// Attach `child` to `parent`.  Passing true in `isLeaf` improves performance for leaf nodes because no check needs
// to be done to rewrite the paths of child nodes.  When a `parent` is not passed, it implies the `child` is to be made
// a root.
var attach = module.exports.attach = function(child, parent, isLeaf) {
    var childPath;
    // Compute the mpath for the `child` node.  Is there is no `parent`, the mpath is empty, if the `parent` is a root, then
    // the mpath is equal to the `parent` id, otherwise it is the `parent` mpath plus it's id
    if (!parent) {
        childPath = "";
    }
    else if (!parent.mpath) {
        childPath = "/"+parent.id;
    }
    else {
        childPath = parent.mpath+"/"+parent.id;
    }
    // If the `child` is a leaf, just set the mpath, save it, and complete the promise
    if (isLeaf) {
        child.mpath = childPath;
        child.parentId = parent?parent.id:undefined;
        return q.ninvoke(child, "save").then(function() {
            return child;
        });

    }
    else {
        // If the `child` is not a leaf, then find all it's descendants and recompute their mpaths, but still return
        // the `child` once all the save promises are resolved.
        return getDescendants(child)
            .then(function(descendants) {
                var promises = descendants.map(function(descendant) {
                    descendant.mpath = descendant.mpath.replace(child.mpath, childPath);
                    return q.ninvoke(descendant, "save");
                });
                child.mpath = childPath;
                child.parentId = parent?parent.id:undefined;
                promises.push(q.ninvoke(child, "save"));
                return q.all(promises);
            })
            .then(function() {
                return child;
            });

    }
};

// Detach a `child` from it's parent.  This is done by simply "attaching" it to a undefined parent
var detach = module.exports.detach = function(child) {
    return attach(child);
};

// Make a copy of `root`, assigning the copy to the same parent.  Note that `root` does not actually need to be a root
// node, it is just the root of the tree of nodes that should be copied.
var copy = module.exports.copy = function(root, options) {
    var objects = [];
    // Convert the `node` to a deep POJO representing this tree.  This function is called recursively to process the
    // entire tree rooted at `node`
    function toDeepObject(node) {
        var copy = new node.constructor(node.toObject());
        copy._id = new mongoose.Types.ObjectId();
        var object = copy.toObject();
        object.children = {};
        for (var id in node.children) {
            var child = node.children[id];
            if (options.filterChild(child)) {
                var childObject = toDeepObject(child);
                childObject.parent = object;
                object.children[id] = childObject;
            }
        }
        return object;
    }
    // Reset the parent id and mpaths of `object` and all it's child objects.  This method is intended to be called with
    // the output of toDeepObject
    function reId(object) {
        if (object.parent) {
            object.parentId = object.parent._id.toString();
            if (object.parent.mpath) {
                object.mpath = object.parent.mpath+"/"+object.parent._id.toString();
            }
            else {
                object.mpath = "/"+object.parent._id.toString();
            }
        }
        objects.push(object);
        for (var id in object.children) {
            reId(object.children[id]);
        }
    }
    // Start the promise chaining by getting the descendant tree of `root`
    return buildDescendantTree(root)
        .then(function(root) {
            // Convert `root` to a deep object and reset the parent id and mpath values
            reId(toDeepObject(root));
            // Delete the child and parent properties of the deep `object`.  Once parent id and mpath properties have
            // been computed, there is no further need to maintain the references, in fact they need to be removed
            // before bulk insert.
            objects.forEach(function(object) {
                delete object.children;
                delete object.parent;
            })
            // Bulk insert the `objects` using mongo's direct driver call.  This is efficient, but will not run any
            // middleware
            return q
                .ninvoke(root.constructor.collection, "insert", objects)
                .then(function(copies) {
                    return mongoose
                        .model(root.constructor.modelName)
                        .findOne(
                            {
                                _id:copies[0]._id.toString()
                            },
                            "")
                        .exec();
                })
                .then(function(rootCopy) {
                    return buildDescendantTree(rootCopy);
                })
        })
}

// Find all descendants of `root`.  This does not return a tree structure, just the nodes that would comprise such a tree
var getDescendants = module.exports.getDescendants = function(root, options) {
    options = options?options:{};
    var query = options.query?options.query:{};
    query.mpath = new RegExp("^"+(root.mpath?root.mpath:"")+"\/"+root.id);
    return root.constructor
        .find(query)
        .exec();
}

// Delete all descendants of `root`
var removeDescendants = module.exports.removeDescendants = function(root, options) {
    options = options?options:{};
    var query = options.query?options.query:{};
    query.mpath = new RegExp("^"+(root.mpath?root.mpath:"")+"\/"+root.id);
    return root.constructor
        .find(query)
        .remove()
        .exec();
}

// Get all children of `root`
var getChildren = module.exports.getChildren = function(root, options) {
    options = options?options:{};
    var query = options.query?options.query:{};
    query.parentId = root.id;
    return root.constructor
        .find(query)
        .exec();
}

// Given an array of `nodes`, construct the set of trees containing all the `nodes` such that the number of roots is
// minimized.  In the common case, this is used to convert the output of `getDescendants()` into a tree structure
var buildTrees = module.exports.buildTree = function(nodes) {
    var nodeMap = {};
    nodes.forEach(function(node) {
        nodeMap[node.id] = node;
        node.children = {};
    });
    nodes.forEach(function(node) {
        var parent = nodeMap[node.parentId];
        if (parent) {
            parent.children[node.id] = node;
            node.parent = parent;
        }
    });
    nodes.forEach(function(node) {
        if (node.parent) {
            delete nodeMap[node.id];
        }
    });
    return _.values(nodeMap);
}

// Get all descendants of `root` structured as a tree
var buildDescendantTree = module.exports.buildDescendantTree = function(root) {
    return getDescendants(root)
        .then(function(documents) {
            documents.push(root);
            buildTrees(documents);
            return root;
        })
}

// Get all children of `root` structured as a tree
var buildChildrenTree = module.exports.buildChildrenTree = function(root) {
    return getChildren(root)
        .then(function(documents) {
            documents.push(root);
            buildTrees(documents);
            return root;
        })
}

// Get all ancestors of all of the `documents` structured as a set of trees.  Note that the number of returned trees may
// be less than the number of passed `documents` because different child nodes may have common ancestors.  In a single root
// hierarchy (where all paths lead to the same root), this method will always return one tree.  If any of the `documents`
// have children, or descendants, integrate these into the overall ancestor tree, but do not include any children of
// any of the ancestors of the `documents`
var buildAncestorTree = module.exports.buildAncestorTree = function(documents) {
    var idSet = {};
    var descendants = [];
    if (!documents) {
        return [];
    }
    documents = [].concat(documents);
    if (documents.length==0) {
        return [];
    }
    function getDescendants(document) {
        if (document.children) {
            _.values(document.children).forEach(function(child) {
                descendants.push(child);
                getDescendants(child);
            })
        }
    }
    documents.forEach(function(document) {
        getDescendants(document);
        if (document.mpath) {
            document.mpath
                .split("/")
                .forEach(function(id) {
                    idSet[id] = true;
                });
        }
    })
    delete idSet[""];

    return documents[0].constructor
        .find({
            _id:{
                $in:_.keys(idSet)
            }
        })
        .exec()
        .then(function(ancestors) {
            documents = documents.concat(descendants);
            documents = documents.concat(ancestors);
            return buildTrees(documents);
        })
}
<|endoftext|>


<commit_before>//Kolla om inlogg redan existrerar
function kollainlogg(){
show();
'use strict';
	if(localStorage.LoggedIn == 'true'){
		//dehamta(localStorage.anv,localStorage.pass);
		//loadtvatta('https://www.sgsstudentbostader.se/ext_gw.aspx?module=wwwash&lang=se#lblPanelName');
		byggnad('http://www.sgsstudentbostader.se/ext_gw.aspx?module=wwwash&lang=se#lblPanelName');
		console.log('Det verkar finnas en användare');
		console.log("Detta är användaren" + localStorage.ReturnCode);
	}
	else{
		inputfields();
		loadtvattaklar();
		if(localStorage.ReturnCode == 'TOOMANYFAILEDLOGINS'){
			console.log('Du har loggat in för många gånger, var god vänta en stund och försök igen');
			tomany();
			loadtvattaklar();
		}
		console.log('Detta är användaren i else ' + localStorage.LoggedIn);
		}
	}

var historik = new Array();
//Hämtar inloggningsformet
function getFormData(){
show();
localStorage.anv=document.getElementById("username").value;
localStorage.pass=document.getElementById("password").value;
console.log("Den skickar inloggningen");
//Kyrptering av känslig data
encrypt(localStorage.anv,localStorage.pass);
dehamta(localStorage.anv,localStorage.pass);

}

//hämta marknad
function hamta(anv,pass) {
	$.getJSON("http://marknad.sgsstudentbostader.se/API/Service/AuthorizationServiceHandler.ashx?&syndicateNo=1&syndicateObjectMainGroupNo=1&username=" +anv.toString(CryptoJS.enc.Utf8)+ "&password=" +pass.toString(CryptoJS.enc.Utf8)+ "&Method=APILoginSGS&callback=?",

	  function(result) {
		//det går utmärkt att göra denna hämtning via en variabel, localStorage går alltså att utesluta.
		var infoinlogg = new Object();
		console.log(result);
		$.each(result, function (newsItem,news)
		{
		infoinlogg[newsItem]=news;
		localStorage[newsItem]=news
			console.log(newsItem + " = " + localStorage[newsItem])
			console.log(infoinlogg[newsItem]);
		});

		if(localStorage.ReturnCode == 'NOMATCH'){
			inloggningfel();
		}
		else{

	  loggain()
	  }
	  });

};

//logga in
function loggain(){
var str1="_=1366895108402&customer_name=" + localStorage.SGS_CustomerName + "&customerid=" + localStorage.UserName + "&isresident=" + localStorage.SGS_LivesAtSgs + "&loggedin=" + localStorage.LoggedIn + "&token=" + localStorage.SecurityTokenId + "&tvattstuga=" + localStorage.SGS_Laundry;
localStorage.urlen="http://www.sgsstudentbostader.se/Assets/Handlers/Momentum.ashx?" + str1;
hiddenbrowser();
}

//Hidden InAppBrowser
function hiddenbrowser(){
	console.log("Går in i funktionen hiddenbrowser");
	 var ref = window.open(localStorage.urlen, '_blank', 'hidden=yes');
	 console.log(ref);
	 	ref.addEventListener('loadstart', function(event) {
	 		console.log('Inloggning hidden loadstart');
		 	show();
	 	})
		ref.addEventListener('loadstop', function(event) {
			 byggnad('http://www.sgsstudentbostader.se/ext_gw.aspx?module=wwwash&lang=se#lblPanelName');
			 console.log("Inloggning hidden loadstop");

})

}

function loggaut(button){
if(button == 2){
	var loggautmomentum = window.open('http://www.sgsstudentbostader.se/Assets/Handlers/MomentumLogout.ashx?_=1390854127881', '_blank', 'hidden=yes');
	var loggautmarknad = window.open('http://marknad.sgsstudentbostader.se/API/Service/AuthorizationServiceHandler.ashx?&Method=APILogout&callback=jsonp1390854117395', '_blank', 'hidden=yes');
	console.log("Loggar ut momentum " + loggautmomentum);
	console.log("Loggar ut marknad " + loggautmarknad);

	loggautmomentum.addEventListener('loadstop', function(momentumevent){
		console.log('Den har laddat klart logga ut momentum');
		console.log(momentumevent);
	})

	loggautmarknad.addEventListener('loadstop', function(marknadevent){
		console.log('Den har laddat klart logga ut marknad');
		console.log(marknadevent);
	})
	console.log('Sätter anv och pass till null');
	localStorage.LoggedIn = 'false';
	localStorage.removeItem(anv);
	localStorage.removeItem(pass);
	console.log(localStorage.LoggedIn);
	//var mobilsystem = device.platform;
	//console.log(mobilsystem);

	//if(mobilsystem != 'iOS'){
	//	navigator.app.exitApp();
	//}
	//alert('Du är nu utloggad!');

	}
}

<commit_msg>inloggning öppnad<commit_after>//Kolla om inlogg redan existrerar
function kollainlogg(){
show();
'use strict';
	if(localStorage.LoggedIn == 'true'){
		dehamta(localStorage.anv,localStorage.pass);
		//loadtvatta('https://www.sgsstudentbostader.se/ext_gw.aspx?module=wwwash&lang=se#lblPanelName');
		//byggnad('http://www.sgsstudentbostader.se/ext_gw.aspx?module=wwwash&lang=se#lblPanelName');
		console.log('Det verkar finnas en användare');
		console.log("Detta är användaren" + localStorage.ReturnCode);
	}
	else{
		inputfields();
		loadtvattaklar();
		if(localStorage.ReturnCode == 'TOOMANYFAILEDLOGINS'){
			console.log('Du har loggat in för många gånger, var god vänta en stund och försök igen');
			tomany();
			loadtvattaklar();
		}
		console.log('Detta är användaren i else ' + localStorage.LoggedIn);
		}
	}

var historik = new Array();
//Hämtar inloggningsformet
function getFormData(){
show();
localStorage.anv=document.getElementById("username").value;
localStorage.pass=document.getElementById("password").value;
console.log("Den skickar inloggningen");
//Kyrptering av känslig data
encrypt(localStorage.anv,localStorage.pass);
dehamta(localStorage.anv,localStorage.pass);

}

//hämta marknad
function hamta(anv,pass) {
	$.getJSON("http://marknad.sgsstudentbostader.se/API/Service/AuthorizationServiceHandler.ashx?&syndicateNo=1&syndicateObjectMainGroupNo=1&username=" +anv.toString(CryptoJS.enc.Utf8)+ "&password=" +pass.toString(CryptoJS.enc.Utf8)+ "&Method=APILoginSGS&callback=?",

	  function(result) {
		//det går utmärkt att göra denna hämtning via en variabel, localStorage går alltså att utesluta.
		var infoinlogg = new Object();
		console.log(result);
		$.each(result, function (newsItem,news)
		{
		infoinlogg[newsItem]=news;
		localStorage[newsItem]=news
			console.log(newsItem + " = " + localStorage[newsItem])
			console.log(infoinlogg[newsItem]);
		});

		if(localStorage.ReturnCode == 'NOMATCH'){
			inloggningfel();
		}
		else{

	  loggain()
	  }
	  });

};

//logga in
function loggain(){
var str1="_=1366895108402&customer_name=" + localStorage.SGS_CustomerName + "&customerid=" + localStorage.UserName + "&isresident=" + localStorage.SGS_LivesAtSgs + "&loggedin=" + localStorage.LoggedIn + "&token=" + localStorage.SecurityTokenId + "&tvattstuga=" + localStorage.SGS_Laundry;
localStorage.urlen="http://www.sgsstudentbostader.se/Assets/Handlers/Momentum.ashx?" + str1;
hiddenbrowser();
}

//Hidden InAppBrowser
function hiddenbrowser(){
	console.log("Går in i funktionen hiddenbrowser");
	 var ref = window.open(localStorage.urlen, '_blank', 'hidden=yes');
	 console.log(ref);
	 	ref.addEventListener('loadstart', function(event) {
	 		console.log('Inloggning hidden loadstart');
		 	show();
	 	})
		ref.addEventListener('loadstop', function(event) {
			 byggnad('http://www.sgsstudentbostader.se/ext_gw.aspx?module=wwwash&lang=se#lblPanelName');
			 console.log("Inloggning hidden loadstop");

})

}

function loggaut(button){
if(button == 2){
	var loggautmomentum = window.open('http://www.sgsstudentbostader.se/Assets/Handlers/MomentumLogout.ashx?_=1390854127881', '_blank', 'hidden=yes');
	var loggautmarknad = window.open('http://marknad.sgsstudentbostader.se/API/Service/AuthorizationServiceHandler.ashx?&Method=APILogout&callback=jsonp1390854117395', '_blank', 'hidden=yes');
	console.log("Loggar ut momentum " + loggautmomentum);
	console.log("Loggar ut marknad " + loggautmarknad);

	loggautmomentum.addEventListener('loadstop', function(momentumevent){
		console.log('Den har laddat klart logga ut momentum');
		console.log(momentumevent);
	})

	loggautmarknad.addEventListener('loadstop', function(marknadevent){
		console.log('Den har laddat klart logga ut marknad');
		console.log(marknadevent);
	})
	console.log('Sätter anv och pass till null');
	localStorage.LoggedIn = 'false';
	localStorage.removeItem(anv);
	localStorage.removeItem(pass);
	console.log(localStorage.LoggedIn);
	//var mobilsystem = device.platform;
	//console.log(mobilsystem);

	//if(mobilsystem != 'iOS'){
	//	navigator.app.exitApp();
	//}
	//alert('Du är nu utloggad!');

	}
}

<|endoftext|>