<commit_before>if (typeof Cc == "undefined")
    var Cc = Components.classes;
if (typeof Ci == "undefined")
    var Ci = Components.interfaces;
if (typeof Cr == "undefined")
    var Cr = Components.results;
if (typeof Cu == "undefined")
    var Cu = Components.utils;

Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

var PersonaController = {}

var WindowListener = {
    setupBrowserUI: function(window, closebar) {
        // Take any steps to add UI or anything to the browser window
        // document.getElementById() etc. will work here
        PersonaController.startUp(window);
    },
    tearDownBrowserUI: function(window) {
        // Take any steps to remove UI or anything from the browser window
        // document.getElementById() etc. will work here
        PersonaController.shutDown(window);
    },
    // nsIWindowMediatorListener functions
    onOpenWindow: function(xulWindow) {
        // A new window has opened
        var domWindow = xulWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
            .getInterface(Components.interfaces.nsIDOMWindow);
        // Wait for it to finish loading
        domWindow.addEventListener("load", function listener() {
            domWindow.removeEventListener("load", listener, false);
            // If this is a browser window then setup its UI
            var windowtype = domWindow.document.documentElement.getAttribute("windowtype");
            if (windowtype == "navigator:browser" || windowtype == "mail:3pane") WindowListener.setupBrowserUI(domWindow);
        }, false);
    },
    onCloseWindow: function(xulWindow) {},
    onWindowTitleChange: function(xulWindow, newTitle) {}
};

var PersonasPlusBootstrapAddon = {
    prefsinstance: Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch),
    STRINGS: [],
    startup: function(data, reason) {
        this.requestAddPrerequisites(data);
        this.setDefaultPrefs();
        this.setDefaultLocalizations();
        Cu.import("resource://personas/modules/personas.js");
        this.setupBrowserUI();
        this.addWindowListener();
        this.addAddonSkinCSS();
        if (reason == ADDON_ENABLE || reason == ADDON_INSTALL) {
            Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
            try {
                var selected = PersonasPlusBootstrapAddon.prefsinstance.getCharPref("extensions.personas.selected");
                if (selected != "default") {
                    var lastselected0 = PersonasPlusBootstrapAddon.prefsinstance.getCharPref("extensions.personas.lastselected0");
                    LightweightThemeManager.currentTheme = JSON.parse(lastselected0);
                }
            } catch (e) {}
        }
    },
    shutdown: function(data, reason) {
        // When the application is shutting down we normally don't have to clean
        // up any UI changes made
        if (reason == APP_SHUTDOWN) return;
        this.removeDefaultLocalizations();
        this.tearBrowserUI();
        this.removeWindowListener();
        this.removeAddonSkinCSS();
        PersonaController.onQuitApplicationByShutDown();
        Cu.unload("resource://personas/modules/personas.js");
        this.requestRemovePrerequisites(data);
        if (reason == ADDON_DISABLE || reason == ADDON_UNINSTALL) {
            Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
            if (LightweightThemeManager.currentTheme) {
                if (LightweightThemeManager.currentTheme.id != "15131") {
                    var prompt = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
                    if (prompt.confirm(null, "Personas Plus", "Would you like to remove the current theme?")) {
                        try {
                            LightweightThemeManager.forgetUsedTheme(LightweightThemeManager.currentTheme.id);
                        } catch (e) {
                            try {
                                LightweightThemeManager.currentTheme = null;
                            } catch (e) {}
                        }
                    }
                }
                // Remove Groovy Blue Theme in any case at it was installed automatically
                LightweightThemeManager.forgetUsedTheme("15131");
            }
        }
    },
    requestAddPrerequisites: function(data) {
        this.addResourceProtocol(data);
        this.addChromeProtocol(data);
    },
    requestRemovePrerequisites: function(data) {
        this.removeResourceProtocol(data);
        this.removeChromeProtocol(data);
    },
    addResourceProtocol: function(data) {
        var ios = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
        var rh = ios.getProtocolHandler("resource").QueryInterface(Components.interfaces.nsIResProtocolHandler);
        var nfu = ios.newFileURI(data.installPath);
        var isDir = data.installPath.isDirectory();
        if (isDir) {
            nfu = ios.newURI(nfu.spec + "/", null, null);
        } else {
            nfu = ios.newURI("jar:" + nfu.spec + "!/", null, null);
        }
        //Note for Validator: This is safe and used to register resource protocol for our add-on, i.e. resource://personas/
        rh.setSubstitution("personas", nfu);
    },
    removeResourceProtocol: function(data) {
        var ios = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
        var r = ios.getProtocolHandler("resource").QueryInterface(Components.interfaces.nsIResProtocolHandler);
        r.setSubstitution("personas", null);
    },
    addChromeProtocol: function(data) {
        if (Services.vc.compare(Services.appinfo.platformVersion, "10.0") < 0 && Services.vc.compare(Services.appinfo.platformVersion, "8.0") >= 0)
            Components.manager.addBootstrappedManifestLocation(data.installPath);
    },
    removeChromeProtocol: function(data) {
        if (Services.vc.compare(Services.appinfo.platformVersion, "10.0") < 0 && Services.vc.compare(Services.appinfo.platformVersion, "8.0") >= 0)
            Components.manager.removeBootstrappedManifestLocation(data.installPath);
    },
    setDefaultPrefs: function() {
        function setDefaultPrefs(name, value) {
            function setPrefs(branch, name, value) {
                if (typeof value == "string") {
                    var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
                    str.data = value;
                    branch = branch ? branch : Services.prefs;
                    branch.setComplexValue(name, Components.interfaces.nsISupportsString, str);
                } else if (typeof value == "number") {
                    branch.setIntPref(name, value);
                } else if (typeof value == "boolean") {
                    branch.setBoolPref(name, value);
                }
            }
            var defaultBranch = Services.prefs.getDefaultBranch(null);
            setPrefs(defaultBranch, name, value);
        }
        Services.scriptloader.loadSubScript(this.getPrefsJS(), { pref: setDefaultPrefs });
    },
    getPrefsJS: function() {
        return "resource://personas/defaults/preferences/prefs.js";
    },
    setDefaultLocalizations: function() {
        Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService).flushBundles();
        this.STRINGS["personas.properties"] = Components.classes["@mozilla.org/intl/stringbundle;1"]
            .getService(Components.interfaces.nsIStringBundleService).createBundle("chrome://personas/locale/personas.properties");
        this.STRINGS["personas_bootstrap.properties"] = Components.classes["@mozilla.org/intl/stringbundle;1"]
            .getService(Components.interfaces.nsIStringBundleService).createBundle("chrome://personas/locale/personas_bootstrap.properties");
    },
    removeDefaultLocalizations: function() {
        this.STRINGS = [];
        Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService).flushBundles();
    },
    setupBrowserUI: function() {
        var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
        getService(Ci.nsIWindowMediator);
        // Get the list of browser windows already open
        var windows = wm.getEnumerator("navigator:browser");
        while (windows.hasMoreElements()) {
            var domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
            WindowListener.setupBrowserUI(domWindow, false);
        }
    },
    tearBrowserUI: function() {
        var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
        getService(Ci.nsIWindowMediator);
        // Get the list of browser windows already open
        var windows = wm.getEnumerator("navigator:browser");
        while (windows.hasMoreElements()) {
            var domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
            WindowListener.tearDownBrowserUI(domWindow);
        }
    },
    addWindowListener: function() {
        var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
        getService(Ci.nsIWindowMediator);
        // Wait for any new browser windows to open
        wm.addListener(WindowListener);
    },
    removeWindowListener: function() {
        var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
        getService(Ci.nsIWindowMediator);
        // Stop listening for any new browser windows to open
        wm.removeListener(WindowListener);
    },
    registerStyle: function(url) {
        var sss = Components.classes["@mozilla.org/content/style-sheet-service;1"]
            .getService(Components.interfaces.nsIStyleSheetService);
        var ios = Components.classes["@mozilla.org/network/io-service;1"]
            .getService(Components.interfaces.nsIIOService);
        var uri = ios.newURI(url, null, null);
        if (!sss.sheetRegistered(uri, sss.AUTHOR_SHEET))
            sss.loadAndRegisterSheet(uri, sss.AUTHOR_SHEET);
    },
    unregisterStyle: function(url) {
        var sss = Components.classes["@mozilla.org/content/style-sheet-service;1"]
            .getService(Components.interfaces.nsIStyleSheetService);
        var ios = Components.classes["@mozilla.org/network/io-service;1"]
            .getService(Components.interfaces.nsIIOService);
        var u = ios.newURI(url, null, null);
        if (sss.sheetRegistered(u, sss.AUTHOR_SHEET))
        //Note for Validator: This is safe and used to register our add-on skin, i.e. chrome://personas/skin/personas.css
            sss.unregisterSheet(u, sss.AUTHOR_SHEET);
    },
    addAddonSkinCSS: function() {
        this.registerStyle(this.getContentPersonasCSS());
        this.registerStyle(this.getSkinPersonasCSS());
        this.registerStyle(this.getSkinPersonasCSS());
    },
    removeAddonSkinCSS: function() {
        this.unregisterStyle(this.getContentPersonasCSS());
        this.unregisterStyle(this.getSkinPersonasCSS());
        this.unregisterStyle(this.getSkinPersonasCSS());
    },
    getContentPersonasCSS: function() {
        return "chrome://personas/content/personas.css";
    },
    getSkinPersonasCSS: function() {
        return "chrome://personas/skin/personas.css";
    },
    getContentOverlayCSS: function() {
        return "chrome://personas/content/overlay.css";
    }
}

function install(data) {}

function uninstall(data, reason) {
    if (reason != ADDON_UNINSTALL) return;
    Components.classes["@mozilla.org/preferences-service;1"]
        .getService(Components.interfaces.nsIPrefService).getBranch("extensions.personas.").deleteBranch("");
    Components.classes["@mozilla.org/intl/stringbundle;1"]
        .getService(Components.interfaces.nsIStringBundleService).flushBundles();
}

function startup(data, reason) {
    PersonasPlusBootstrapAddon.startup(data, reason);
}

function shutdown(data, reason) {
    PersonasPlusBootstrapAddon.shutdown(data, reason);
}
<commit_msg>Thunderbird Fix for Mac OS X<commit_after>if (typeof Cc == "undefined")
    var Cc = Components.classes;
if (typeof Ci == "undefined")
    var Ci = Components.interfaces;
if (typeof Cr == "undefined")
    var Cr = Components.results;
if (typeof Cu == "undefined")
    var Cu = Components.utils;

Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

var PersonaController = {}

var WindowListener = {
    setupBrowserUI: function(window, closebar) {
        // Take any steps to add UI or anything to the browser window
        // document.getElementById() etc. will work here
        PersonaController.startUp(window);
    },
    tearDownBrowserUI: function(window) {
        // Take any steps to remove UI or anything from the browser window
        // document.getElementById() etc. will work here
        PersonaController.shutDown(window);
    },
    // nsIWindowMediatorListener functions
    onOpenWindow: function(xulWindow) {
        // A new window has opened
        var domWindow = xulWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
            .getInterface(Components.interfaces.nsIDOMWindow);
        // Wait for it to finish loading
        domWindow.addEventListener("load", function listener() {
            domWindow.removeEventListener("load", listener, false);
            // If this is a browser window then setup its UI
            var windowtype = domWindow.document.documentElement.getAttribute("windowtype");
            if (windowtype == "navigator:browser" || windowtype == "mail:3pane") WindowListener.setupBrowserUI(domWindow);
        }, false);
    },
    onCloseWindow: function(xulWindow) {},
    onWindowTitleChange: function(xulWindow, newTitle) {}
};

var PersonasPlusBootstrapAddon = {
    prefsinstance: Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch),
    STRINGS: [],
    startup: function(data, reason) {
        this.requestAddPrerequisites(data);
        this.setDefaultPrefs();
        this.setDefaultLocalizations();
        Cu.import("resource://personas/modules/personas.js");
        this.setupBrowserUI();
        this.addWindowListener();
        this.addAddonSkinCSS();
        if (reason == ADDON_ENABLE || reason == ADDON_INSTALL) {
            Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
            try {
                var selected = PersonasPlusBootstrapAddon.prefsinstance.getCharPref("extensions.personas.selected");
                if (selected != "default") {
                    var lastselected0 = PersonasPlusBootstrapAddon.prefsinstance.getCharPref("extensions.personas.lastselected0");
                    LightweightThemeManager.currentTheme = JSON.parse(lastselected0);
                }
            } catch (e) {}
        }
    },
    shutdown: function(data, reason) {
        // When the application is shutting down we normally don't have to clean
        // up any UI changes made
        if (reason == APP_SHUTDOWN) return;
        this.removeDefaultLocalizations();
        this.tearBrowserUI();
        this.removeWindowListener();
        this.removeAddonSkinCSS();
        PersonaController.onQuitApplicationByShutDown();
        Cu.unload("resource://personas/modules/personas.js");
        this.requestRemovePrerequisites(data);
        if (reason == ADDON_DISABLE || reason == ADDON_UNINSTALL) {
            Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
            if (LightweightThemeManager.currentTheme) {
                if (LightweightThemeManager.currentTheme.id != "15131") {
                    var prompt = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
                    if (prompt.confirm(null, "Personas Plus", "Would you like to remove the current theme?")) {
                        try {
                            LightweightThemeManager.forgetUsedTheme(LightweightThemeManager.currentTheme.id);
                        } catch (e) {
                            try {
                                LightweightThemeManager.currentTheme = null;
                            } catch (e) {}
                        }
                    }
                }
                // Remove Groovy Blue Theme in any case at it was installed automatically
                LightweightThemeManager.forgetUsedTheme("15131");
            }
        }
    },
    requestAddPrerequisites: function(data) {
        this.addResourceProtocol(data);
        this.addChromeProtocol(data);
    },
    requestRemovePrerequisites: function(data) {
        this.removeResourceProtocol(data);
        this.removeChromeProtocol(data);
    },
    addResourceProtocol: function(data) {
        var ios = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
        var rh = ios.getProtocolHandler("resource").QueryInterface(Components.interfaces.nsIResProtocolHandler);
        var nfu = ios.newFileURI(data.installPath);
        var isDir = data.installPath.isDirectory();
        if (isDir) {
            nfu = ios.newURI(nfu.spec + "/", null, null);
        } else {
            nfu = ios.newURI("jar:" + nfu.spec + "!/", null, null);
        }
        //Note for Validator: This is safe and used to register resource protocol for our add-on, i.e. resource://personas/
        rh.setSubstitution("personas", nfu);
    },
    removeResourceProtocol: function(data) {
        var ios = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
        var r = ios.getProtocolHandler("resource").QueryInterface(Components.interfaces.nsIResProtocolHandler);
        r.setSubstitution("personas", null);
    },
    addChromeProtocol: function(data) {
        if (Services.vc.compare(Services.appinfo.platformVersion, "10.0") < 0 && Services.vc.compare(Services.appinfo.platformVersion, "8.0") >= 0)
            Components.manager.addBootstrappedManifestLocation(data.installPath);
    },
    removeChromeProtocol: function(data) {
        if (Services.vc.compare(Services.appinfo.platformVersion, "10.0") < 0 && Services.vc.compare(Services.appinfo.platformVersion, "8.0") >= 0)
            Components.manager.removeBootstrappedManifestLocation(data.installPath);
    },
    setDefaultPrefs: function() {
        function setDefaultPrefs(name, value) {
            function setPrefs(branch, name, value) {
                if (typeof value == "string") {
                    var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
                    str.data = value;
                    branch = branch ? branch : Services.prefs;
                    branch.setComplexValue(name, Components.interfaces.nsISupportsString, str);
                } else if (typeof value == "number") {
                    branch.setIntPref(name, value);
                } else if (typeof value == "boolean") {
                    branch.setBoolPref(name, value);
                }
            }
            var defaultBranch = Services.prefs.getDefaultBranch(null);
            setPrefs(defaultBranch, name, value);
        }
        Services.scriptloader.loadSubScript(this.getPrefsJS(), { pref: setDefaultPrefs });
    },
    getPrefsJS: function() {
        return "resource://personas/defaults/preferences/prefs.js";
    },
    setDefaultLocalizations: function() {
        Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService).flushBundles();
        this.STRINGS["personas.properties"] = Components.classes["@mozilla.org/intl/stringbundle;1"]
            .getService(Components.interfaces.nsIStringBundleService).createBundle("chrome://personas/locale/personas.properties");
        this.STRINGS["personas_bootstrap.properties"] = Components.classes["@mozilla.org/intl/stringbundle;1"]
            .getService(Components.interfaces.nsIStringBundleService).createBundle("chrome://personas/locale/personas_bootstrap.properties");
    },
    removeDefaultLocalizations: function() {
        this.STRINGS = [];
        Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService).flushBundles();
    },
    setupBrowserUI: function() {
        var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
        getService(Ci.nsIWindowMediator);
        // Get the list of browser windows already open
        var windows = wm.getEnumerator(this.getWindowType());
        while (windows.hasMoreElements()) {
            var domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
            WindowListener.setupBrowserUI(domWindow, false);
        }
    },
    tearBrowserUI: function() {
        var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
        getService(Ci.nsIWindowMediator);
        // Get the list of browser windows already open
        var windows = wm.getEnumerator(this.getWindowType());
        while (windows.hasMoreElements()) {
            var domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
            WindowListener.tearDownBrowserUI(domWindow);
        }
    },
    getWindowType: function() {
        if (Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).ID == "{3550f703-e582-4d05-9a08-453d09bdfdc6}") return "mail:3pane";
        else return "navigator:browser";
    },
    addWindowListener: function() {
        var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
        getService(Ci.nsIWindowMediator);
        // Wait for any new browser windows to open
        wm.addListener(WindowListener);
    },
    removeWindowListener: function() {
        var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
        getService(Ci.nsIWindowMediator);
        // Stop listening for any new browser windows to open
        wm.removeListener(WindowListener);
    },
    registerStyle: function(url) {
        var sss = Components.classes["@mozilla.org/content/style-sheet-service;1"]
            .getService(Components.interfaces.nsIStyleSheetService);
        var ios = Components.classes["@mozilla.org/network/io-service;1"]
            .getService(Components.interfaces.nsIIOService);
        var uri = ios.newURI(url, null, null);
        if (!sss.sheetRegistered(uri, sss.AUTHOR_SHEET))
            sss.loadAndRegisterSheet(uri, sss.AUTHOR_SHEET);
    },
    unregisterStyle: function(url) {
        var sss = Components.classes["@mozilla.org/content/style-sheet-service;1"]
            .getService(Components.interfaces.nsIStyleSheetService);
        var ios = Components.classes["@mozilla.org/network/io-service;1"]
            .getService(Components.interfaces.nsIIOService);
        var u = ios.newURI(url, null, null);
        if (sss.sheetRegistered(u, sss.AUTHOR_SHEET))
        //Note for Validator: This is safe and used to register our add-on skin, i.e. chrome://personas/skin/personas.css
            sss.unregisterSheet(u, sss.AUTHOR_SHEET);
    },
    addAddonSkinCSS: function() {
        this.registerStyle(this.getContentPersonasCSS());
        this.registerStyle(this.getSkinPersonasCSS());
        this.registerStyle(this.getSkinPersonasCSS());
    },
    removeAddonSkinCSS: function() {
        this.unregisterStyle(this.getContentPersonasCSS());
        this.unregisterStyle(this.getSkinPersonasCSS());
        this.unregisterStyle(this.getSkinPersonasCSS());
    },
    getContentPersonasCSS: function() {
        return "chrome://personas/content/personas.css";
    },
    getSkinPersonasCSS: function() {
        return "chrome://personas/skin/personas.css";
    },
    getContentOverlayCSS: function() {
        return "chrome://personas/content/overlay.css";
    }
}

function install(data) {}

function uninstall(data, reason) {
    if (reason != ADDON_UNINSTALL) return;
    Components.classes["@mozilla.org/preferences-service;1"]
        .getService(Components.interfaces.nsIPrefService).getBranch("extensions.personas.").deleteBranch("");
    Components.classes["@mozilla.org/intl/stringbundle;1"]
        .getService(Components.interfaces.nsIStringBundleService).flushBundles();
}

function startup(data, reason) {
    PersonasPlusBootstrapAddon.startup(data, reason);
}

function shutdown(data, reason) {
    PersonasPlusBootstrapAddon.shutdown(data, reason);
}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2009
 * Jan-Felix Schwarz
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

MOVI.namespace("model");

(function() {

	/**
     * Create the host element.
     * @method _createHostElement
     * @private
     */
    var _createHostElement = function() {
        var el = document.createElement('div');
        return el;
    };

	/**
     * Returns the appropriate subclass to choose when creating the shape
	 * object from the specified JSON object. If no subclass can be
	 * determined the Shape base class is returned.
     * @method _getSubclassForJSONObj
 	 * @param {Object} jsonObj The JSON object to create the shape from
	 * @param {Stencilset} stencilset The stencilset object
     * @private
     */
	var _getSubclassForJSONObj = function(jsonObj, stencilset) {
		var stencil = stencilset.getStencil(jsonObj.stencil.id);
		if(stencil) {
			if(stencil.type=="node")
				return MOVI.model.Node;
			else if(stencil.type=="edge")
				return MOVI.model.Edge;
		}
		return MOVI.model.Shape;
	}

	/**
     * An abstract base class providing a wrapper object for all
	 * model shapes (including the canvas, nodes, and edges).
     * @namespace MOVI.model
     * @class MOVI.model.Shape
     * @extends YAHOO.util.Element
     * @constructor
     * @param {Object} jsonObj The JSON object from which the new shape
     * is created
	 * @param {Object} stencilset The stencilset for lookup of the shape's
	 * stencil
	 * @param {Shape} parent The shapes's parent shape
	 * @param {String} prefix The element's ID prefix (unique per modelviewer)
	 * @param {Object} attr (optional) A key map of the shape element's
     * initial attributes
     */
    MOVI.model.Shape = function(jsonObj, stencilset, parent, prefix, attr) {

		YAHOO.lang.augmentObject(this, jsonObj, true);

		if(!stencilset) {
			throw new Error("No stencilset associated for shape with resource id" +
					 this.resourceId + ".", "shape.js");
			return false;
		}

		if(!this.resourceId) {
			throw new Error("The shape has no resource id.", "shape.js");
			return false;
		}

		if(!this.stencil || !this.stencil.id) {
			throw new Error("No stencil definition found for shape with resource id " +
				this.resourceId, "shape.js");
			return false;
		}

		// retrieve stencil
		var sId = this.stencil.id;
		this.stencil = stencilset.getStencil(sId);

		if(!this.stencil) {
			throw new Error("Could not find definition for stencil " + sId +
					 " in stencilset " + stencilset.namespace + ".", "shape.js" );
			return false;
		}

		// register parent shape
		this.parentShape = parent;

		// create host element
		attr = attr || {};
        el = _createHostElement.call(this, attr);

		MOVI.model.Shape.superclass.constructor.call(this, el, attr);

		this.set("id", prefix + this.resourceId);

		/* create child shape objects and append them as child elements */

		var childSh = this.childShapes; // store children's JSON objects in temp var
		this.childShapes = {};			// reset childShapes map

		// create shape object for each child and append to childShapes map
		for(var i = 0; i<childSh.length; i++) {
			var child = childSh[i];
			var subclass = _getSubclassForJSONObj(child, stencilset);
			child = new subclass(child, stencilset, this, prefix);
			this.childShapes[child.resourceId] = child;
			this.appendChild(child);
		}

	}

	MOVI.extend(MOVI.model.Shape, YAHOO.util.Element, {

		/**
		 * The shapes parent shape
		 * @property parentShape
		 */
		parentShape: null,

		/**
	     * Returns the canvas that owns this shape
	     * @method getCanvas
		 * @return {Canvas} The Canvas the shape belongs to
	     */
		getCanvas: function() {
			var e = this;
			while(e.parentShape!=null)
				e = e.parentShape;
			return e;
		},

		/**
		 * Returns a set on all incoming shapes
		 * @method getIncomingShapes
		 * @return {Array}
		 */
		getIncomingShapes: function(){

			if (!this._incomingShapes){

				var incoming = [];
				var canvas = this.getCanvas();

				if (canvas) {
					var nodes = canvas.getNodes();
					for (var i=0, len = nodes.length; i < len; ++i){
						if ((nodes[i].outgoing||[]).length > 0) {
							var isOutgoing = false;
								for (var j=0, oLen = nodes.length; j < oLen; ++j){
									if (nodes[i].outgoing[j].resourceId == this.resourceId){
										incoming.push(nodes[i]);
										break;
									}
								}
						}
					}
				}
				this._incomingShapes = incoming;
			}

			return this._incomingShapes;
		},

		/**
		 * Returns a set on all outgoing shapes
		 * @method getOutgoingShapes
		 * @return {Array}
		 */
		getOutgoingShapes: function(){

			if (!this._outgoingShapes){

				var outgoings = [];
				var canvas = this.getCanvas();

				if (canvas) {
					var og = this.outgoing || [];
					for (var i=0, len=og.length; i<len; ++i){
						var shape = canvas.getShape(og[i].resourceId);
						if (shape) {
							outgoings.push(shape);
						}
					}
				}

				this._outgoingShapes = outgoings;
			}

			return this._outgoingShapes;
		},

		/**
		 * Returns the bounds of a shape
		 *
		 */
		getBounds: function(){

			if (!this.bounds) {

				// ERROR HANDLING if there is no bounds set
				if (this.dockers) {
					var min, max;

					for (var i = 0, len = this.dockers.length; i < len; ++i) {

						var pos = {x:this.dockers[i].x, y:this.dockers[i].y}

						// Add the bounds of the docked incoming shape
						if (i==0){
							var nodes = this.getIncomingShapes();
							if (nodes.length > 0){
								var bounds = nodes[0].getAbsBounds();
								pos.x += bounds.upperLeft.x;
								pos.y += bounds.upperLeft.y;
							}
						}

						// Add the bounds of the docked outgoing shape
						if (i == len-1&&(this.outgoing||[]).length > 0) {
							var nodes = this.getOutgoingShapes();
							if (nodes.length > 0){
								var bounds = nodes[0].getAbsBounds();
								pos.x += bounds.upperLeft.x;
								pos.y += bounds.upperLeft.y;
							}
						}

						if (!min) min = {x: pos.x, y: pos.y}
						if (!max) max = {x: pos.x, y: pos.y }

						min.x = Math.min(min.x, pos.x);
						min.y = Math.min(min.y, pos.y);
						max.x = Math.max(max.x, pos.x);
						max.y = Math.max(max.y, pos.y);
					}

					if (min && max) {
						this.bounds = {
							upperLeft: min,
							lowerRight: max
						}
					}
				}

				if (!this.bounds){
					this.bounds =  {
						upperLeft: { x: 0, y: 0 },
						lowerRight: { x: 0, y: 0 }
					}
				}
			}

			return { upperLeft: {
				x: this.bounds.upperLeft.x,
				y: this.bounds.upperLeft.y
			  },
			  lowerRight: {
				x: this.bounds.lowerRight.x,
				y: this.bounds.lowerRight.y
			  }
			}

		},

		/**
	     * Returns the shape's absolute bounds coordinates. 'Absolute' means
		 * relative to the canvas element rather than relative to the document
	     * @method getAbsBounds
		 * @return {Object} The absolute shape bounds accessible as
		 * { upperLeft: {x:Number,y:Number}, lowerRight: {x:Number,y:Number} }
	     */
		getAbsBounds: function() {
			var absBounds =	this.getBounds();
			var e = this;
			while(e.parentShape!=null && e.parentShape.parentShape!=null) {
				e = e.parentShape;
				var b = e.getBounds();
				absBounds.upperLeft.x +=b.upperLeft.x;
				absBounds.upperLeft.y += b.upperLeft.y;
				absBounds.lowerRight.x += b.upperLeft.x;
				absBounds.lowerRight.y += b.upperLeft.y;
			}
			return absBounds;
		},

		/**
		 * Tests if the shape has children
		 * @method hasChildShapes
		 * @return {Boolean} true if the shape has children, false otherwise
		 */
		hasChildShapes: function() {
			for(key in this.childShapes) {
				if(!YAHOO.lang.hasOwnProperty(this.childShapes, key)) continue;
				return true;
			}
			return false;
		},

		/**
		 * Returns the stencil of this shape
		 * @method getStencil
		 * @return {Stencil} The stencil object
		 */
		getStencil: function() {
			return this.stencil;
		}

		// TODO: Doc for shape attributes from JSON

	});

})();<commit_msg>Fixed some issue which I caused in the getBounds of the MOVI API Shape implementation<commit_after>/**
 * Copyright (c) 2009
 * Jan-Felix Schwarz
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

MOVI.namespace("model");

(function() {

	/**
     * Create the host element.
     * @method _createHostElement
     * @private
     */
    var _createHostElement = function() {
        var el = document.createElement('div');
        return el;
    };

	/**
     * Returns the appropriate subclass to choose when creating the shape
	 * object from the specified JSON object. If no subclass can be
	 * determined the Shape base class is returned.
     * @method _getSubclassForJSONObj
 	 * @param {Object} jsonObj The JSON object to create the shape from
	 * @param {Stencilset} stencilset The stencilset object
     * @private
     */
	var _getSubclassForJSONObj = function(jsonObj, stencilset) {
		var stencil = stencilset.getStencil(jsonObj.stencil.id);
		if(stencil) {
			if(stencil.type=="node")
				return MOVI.model.Node;
			else if(stencil.type=="edge")
				return MOVI.model.Edge;
		}
		return MOVI.model.Shape;
	}

	/**
     * An abstract base class providing a wrapper object for all
	 * model shapes (including the canvas, nodes, and edges).
     * @namespace MOVI.model
     * @class MOVI.model.Shape
     * @extends YAHOO.util.Element
     * @constructor
     * @param {Object} jsonObj The JSON object from which the new shape
     * is created
	 * @param {Object} stencilset The stencilset for lookup of the shape's
	 * stencil
	 * @param {Shape} parent The shapes's parent shape
	 * @param {String} prefix The element's ID prefix (unique per modelviewer)
	 * @param {Object} attr (optional) A key map of the shape element's
     * initial attributes
     */
    MOVI.model.Shape = function(jsonObj, stencilset, parent, prefix, attr) {

		YAHOO.lang.augmentObject(this, jsonObj, true);

		if(!stencilset) {
			throw new Error("No stencilset associated for shape with resource id" +
					 this.resourceId + ".", "shape.js");
			return false;
		}

		if(!this.resourceId) {
			throw new Error("The shape has no resource id.", "shape.js");
			return false;
		}

		if(!this.stencil || !this.stencil.id) {
			throw new Error("No stencil definition found for shape with resource id " +
				this.resourceId, "shape.js");
			return false;
		}

		// retrieve stencil
		var sId = this.stencil.id;
		this.stencil = stencilset.getStencil(sId);

		if(!this.stencil) {
			throw new Error("Could not find definition for stencil " + sId +
					 " in stencilset " + stencilset.namespace + ".", "shape.js" );
			return false;
		}

		// register parent shape
		this.parentShape = parent;

		// create host element
		attr = attr || {};
        el = _createHostElement.call(this, attr);

		MOVI.model.Shape.superclass.constructor.call(this, el, attr);

		this.set("id", prefix + this.resourceId);

		/* create child shape objects and append them as child elements */

		var childSh = this.childShapes; // store children's JSON objects in temp var
		this.childShapes = {};			// reset childShapes map

		// create shape object for each child and append to childShapes map
		for(var i = 0; i<childSh.length; i++) {
			var child = childSh[i];
			var subclass = _getSubclassForJSONObj(child, stencilset);
			child = new subclass(child, stencilset, this, prefix);
			this.childShapes[child.resourceId] = child;
			this.appendChild(child);
		}

	}

	MOVI.extend(MOVI.model.Shape, YAHOO.util.Element, {

		/**
		 * The shapes parent shape
		 * @property parentShape
		 */
		parentShape: null,

		/**
	     * Returns the canvas that owns this shape
	     * @method getCanvas
		 * @return {Canvas} The Canvas the shape belongs to
	     */
		getCanvas: function() {
			var e = this;
			while(e.parentShape!=null)
				e = e.parentShape;
			return e;
		},

		/**
		 * Returns a set on all incoming shapes
		 * @method getIncomingShapes
		 * @return {Array}
		 */
		getIncomingShapes: function(){

			if (!this._incomingShapes){

				var incoming = [];
				var canvas = this.getCanvas();

				if (canvas) {
					var nodes = canvas.getNodes();
					for (var i in nodes){
						if(!YAHOO.lang.hasOwnProperty(nodes, i)) continue;

						if ((nodes[i].outgoing||[]).length > 0) {
							var isOutgoing = false;
								for (var j=0, oLen = nodes[i].outgoing.length; j < oLen; ++j){
									if (nodes[i].outgoing[j].resourceId == this.resourceId){
										incoming.push(nodes[i]);
										break;
									}
								}
						}
					}
				}
				this._incomingShapes = incoming;
			}

			return this._incomingShapes;
		},

		/**
		 * Returns a set on all outgoing shapes
		 * @method getOutgoingShapes
		 * @return {Array}
		 */
		getOutgoingShapes: function(){

			if (!this._outgoingShapes){

				var outgoings = [];
				var canvas = this.getCanvas();

				if (canvas) {
					var og = this.outgoing || [];
					for (var i=0, len=og.length; i<len; ++i){
						var shape = canvas.getShape(og[i].resourceId);
						if (shape) {
							outgoings.push(shape);
						}
					}
				}

				this._outgoingShapes = outgoings;
			}

			return this._outgoingShapes;
		},

		/**
		 * Returns the bounds of a shape
		 *
		 */
		getBounds: function(){

			if (!this.bounds) {

				// ERROR HANDLING if there is no bounds set
				if (this.dockers) {
					var min, max;

					for (var i = 0, len = this.dockers.length; i < len; ++i) {

						var pos = {x:this.dockers[i].x, y:this.dockers[i].y}

						// Add the bounds of the docked incoming shape
						if (i==0){
							var nodes = this.getIncomingShapes();
							if (nodes.length > 0){
								var bounds = nodes[0].getAbsBounds();
								pos.x += bounds.upperLeft.x;
								pos.y += bounds.upperLeft.y;
							}
						}

						// Add the bounds of the docked outgoing shape
						if (i == len-1&&(this.outgoing||[]).length > 0) {
							var nodes = this.getOutgoingShapes();
							if (nodes.length > 0){
								var bounds = nodes[0].getAbsBounds();
								pos.x += bounds.upperLeft.x;
								pos.y += bounds.upperLeft.y;
							}
						}

						if (!min) min = {x: pos.x, y: pos.y}
						if (!max) max = {x: pos.x, y: pos.y }

						min.x = Math.min(min.x, pos.x);
						min.y = Math.min(min.y, pos.y);
						max.x = Math.max(max.x, pos.x);
						max.y = Math.max(max.y, pos.y);
					}

					if (min && max) {
						this.bounds = {
							upperLeft: min,
							lowerRight: max
						}
					}
				}

				if (!this.bounds){
					this.bounds =  {
						upperLeft: { x: 0, y: 0 },
						lowerRight: { x: 0, y: 0 }
					}
				}
			}

			return { upperLeft: {
				x: this.bounds.upperLeft.x,
				y: this.bounds.upperLeft.y
			  },
			  lowerRight: {
				x: this.bounds.lowerRight.x,
				y: this.bounds.lowerRight.y
			  }
			}

		},

		/**
	     * Returns the shape's absolute bounds coordinates. 'Absolute' means
		 * relative to the canvas element rather than relative to the document
	     * @method getAbsBounds
		 * @return {Object} The absolute shape bounds accessible as
		 * { upperLeft: {x:Number,y:Number}, lowerRight: {x:Number,y:Number} }
	     */
		getAbsBounds: function() {
			var absBounds =	this.getBounds();
			var e = this;
			while(e.parentShape!=null && e.parentShape.parentShape!=null) {
				e = e.parentShape;
				var b = e.getBounds();
				absBounds.upperLeft.x +=b.upperLeft.x;
				absBounds.upperLeft.y += b.upperLeft.y;
				absBounds.lowerRight.x += b.upperLeft.x;
				absBounds.lowerRight.y += b.upperLeft.y;
			}
			return absBounds;
		},

		/**
		 * Tests if the shape has children
		 * @method hasChildShapes
		 * @return {Boolean} true if the shape has children, false otherwise
		 */
		hasChildShapes: function() {
			for(key in this.childShapes) {
				if(!YAHOO.lang.hasOwnProperty(this.childShapes, key)) continue;
				return true;
			}
			return false;
		},

		/**
		 * Returns the stencil of this shape
		 * @method getStencil
		 * @return {Stencil} The stencil object
		 */
		getStencil: function() {
			return this.stencil;
		}

		// TODO: Doc for shape attributes from JSON

	});

})();<|endoftext|>


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

    openerp.Tour.register({
        id: 'test_instance_introspection',
        name: 'Complete a basic order trough the Front-End',
        path: '/instance_introspection',
        mode: 'test',
        steps: [
            {
                title: 'Wait for the main screen',
                waitFor: 'h3:contains("Addons Paths"),#accordion.results',
                element: '.btn-reload',
                wait: 200,
            },
            {
                title:  'Load Repositories',
                waitFor: '#accordion.results',
            },
        ],
    });

})();

<commit_msg>[IMP] Test should not have wait if thy are long by default.<commit_after>(function() {
    'use strict';

    openerp.Tour.register({
        id: 'test_instance_introspection',
        name: 'Complete a basic order trough the Front-End',
        path: '/instance_introspection',
        mode: 'test',
        steps: [
            {
                title: 'Wait for the main screen',
                waitFor: 'h3:contains("Addons Paths"),#accordion.results',
                element: '.btn-reload',
            },
            {
                title:  'Load Repositories',
                waitFor: '#accordion.results',
            },
        ],
    });

})();

<|endoftext|>


<commit_before>'use strict';

const spawnSync = require('child_process').spawnSync;
const fs = require('fs');
const path = require('path');

// Return if node_modules is already installed. (Avoid infinite loops!)
if (fs.existsSync('node_modules')) {
  return;
}

// Else, run npm install.
else {
  spawnSync('npm', ['install'], {stdio: 'inherit'});
}

// Then, copy over Windows-specific files.
const windowsFiles = [
  'fepper.ps1',
  'fepper.vbs'
];

const srcDir = 'node_modules/fepper/excludes/profiles/windows';

windowsFiles.forEach(function (windowsFile) {
  fs.copyFileSync(path.resolve(srcDir, windowsFile), windowsFile);
});
<commit_msg>install script messaging updates<commit_after>'use strict';

const spawnSync = require('child_process').spawnSync;
const fs = require('fs');
const path = require('path');

// Return if node_modules is already installed. (Avoid infinite loops!)
if (fs.existsSync('node_modules')) {
  console.warn('Fepper is already installed! Aborting!');

  return;
}

// Else, run npm install.
else {
  spawnSync('npm', ['install'], {stdio: 'inherit'});
}

// Then, copy over Windows-specific files.
const windowsFiles = [
  'fepper.ps1',
  'fepper.vbs'
];

const srcDir = 'node_modules/fepper/excludes/profiles/windows';

windowsFiles.forEach(function (windowsFile) {
  fs.copyFileSync(path.resolve(srcDir, windowsFile), windowsFile);
});
<|endoftext|>


<commit_before>/*    Copyright 2017 - 2020 Firewalla Inc
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * Created by Melvin Tu on 05/01/2017.
 */

/*
 * WARNING:
 *   UPNP operations must be isolated to be one process.  NATPMP requires
 *   openning a port, which may cause trouble if two processes are doing
 *   the same
 */

'use strict';

let instance = null;

const log = require("../../net2/logger.js")(__filename);
const util = require('util');

const sysManager = require('../../net2/SysManager.js');

const _ = require('lodash');

const natpmp = require('./nat-pmp');
const natupnp = require('./nat-upnp');
const natpmpTimeout = 86400;

const sclient = require('../../util/redis_manager.js').getSubscriptionClient();
const Message = require('../../net2/Message.js');
const f = require('../../net2/Firewalla.js');
const ip = require('ip');
const mode = require('../../net2/Mode.js');

let registeredUpnpMappings = [];
const upnpCheckInterval = 15 * 60 * 1000 // 15 mins

module.exports = class {
  constructor() {
    if (instance == null) {
      // WAN UPnP/NATPMP client is used to add/remove mappings
      this.wanUpnpClient = null;
      this.wanNatPmpClient = null;

      // monitorUpnpClients are used to query mappings, i.e. read-only
      this.monitoredUpnpClients = [];

      instance = this;
      this.refreshTimers = {};

      this.scheduleReload();

      // TODO: move this to UPNPSensor
      // periodical checks whether all upnp mappings registered are alive
      // if not, adds back
      if (f.isMain()) {
        this.upnpIntervalHandler = setInterval(
          async () => {
            log.info("UPnP periodical check starts")
            if (registeredUpnpMappings.isEmpty) {
              log.info("No mapping registered.")
              return;
            }
            const results = await this.getPortMappingsUPNP().catch((err) => {
              log.error("Failed to get current mappings", err);
              return null;
            });
            if (results) {
              log.info("Current mappings: ", results);
              registeredUpnpMappings.forEach((check) => {
                log.info("Checking registered mapping:", check);
                if (_.isEmpty(
                  results.find((m) => this.mappingCompare(m, check))
                )) {
                  log.info("Mapping no longer exists, adding back to router...")
                  let { protocol, localPort, externalPort, description } = check;
                  this.addPortMappingUPNP(protocol, localPort, externalPort, description)
                } else {
                  log.info("Mapping still exists")
                }
              })
            }
          },
          upnpCheckInterval
        );

        sclient.on("message", (channel, message) => {
          if (channel === Message.MSG_SYS_NETWORK_INFO_RELOADED) {
            log.info("Schedule reload upnp clients since network info is reloaded");
            this.scheduleReload();
          }
        });
        sclient.subscribe(Message.MSG_SYS_NETWORK_INFO_RELOADED);
      }
    }
    return instance;
  }

  scheduleReload() {
    if (this.reloadTask)
      clearTimeout(this.reloadTask);
    this.reloadTask = setTimeout(async () => {
      if (this.wanNatPmpClient)
        this.wanNatPmpClient.close();
      this.wanNatPmpClient = null;
      if (this.wanUpnpClient)
        this.wanUpnpClient.close();
      this.wanUpnpClient = null;
      for (const c of this.monitoredUpnpClients) {
        c.close();
      }
      this.monitoredUpnpClients = [];
      // check availability of UPnP
      const defaultWanIp = sysManager.myDefaultWanIp();
      if (defaultWanIp && ip.isPrivate(defaultWanIp) && !(await mode.isRouterModeOn())) {
        const wanUpnpClient = natupnp.createClient({listenAddr: defaultWanIp});
        wanUpnpClient.externalIp((err, ip) => {
          if (err || ip == null) {
            log.info(`UPnP write client may not work on WAN ${defaultWanIp}`);
          }
        });
        this.wanUpnpClient = wanUpnpClient;
      }
      // check availability of NATPMP
      const defaultGateway = sysManager.myDefaultGateway();
      if (defaultGateway && ip.isPrivate(defaultWanIp) && !(await mode.isRouterModeOn())) {
        const wanNatPmpClient = natpmp.connect(defaultGateway, defaultWanIp);
        wanNatPmpClient.on('error', (err) => {
          log.error(`NATPMP write clien does not work on gw ${defaultGateway}`, err);
          wanNatPmpClient.close();
          this.wanNatPmpClient = null;
        });
        if (wanNatPmpClient) {
          wanNatPmpClient.externalIp((err, info) => {
            if (err || info == null) {
              log.info(`NATPMP write client may not work on gw ${defaultGateway}`);
            }
          });
          this.wanNatPmpClient = wanNatPmpClient;
        }
      }

      // initalize read-only upnp clients
      const monitoringInterfaces = sysManager.getMonitoringInterfaces();
      for (const iface of monitoringInterfaces) {
        if (iface.name.endsWith(":0"))
          continue;
        if (!iface.ip_address)
          continue;
        const upnpClient = natupnp.createClient({listenAddr: iface.ip_address});
        upnpClient.externalIp((err, ip) => {
          if (err || ip == null) {
            log.info(`UPnP monitor client may not work on ${iface.ip_address}`);
          }
          this.monitoredUpnpClients.push(upnpClient);
        });
      }

    }, 5000);
  }

  addPortMapping(protocol, localPort, externalPort, description, callback) {
    protocol = protocol.toLowerCase()
    callback = callback || function() { };

    try {
      if (this.wanUpnpClient) {
        return this.addPortMappingUPNP(this.wanUpnpClient, protocol, localPort, externalPort, description, callback);
      }
      if (this.wanNatPmpClient) {
        return this.addPortMappingNATPMP(this.wanNatPmpClient, protocol, localPort, externalPort, description, callback);
      }
      log.warn("Neither UPnP nor NATPMP write client works");
      callback(null);
    } catch (err) {
      log.error("Failed to add port mapping", err);
      callback(err);
    }
  }

  addPortMappingUPNP(client, protocol, localPort, externalPort, description, callback) {
    protocol = protocol.toLowerCase()
    callback = callback || function () { };

    client.portMapping({
      type: protocol,
      protocol: protocol,
      private: { host: sysManager.myDefaultWanIp(), port: localPort },
      public: externalPort,
      ttl: 0, // set ttl to 0 for better compatibility
      description: description
    }, (err) => {
      if (err) {
        log.error("Failed to add port mapping ", description, " :", err);
        callback(err);
        return;
      }
      log.info(util.format("Port mapping [%s, %s, %s] is added successfully.",
        protocol, localPort, externalPort));

      let mappingObj = { protocol, localPort, externalPort, description };

      // check if mapping registered
      if (_.isEmpty(registeredUpnpMappings.find((m) =>
        m.localPort     == localPort &&
        m.externalPort  == externalPort &&
        m.protocol      === protocol
      ))) {
        registeredUpnpMappings.push(mappingObj);
      } else {
        log.info("Mapping handler already exists");
      }

      callback();
    });
  }

  addPortMappingNATPMP(client, protocol, localPort, externalPort, description, callback) {
    protocol = protocol.toLowerCase()
    callback = callback || function () { };

    client.portMapping({ type: protocol, private: localPort, public: externalPort, ttl: natpmpTimeout }, (err, info) => {
      if (err == null) {
        this.refreshTimers[localPort + ":" + externalPort] = setTimeout(() => {
          this.addPortMappingNATPMP(protocol, localPort, externalPort, description, () => {
          });
        }, natpmpTimeout / 2 * 1000);
      }
      callback(err, info);
    });
  }

  removePortMappingNATPMP(client, protocol, localPort, externalPort, callback) {
    protocol = protocol.toLowerCase()
    callback = callback || function () { };
    let timer = this.refreshTimers[localPort + ":" + externalPort];
    if (timer) {
      clearTimeout(timer);
    }
    client.portUnmapping({ type: protocol, private: localPort, public: externalPort, ttl: 0 }, (err, info) => {
      if (err) {
        log.error("UPNP.removePortMappingNATPMP", err);
      }
      callback(err, info);
    });
  }

  removePortMapping(protocol, localPort, externalPort, callback) {
    protocol = protocol.toLowerCase()
    callback = callback || function () { };

    try {
      if (this.wanUpnpClient) {
        return this.removePortMappingUPNP(this.wanUpnpClient, protocol, localPort, externalPort, callback);
      }
      if (this.wanNatPmpClient) {
        return this.removePortMappingNATPMP(this.wanNatPmpClient, protocol, localPort, externalPort, callback);
      }
      log.warn("Neither UPnP nor NATPMP write client works");
      callback(null);
    } catch (err) {
      log.error("Failed to remove port mapping", err);
      callback(err);
    }
  }

  removePortMappingUPNP(client, protocol, localPort, externalPort, callback) {
    protocol = protocol.toLowerCase()
    callback = callback || function () { };

    client.portUnmapping({
      protocol: protocol,
      private: { host: sysManager.myDefaultWanIp(), port: localPort },
      public: externalPort
    }, (err) => {
      if (err) {
        log.error(`UPNP Failed to remove port mapping [${protocol}, ${localPort}, ${externalPort}]: ` + err);
        if (callback) {
          callback(err);
        }
        return;
      }

      registeredUpnpMappings = _.reject(registeredUpnpMappings, (m) =>
        m.localPort     == localPort &&
        m.externalPort  == externalPort &&
        m.protocol      === protocol
      );

      log.info(util.format("Port mapping [%s, %s, %s] is removed successfully"
        , protocol, localPort, externalPort));

      if (callback) {
        callback();
      }
    });
  }

  getLocalPortMappings(description, callback) {
    callback = callback || function() {};
    this.getPortMappingsUPNP().then((result) => {
      callback(null, result)
    }).catch((err) => {
      callback(err, null);
    });
  }

  async getPortMappingsUPNP() {
    let results = [];
    for (const c of this.monitoredUpnpClients) {
      const getMappingsAsync = util.promisify(c.getMappings).bind(c);
      const mappings = await getMappingsAsync().catch((err) => []);
      results = results.concat(mappings);
    }
    return results;
  }

  getRegisteredUpnpMappings() {
    return registeredUpnpMappings;
  }

  mappingCompare(natUpnpMapping, localMapping) {
    // "==" is used instead of "===" with intention here to enable comparison between number and string
    return  natUpnpMapping.public.port   ==  localMapping.externalPort &&
      natUpnpMapping.private.port  ==  localMapping.localPort &&
      natUpnpMapping.protocol      === localMapping.protocol;
  }

  async getExternalIP() {
    return new Promise((resolve, reject) => {
      if (this.wanUpnpClient) {
        this.wanUpnpClient.externalIp((err, ip) => {
          if(err) {
            reject(err);
          } else {
            resolve(ip);
          }
        })
      } else {
        resolve(null);
      }
    });
  }
}

<commit_msg>upnp minor bug fix<commit_after>/*    Copyright 2017 - 2020 Firewalla Inc
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * Created by Melvin Tu on 05/01/2017.
 */

/*
 * WARNING:
 *   UPNP operations must be isolated to be one process.  NATPMP requires
 *   openning a port, which may cause trouble if two processes are doing
 *   the same
 */

'use strict';

let instance = null;

const log = require("../../net2/logger.js")(__filename);
const util = require('util');

const sysManager = require('../../net2/SysManager.js');

const _ = require('lodash');

const natpmp = require('./nat-pmp');
const natupnp = require('./nat-upnp');
const natpmpTimeout = 86400;

const sclient = require('../../util/redis_manager.js').getSubscriptionClient();
const Message = require('../../net2/Message.js');
const f = require('../../net2/Firewalla.js');
const ip = require('ip');
const mode = require('../../net2/Mode.js');

let registeredUpnpMappings = [];
const upnpCheckInterval = 15 * 60 * 1000 // 15 mins

module.exports = class {
  constructor() {
    if (instance == null) {
      // WAN UPnP/NATPMP client is used to add/remove mappings
      this.wanUpnpClient = null;
      this.wanNatPmpClient = null;

      // monitorUpnpClients are used to query mappings, i.e. read-only
      this.monitoredUpnpClients = [];

      instance = this;
      this.refreshTimers = {};

      this.scheduleReload();

      // TODO: move this to UPNPSensor
      // periodical checks whether all upnp mappings registered are alive
      // if not, adds back
      if (f.isMain()) {
        this.upnpIntervalHandler = setInterval(
          async () => {
            log.info("UPnP periodical check starts")
            if (registeredUpnpMappings.isEmpty) {
              log.info("No mapping registered.")
              return;
            }
            const results = await this.getPortMappingsUPNP().catch((err) => {
              log.error("Failed to get current mappings", err);
              return null;
            });
            if (results) {
              log.info("Current mappings: ", results);
              registeredUpnpMappings.forEach((check) => {
                log.info("Checking registered mapping:", check);
                if (_.isEmpty(
                  results.find((m) => this.mappingCompare(m, check))
                )) {
                  log.info("Mapping no longer exists, adding back to router...")
                  let { protocol, localPort, externalPort, description } = check;
                  if (this.wanUpnpClient)
                    this.addPortMappingUPNP(this.wanUpnpClient, protocol, localPort, externalPort, description);
                } else {
                  log.info("Mapping still exists")
                }
              })
            }
          },
          upnpCheckInterval
        );

        sclient.on("message", (channel, message) => {
          if (channel === Message.MSG_SYS_NETWORK_INFO_RELOADED) {
            log.info("Schedule reload upnp clients since network info is reloaded");
            this.scheduleReload();
          }
        });
        sclient.subscribe(Message.MSG_SYS_NETWORK_INFO_RELOADED);
      }
    }
    return instance;
  }

  scheduleReload() {
    if (this.reloadTask)
      clearTimeout(this.reloadTask);
    this.reloadTask = setTimeout(async () => {
      if (this.wanNatPmpClient)
        this.wanNatPmpClient.close();
      this.wanNatPmpClient = null;
      if (this.wanUpnpClient)
        this.wanUpnpClient.close();
      this.wanUpnpClient = null;
      for (const c of this.monitoredUpnpClients) {
        c.close();
      }
      this.monitoredUpnpClients = [];
      registeredUpnpMappings = [];
      // check availability of UPnP
      const defaultWanIp = sysManager.myDefaultWanIp();
      if (defaultWanIp && ip.isPrivate(defaultWanIp) && !(await mode.isRouterModeOn())) {
        const wanUpnpClient = natupnp.createClient({listenAddr: defaultWanIp});
        wanUpnpClient.externalIp((err, ip) => {
          if (err || ip == null) {
            log.info(`UPnP write client may not work on WAN ${defaultWanIp}`);
          }
        });
        this.wanUpnpClient = wanUpnpClient;
      }
      // check availability of NATPMP
      const defaultGateway = sysManager.myDefaultGateway();
      if (defaultGateway && ip.isPrivate(defaultWanIp) && !(await mode.isRouterModeOn())) {
        const wanNatPmpClient = natpmp.connect(defaultGateway, defaultWanIp);
        wanNatPmpClient.on('error', (err) => {
          log.error(`NATPMP write clien does not work on gw ${defaultGateway}`, err);
          wanNatPmpClient.close();
          this.wanNatPmpClient = null;
        });
        if (wanNatPmpClient) {
          wanNatPmpClient.externalIp((err, info) => {
            if (err || info == null) {
              log.info(`NATPMP write client may not work on gw ${defaultGateway}`);
            }
          });
          this.wanNatPmpClient = wanNatPmpClient;
        }
      }

      // initalize read-only upnp clients
      const monitoringInterfaces = sysManager.getMonitoringInterfaces();
      for (const iface of monitoringInterfaces) {
        if (iface.name.endsWith(":0"))
          continue;
        if (!iface.ip_address)
          continue;
        const upnpClient = natupnp.createClient({listenAddr: iface.ip_address});
        upnpClient.externalIp((err, ip) => {
          if (err || ip == null) {
            log.info(`UPnP monitor client may not work on ${iface.ip_address}`);
          }
          this.monitoredUpnpClients.push(upnpClient);
        });
      }

    }, 5000);
  }

  addPortMapping(protocol, localPort, externalPort, description, callback) {
    protocol = protocol.toLowerCase()
    callback = callback || function() { };

    try {
      if (this.wanUpnpClient) {
        return this.addPortMappingUPNP(this.wanUpnpClient, protocol, localPort, externalPort, description, callback);
      }
      if (this.wanNatPmpClient) {
        return this.addPortMappingNATPMP(this.wanNatPmpClient, protocol, localPort, externalPort, description, callback);
      }
      log.warn("Neither UPnP nor NATPMP write client works");
      callback(null);
    } catch (err) {
      log.error("Failed to add port mapping", err);
      callback(err);
    }
  }

  addPortMappingUPNP(client, protocol, localPort, externalPort, description, callback) {
    protocol = protocol.toLowerCase()
    callback = callback || function () { };

    client.portMapping({
      type: protocol,
      protocol: protocol,
      private: { host: sysManager.myDefaultWanIp(), port: localPort },
      public: externalPort,
      ttl: 0, // set ttl to 0 for better compatibility
      description: description
    }, (err) => {
      if (err) {
        log.error("Failed to add port mapping ", description, " :", err);
        callback(err);
        return;
      }
      log.info(util.format("Port mapping [%s, %s, %s] is added successfully.",
        protocol, localPort, externalPort));

      let mappingObj = { protocol, localPort, externalPort, description };

      // check if mapping registered
      if (_.isEmpty(registeredUpnpMappings.find((m) =>
        m.localPort     == localPort &&
        m.externalPort  == externalPort &&
        m.protocol      === protocol
      ))) {
        registeredUpnpMappings.push(mappingObj);
      } else {
        log.info("Mapping handler already exists");
      }

      callback();
    });
  }

  addPortMappingNATPMP(client, protocol, localPort, externalPort, description, callback) {
    protocol = protocol.toLowerCase()
    callback = callback || function () { };

    client.portMapping({ type: protocol, private: localPort, public: externalPort, ttl: natpmpTimeout }, (err, info) => {
      if (err == null) {
        this.refreshTimers[localPort + ":" + externalPort] = setTimeout(() => {
          this.addPortMappingNATPMP(protocol, localPort, externalPort, description, () => {
          });
        }, natpmpTimeout / 2 * 1000);
      }
      callback(err, info);
    });
  }

  removePortMappingNATPMP(client, protocol, localPort, externalPort, callback) {
    protocol = protocol.toLowerCase()
    callback = callback || function () { };
    let timer = this.refreshTimers[localPort + ":" + externalPort];
    if (timer) {
      clearTimeout(timer);
    }
    client.portUnmapping({ type: protocol, private: localPort, public: externalPort, ttl: 0 }, (err, info) => {
      if (err) {
        log.error("UPNP.removePortMappingNATPMP", err);
      }
      callback(err, info);
    });
  }

  removePortMapping(protocol, localPort, externalPort, callback) {
    protocol = protocol.toLowerCase()
    callback = callback || function () { };

    try {
      if (this.wanUpnpClient) {
        return this.removePortMappingUPNP(this.wanUpnpClient, protocol, localPort, externalPort, callback);
      }
      if (this.wanNatPmpClient) {
        return this.removePortMappingNATPMP(this.wanNatPmpClient, protocol, localPort, externalPort, callback);
      }
      log.warn("Neither UPnP nor NATPMP write client works");
      callback(null);
    } catch (err) {
      log.error("Failed to remove port mapping", err);
      callback(err);
    }
  }

  removePortMappingUPNP(client, protocol, localPort, externalPort, callback) {
    protocol = protocol.toLowerCase()
    callback = callback || function () { };

    client.portUnmapping({
      protocol: protocol,
      private: { host: sysManager.myDefaultWanIp(), port: localPort },
      public: externalPort
    }, (err) => {
      if (err) {
        log.error(`UPNP Failed to remove port mapping [${protocol}, ${localPort}, ${externalPort}]: ` + err);
        if (callback) {
          callback(err);
        }
        return;
      }

      registeredUpnpMappings = _.reject(registeredUpnpMappings, (m) =>
        m.localPort     == localPort &&
        m.externalPort  == externalPort &&
        m.protocol      === protocol
      );

      log.info(util.format("Port mapping [%s, %s, %s] is removed successfully"
        , protocol, localPort, externalPort));

      if (callback) {
        callback();
      }
    });
  }

  getLocalPortMappings(description, callback) {
    callback = callback || function() {};
    this.getPortMappingsUPNP().then((result) => {
      callback(null, result)
    }).catch((err) => {
      callback(err, null);
    });
  }

  async getPortMappingsUPNP() {
    let results = [];
    for (const c of this.monitoredUpnpClients) {
      const getMappingsAsync = util.promisify(c.getMappings).bind(c);
      const mappings = await getMappingsAsync().catch((err) => []);
      results = results.concat(mappings);
    }
    return results;
  }

  getRegisteredUpnpMappings() {
    return registeredUpnpMappings;
  }

  mappingCompare(natUpnpMapping, localMapping) {
    // "==" is used instead of "===" with intention here to enable comparison between number and string
    return  natUpnpMapping.public.port   ==  localMapping.externalPort &&
      natUpnpMapping.private.port  ==  localMapping.localPort &&
      natUpnpMapping.protocol      === localMapping.protocol;
  }

  async getExternalIP() {
    return new Promise((resolve, reject) => {
      if (this.wanUpnpClient) {
        this.wanUpnpClient.externalIp((err, ip) => {
          if(err) {
            reject(err);
          } else {
            resolve(ip);
          }
        })
      } else {
        resolve(null);
      }
    });
  }
}

<|endoftext|>


<commit_before>angular.module('ionic.service.actionSheet', ['ionic.service.templateLoad', 'ionic.service.platform', 'ionic.ui.actionSheet', 'ngAnimate'])

/**
 * @ngdoc service
 * @name $ionicActionSheet
 * @module ionic
 * @description
 * The Action Sheet is a slide-up pane that lets the user choose from a set of options.
 * Dangerous options are highlighted in red and made obvious.
 *
 * There are easy ways to cancel out of the action sheet, such as tapping the backdrop or even
 * hitting escape on the keyboard for desktop testing.
 *
 * ![Action Sheet](http://ionicframework.com.s3.amazonaws.com/docs/controllers/actionSheet.gif)
 *
 * @usage
 * To trigger an Action Sheet in your code, use the $ionicActionSheet service in your angular controllers:
 *
 * ```js
 * angular.module('mySuperApp', ['ionic'])
 * .controller(function($scope, $ionicActionSheet) {
 *
 *  // Triggered on a button click, or some other target
 *  $scope.show = function() {
 *
 *    // Show the action sheet
 *    $ionicActionSheet.show({
 *      buttons: [
 *        { text: 'Share' },
 *        { text: 'Move' },
 *      ],
 *      destructiveText: 'Delete',
 *      titleText: 'Modify your album',
 *      cancelText: 'Cancel',
 *      buttonClicked: function(index) {
 *        return true;
 *      }
 *    });
 *
 *  };
 * });
 * ```
 *
 */
.factory('$ionicActionSheet', ['$rootScope', '$document', '$compile', '$animate', '$timeout', '$ionicTemplateLoader', '$ionicPlatform',
function($rootScope, $document, $compile, $animate, $timeout, $ionicTemplateLoader, $ionicPlatform) {

  return {
    /**
     * @ngdoc method
     * @name $ionicActionSheet#show
     * @description
     * Load and return a new action sheet.
     *
     * A new isolated scope will be created for the
     * action sheet and the new element will be appended into the body.
     *
     * @param {object} opts The options for this ActionSheet. Properties:
     *
     *  - `[Object]` `buttons` Which buttons to show.  Each button is an object with a `text` field.
     *  - `{string}` `titleText` The title to show on the action sheet.
     *  - `{string=}` `cancelText` The text for a 'cancel' button on the action sheet.
     *  - `{string=}` `destructiveText` The text for a 'danger' on the action sheet.
     *  - `{function=}` `cancel` Called if the cancel button is pressed or the backdrop is tapped.
     *  - `{function=}` `buttonClicked` Called when one of the non-destructive buttons is clicked,
     *     with the index of the button that was clicked. Return true to close the action sheet,
     *     or false to keep it opened.
     *  - `{function=}` `destructiveButtonClicked` Called when the destructive button is clicked.
     *     Return true to close the action sheet, or false to keep it opened.
     */
    show: function(opts) {
      var scope = $rootScope.$new(true);

      angular.extend(scope, opts);

      // Compile the template
      var element = $compile('<ion-action-sheet buttons="buttons"></ion-action-sheet>')(scope);

      // Grab the sheet element for animation
      var sheetEl = angular.element(element[0].querySelector('.action-sheet-wrapper'));

      var hideSheet = function(didCancel) {
        sheetEl.removeClass('action-sheet-up');
        if(didCancel) {
          $timeout(function(){
            opts.cancel();
          }, 200);
        }

        $animate.removeClass(element, 'active', function() {
          scope.$destroy();
        });

        $document[0].body.classList.remove('action-sheet-open');

        scope.$deregisterBackButton && scope.$deregisterBackButton();
      };

      // Support Android back button to close
      scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction(function(){
        hideSheet();
      }, 300);

      scope.cancel = function() {
        hideSheet(true);
      };

      scope.buttonClicked = function(index) {
        // Check if the button click event returned true, which means
        // we can close the action sheet
        if((opts.buttonClicked && opts.buttonClicked(index)) === true) {
          hideSheet(false);
        }
      };

      scope.destructiveButtonClicked = function() {
        // Check if the destructive button click event returned true, which means
        // we can close the action sheet
        if((opts.destructiveButtonClicked && opts.destructiveButtonClicked()) === true) {
          hideSheet(false);
        }
      };

      $document[0].body.appendChild(element[0]);

      $document[0].body.classList.add('action-sheet-open');

      var sheet = new ionic.views.ActionSheet({el: element[0] });
      scope.sheet = sheet;

      $animate.addClass(element, 'active');

      $timeout(function(){
        sheetEl.addClass('action-sheet-up');
      }, 20);

      return sheet;
    }
  };

}]);
<commit_msg>fix($ionicActionSheet): provide default functions for options<commit_after>angular.module('ionic.service.actionSheet', ['ionic.service.templateLoad', 'ionic.service.platform', 'ionic.ui.actionSheet', 'ngAnimate'])

/**
 * @ngdoc service
 * @name $ionicActionSheet
 * @module ionic
 * @description
 * The Action Sheet is a slide-up pane that lets the user choose from a set of options.
 * Dangerous options are highlighted in red and made obvious.
 *
 * There are easy ways to cancel out of the action sheet, such as tapping the backdrop or even
 * hitting escape on the keyboard for desktop testing.
 *
 * ![Action Sheet](http://ionicframework.com.s3.amazonaws.com/docs/controllers/actionSheet.gif)
 *
 * @usage
 * To trigger an Action Sheet in your code, use the $ionicActionSheet service in your angular controllers:
 *
 * ```js
 * angular.module('mySuperApp', ['ionic'])
 * .controller(function($scope, $ionicActionSheet) {
 *
 *  // Triggered on a button click, or some other target
 *  $scope.show = function() {
 *
 *    // Show the action sheet
 *    $ionicActionSheet.show({
 *      buttons: [
 *        { text: 'Share' },
 *        { text: 'Move' },
 *      ],
 *      destructiveText: 'Delete',
 *      titleText: 'Modify your album',
 *      cancelText: 'Cancel',
 *      buttonClicked: function(index) {
 *        return true;
 *      }
 *    });
 *
 *  };
 * });
 * ```
 *
 */
.factory('$ionicActionSheet', ['$rootScope', '$document', '$compile', '$animate', '$timeout', '$ionicTemplateLoader', '$ionicPlatform',
function($rootScope, $document, $compile, $animate, $timeout, $ionicTemplateLoader, $ionicPlatform) {

  return {
    /**
     * @ngdoc method
     * @name $ionicActionSheet#show
     * @description
     * Load and return a new action sheet.
     *
     * A new isolated scope will be created for the
     * action sheet and the new element will be appended into the body.
     *
     * @param {object} opts The options for this ActionSheet. Properties:
     *
     *  - `[Object]` `buttons` Which buttons to show.  Each button is an object with a `text` field.
     *  - `{string}` `titleText` The title to show on the action sheet.
     *  - `{string=}` `cancelText` The text for a 'cancel' button on the action sheet.
     *  - `{string=}` `destructiveText` The text for a 'danger' on the action sheet.
     *  - `{function=}` `cancel` Called if the cancel button is pressed or the backdrop is tapped.
     *  - `{function=}` `buttonClicked` Called when one of the non-destructive buttons is clicked,
     *     with the index of the button that was clicked. Return true to close the action sheet,
     *     or false to keep it opened.
     *  - `{function=}` `destructiveButtonClicked` Called when the destructive button is clicked.
     *     Return true to close the action sheet, or false to keep it opened.
     */
    show: function(opts) {
      var scope = $rootScope.$new(true);

      angular.extend(scope, {
        cancel: angular.noop,
        buttonClicked: angular.noop,
        destructiveButtonClicked: angular.noop
      }, opts);

      // Compile the template
      var element = $compile('<ion-action-sheet buttons="buttons"></ion-action-sheet>')(scope);

      // Grab the sheet element for animation
      var sheetEl = angular.element(element[0].querySelector('.action-sheet-wrapper'));

      var hideSheet = function(didCancel) {
        sheetEl.removeClass('action-sheet-up');
        if(didCancel) {
          $timeout(function(){
            opts.cancel();
          }, 200);
        }

        $animate.removeClass(element, 'active', function() {
          scope.$destroy();
        });

        $document[0].body.classList.remove('action-sheet-open');

        scope.$deregisterBackButton && scope.$deregisterBackButton();
      };

      // Support Android back button to close
      scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction(function(){
        hideSheet();
      }, 300);

      scope.cancel = function() {
        hideSheet(true);
      };

      scope.buttonClicked = function(index) {
        // Check if the button click event returned true, which means
        // we can close the action sheet
        if((opts.buttonClicked && opts.buttonClicked(index)) === true) {
          hideSheet(false);
        }
      };

      scope.destructiveButtonClicked = function() {
        // Check if the destructive button click event returned true, which means
        // we can close the action sheet
        if((opts.destructiveButtonClicked && opts.destructiveButtonClicked()) === true) {
          hideSheet(false);
        }
      };

      $document[0].body.appendChild(element[0]);

      $document[0].body.classList.add('action-sheet-open');

      var sheet = new ionic.views.ActionSheet({el: element[0] });
      scope.sheet = sheet;

      $animate.addClass(element, 'active');

      $timeout(function(){
        sheetEl.addClass('action-sheet-up');
      }, 20);

      return sheet;
    }
  };

}]);
<|endoftext|>


<commit_before>'use strict';

var debug              = require('debug-ext')('business-process-data-forms-print')
  , ensureCallable     = require('es5-ext/object/valid-callable')
  , ensureObject       = require('es5-ext/object/valid-object')
  , ensureString       = require('es5-ext/object/validate-stringifiable-value')

  , re = /^\/business-process-data-forms-([0-9][0-9a-z]+)\.pdf$/;

module.exports = function (config) {
	var queryHandler = ensureCallable(ensureObject(config).queryHandler)
	  , uploadsPath  = ensureString(config.uploadsPath)
	  , isDev        = ensureObject(config.env).dev
	  , stMiddleware = ensureCallable(config.stMiddleware);

	debug('Delcaring');

	return function (req, res, next) {
		var url   = req._parsedUrl.pathname
		  , match = url.match(re)
		  , archive, archiveFile, onError, businessProcessId;

		debug('matching:', url);

		if (!match) {
			debug('not matched');
			next();
			return;
		}
		debug('matched!');

		businessProcessId = match[1];
		queryHandler([businessProcessId], 'generateDataFormsPdf', {
			businessProcessId: businessProcessId
		}).done(function (result) {
			if (!result) {
				debug('not found');
				res.statusCode = 404;
				res.end('Not Found');
				return;
			}
			debug('done processing');
			return stMiddleware(req, res, next);
		}, function (err) {
			debug('error');
			debug(err.stack);
			res.statusCode = 500;
			res.end('Server error');
		});
		//
	};
};
<commit_msg>Issue #1389. Cleanup data forms print middleware<commit_after>'use strict';

var debug              = require('debug-ext')('business-process-data-forms-print')
  , ensureCallable     = require('es5-ext/object/valid-callable')
  , ensureObject       = require('es5-ext/object/valid-object')
  , ensureString       = require('es5-ext/object/validate-stringifiable-value')
  , resolve            = require('path').resolve

  , re = /^\/business-process-data-forms-([0-9][0-9a-z]+)\.pdf$/;

module.exports = function (config) {
	var queryHandler = ensureCallable(ensureObject(config).queryHandler)
	  , uploadsPath  = ensureString(config.uploadsPath)
	  , stMiddleware = ensureCallable(config.stMiddleware);

	return function (req, res, next) {
		var url   = req._parsedUrl.pathname
		  , match = url.match(re)
		  , businessProcessId, filePath;

		if (!match) {
			next();
			return;
		}

		businessProcessId = match[1];
		filePath = resolve(uploadsPath, url.slice(1));

		queryHandler([businessProcessId], 'generateDataFormsPdf', {
			businessProcessId: businessProcessId,
			filePath: filePath
		}).done(function (result) {
			if (!result) {
				res.statusCode = 404;
				res.end('Not Found');
				return;
			}
			return stMiddleware(req, res, next);
		}, function (err) {
			debug(err.stack);
			res.statusCode = 500;
			res.end('Server error');
		});
		//
	};
};
<|endoftext|>