<commit_before>
/*
 *  Guacamole - Pure JavaScript/HTML VNC Client
 *  Copyright (C) 2010  Michael Jumper
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  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/>.
 */

function VNCClient(display) {

    var STATE_IDLE          = 0;
    var STATE_CONNECTING    = 1;
    var STATE_WAITING       = 2;
    var STATE_CONNECTED     = 3;
    var STATE_DISCONNECTING = 4;
    var STATE_DISCONNECTED  = 5;

    var currentState = STATE_IDLE;
    var stateChangeHandler = null;

    function setState(state) {
        if (state != currentState) {
            currentState = state;
            if (stateChangeHandler)
                stateChangeHandler(currentState);
        }
    }

    this.setOnStateChangeHandler = function(handler) {
        stateChangeHandler = handler;
    }

    function isConnected() {
        return currentState == STATE_CONNECTED
            || currentState == STATE_WAITING;
    }

    var keyIndex = 0;
    var xmlIndex = 0;

    // Layers
    var background = null;
    var cursor = null;

    var cursorImage = null;
    var cursorHotspotX = 0;
    var cursorHotspotY = 0;

    // FIXME: Make object. Clean up.
    var cursorRectX = 0;
    var cursorRectY = 0;
    var cursorRectW = 0;
    var cursorRectH = 0;

    var cursorHidden = 0;

    function redrawCursor() {

        // Hide hardware cursor
        if (cursorHidden == 0) {
            display.className += " hideCursor";
            cursorHidden = 1;
        }

        // Erase old cursor
        cursor.clearRect(cursorRectX, cursorRectY, cursorRectW, cursorRectH);

        // Update rect
        cursorRectX = mouse.getX() - cursorHotspotX;
        cursorRectY = mouse.getY() - cursorHotspotY;
        cursorRectW = cursorImage.width;
        cursorRectH = cursorImage.height;

        // Draw new cursor
        cursor.drawImage(cursorRectX, cursorRectY, cursorImage);
    }

	/*****************************************/
	/*** Keyboard                          ***/
	/*****************************************/

    var keyboard = new GuacamoleKeyboard(document);

    this.disableKeyboard = function() {
        keyboard.setKeyPressedHandler(null);
        keyboard.setKeyReleasedHandler(null);
    };

    this.enableKeyboard = function() {
        keyboard.setKeyPressedHandler(
            function (keysym) {
                sendKeyEvent(1, keysym);
            }
        );

        keyboard.setKeyReleasedHandler(
            function (keysym) {
                sendKeyEvent(0, keysym);
            }
        );
    };

    // Enable keyboard by default
    this.enableKeyboard();

    function sendKeyEvent(pressed, keysym) {

        // Do not send requests if not connected
        if (!isConnected())
            return;

        var key_xmlhttprequest = new XMLHttpRequest();
        key_xmlhttprequest.open("GET",
            "key?index=" + (keyIndex++)
                + "&pressed=" + pressed
                + "&keysym=" + keysym);
        key_xmlhttprequest.send(null);
    }

    this.pressKey = function(keysym) {
        sendKeyEvent(1, keysym);
    };

    this.releaseKey = function(keysym) {
        sendKeyEvent(0, keysym);
    };

	/*****************************************/
	/*** Mouse                             ***/
	/*****************************************/

    var mouse = new GuacamoleMouse(display);
    mouse.setButtonPressedHandler(
        function(mouseState) {
            sendMouseState(mouseState);
        }
    );

    mouse.setButtonReleasedHandler(
        function(mouseState) {
            sendMouseState(mouseState);
        }
    );

    mouse.setMovementHandler(
        function(mouseState) {

            // Draw client-side cursor
            if (cursorImage != null) {
                redrawCursor();
            }

            sendMouseState(mouseState);
        }
    );

    var sendingMouseEvents = 0;
    var mouseEventBuffer = "";

    function sendMouseState(mouseState) {

        // Do not send requests if not connected
        if (!isConnected())
            return;

        // Add event to queue, restart send loop if finished.
        if (mouseEventBuffer.length > 0) mouseEventBuffer += "&";
        mouseEventBuffer += "event=" + mouseState.toString();
        if (sendingMouseEvents == 0)
            sendPendingMouseEvents();

    }

    function sendPendingMouseEvents() {

        // Do not send requests if not connected
        if (!isConnected())
            return;

        if (mouseEventBuffer.length > 0) {

            sendingMouseEvents = 1;

            var mouse_xmlhttprequest = new XMLHttpRequest();
            mouse_xmlhttprequest.open("GET", "pointer?" + mouseEventBuffer);
            mouseEventBuffer = ""; // Clear buffer

            // Once response received, send next queued event.
            mouse_xmlhttprequest.onreadystatechange = function() {
                if (mouse_xmlhttprequest.readyState == 4)
                    sendPendingMouseEvents();
            }

            mouse_xmlhttprequest.send(null);
        }
        else
            sendingMouseEvents = 0;

    }

	/*****************************************/
	/*** Clipboard                         ***/
	/*****************************************/

    this.setClipboard = function(data) {

        // Do not send requests if not connected
        if (!isConnected())
            return;

        var clipboard_xmlhttprequest = new XMLHttpRequest();
        clipboard_xmlhttprequest.open("POST", "clipboard");
        clipboard_xmlhttprequest.send(data);

    }

    function desaturateFilter(data, width, height) {

        for (var i=0; i<data.length; i+=4) {

            // Get RGB values
            var r = data[i];
            var g = data[i+1];
            var b = data[i+2];

            // Desaturate
            var v = Math.max(r, g, b) / 2;
            data[i]   = v;
            data[i+1] = v;
            data[i+2] = v;

        }

    }

    var errorHandler = null;
    this.setErrorHandler = function(handler) {
        errorHandler = handler;
    };

    var errorEncountered = 0;
    function showError(error) {
        // Only display first error (avoid infinite error loops)
        if (errorEncountered == 0) {
            errorEncountered = 1;

            disconnect();

            // In case nothing has been rendered yet, use error style
            display.className += " error";

            // Show error by desaturating display
            if (background)
                background.filter(desaturateFilter);

            if (errorHandler)
                errorHandler(error);
        }
    }

    function handleErrors(message) {
        var errors = message.getErrors();
        for (var errorIndex=0; errorIndex<errors.length; errorIndex++)
            showError(errors[errorIndex].getMessage());
    }

    var clipboardHandler = null;
    var requests = 0;

    this.setClipboardHandler = function(handler) {
        clipboardHandler = handler;
    };

    function handleResponse(xmlhttprequest) {

        var nextRequest = null;

        var instructionStart = 0;
        var startIndex = 0;

        function parseResponse() {

            // Start next request as soon as possible
            if (xmlhttprequest.readyState >= 2 && nextRequest == null)
                nextRequest = makeRequest();

            // Parse stream when data is received and when complete.
            if (xmlhttprequest.readyState == 3 ||
                xmlhttprequest.readyState == 4) {

                // Halt on error during request
                if (xmlhttprequest.status == 0) {
                    showError("Request canceled by browser.");
                    return;
                }
                else if (xmlhttprequest.status != 200) {
                    showError("Error during request (HTTP " + xmlhttprequest.status + "): " + xmlhttprequest.statusText);
                    return;
                }

                var current = xmlhttprequest.responseText;
                var instructionEnd;

                while ((instructionEnd = current.indexOf(";", startIndex)) != -1) {

                    // Start next search at next instruction
                    startIndex = instructionEnd+1;

                    var instruction = current.substr(instructionStart,
                            instructionEnd - instructionStart);

                    instructionStart = startIndex;

                    var opcodeEnd = instruction.indexOf(":");

                    var opcode;
                    var parameters;
                    if (opcodeEnd == -1) {
                        opcode = instruction;
                        parameters = new Array();
                    }
                    else {
                        opcode = instruction.substr(0, opcodeEnd);
                        parameters = instruction.substr(opcodeEnd+1).split(",");
                    }

                    // If we're done parsing, handle the next response.
                    if (opcode.length == 0) {

                        if (isConnected()) {
                            delete xmlhttprequest;
                            if (nextRequest)
                                handleResponse(nextRequest);
                        }

                        break;
                    }

                    // Call instruction handler.
                    doInstruction(opcode, parameters);
                }

                // Start search at end of string.
                startIndex = current.length;

                delete instruction;
                delete parameters;

            }

        }

        xmlhttprequest.onreadystatechange = parseResponse;
        parseResponse();

    }

    var seq = 0;

    function makeRequest() {

        // Download self
        var xmlhttprequest = new XMLHttpRequest();
        xmlhttprequest.open("GET", "instructions?seq=" + (seq++));

        xmlhttprequest.send(null);
        return xmlhttprequest;

    }

    function unescapeGuacamoleString(str) {

        var unescapedString = "";

        for (var i=0; i<str.length; i++) {

            var c = str.charAt(i);
            if (c == "\\" && i<str.length-1) {

                var escapeChar = str.charAt(++i);
                if (escapeChar == "c")
                    unescapedString += ",";
                else if (escapeChar == "s")
                    unescapedString += ";";
                else if (escapeChar == "\\")
                    unescapedString += "\\";
                else
                    unescapedString += "\\" + escapeChar;

            }
            else
                unescapedString += c;

        }

        return unescapedString;

    }

    var instructionHandlers = {

        "error": function(parameters) {
            showError(unescapeGuacamoleString(parameters[0]));
        },

        "name": function(parameters) {
            document.title = "Guacamole (" + unescapeGuacamoleString(parameters[0]) + ")";
        },

        "clipboard": function(parameters) {
            clipboardHandler(unescapeGuacamoleString(parameters[0]));
        },

        "size": function(parameters) {

            var width = parseInt(parameters[0]);
            var height = parseInt(parameters[1]);

            // Update (set) display size
            if (display && (background == null || cursor == null)) {
                display.style.width = width + "px";
                display.style.height = height + "px";

                background = new Layer(width, height);
                cursor = new Layer(width, height);

                display.appendChild(background);
                display.appendChild(cursor);
            }

        },

        "rect": function(parameters) {

            var x = parseInt(parameters[0]);
            var y = parseInt(parameters[1]);
            var w = parseInt(parameters[2]);
            var h = parseInt(parameters[3]);
            var color = parameters[4];

            background.drawRect(
                x,
                y,
                w,
                h,
                color
            );

        },

        "png": function(parameters) {

            var x = parseInt(parameters[0]);
            var y = parseInt(parameters[1]);
            var data = parameters[2];

            background.draw(
                x,
                y,
                "data:image/png;base64," + data
            );

            // If received first update, no longer waiting.
            if (currentState == STATE_WAITING)
                setState(STATE_CONNECTED);

        },

        "copy": function(parameters) {

            var srcX = parseInt(parameters[0]);
            var srcY = parseInt(parameters[1]);
            var srcWidth = parseInt(parameters[2]);
            var srcHeight = parseInt(parameters[3]);
            var dstX = parseInt(parameters[4]);
            var dstY = parseInt(parameters[5]);

            background.copyRect(
                srcX,
                srcY,
                srcWidth,
                srcHeight,
                dstX,
                dstY
            );

        },

        "cursor": function(parameters) {

            var x = parseInt(parameters[0]);
            var y = parseInt(parameters[1]);
            var data = parameters[2];

            // Start cursor image load
            var image = new Image();
            image.onload = function() {
                cursorImage = image;
                cursorHotspotX = x;
                cursorHotspotY = y;
                redrawCursor();
            };
            image.src = "data:image/png;base64," + data

        }

    };

    function doInstruction(opcode, parameters) {

        var handler = instructionHandlers[opcode];
        if (handler)
            handler(parameters);

    }

    this.connect = function() {

        // Attempt connection
        var connect_xmlhttprequest = new XMLHttpRequest();
        connect_xmlhttprequest.open("GET", "connect", false);

        setState(STATE_CONNECTING);
        connect_xmlhttprequest.send(null);

        // Handle result (and check for errors)
        var message = new GuacamoleMessage(connect_xmlhttprequest.responseXML);
        if (!message.hasErrors()) {
            setState(STATE_WAITING);
            handleResponse(makeRequest());
        }
        else
            handleErrors(message);

    };

    function disconnect() {

        // Only attempt disconnection not disconnected.
        if (currentState != STATE_DISCONNECTED
                && currentState != STATE_DISCONNECTING) {

            setState(STATE_DISCONNECTING);

            // Attempt disdisconnection
            var disconnect_xmlhttprequest = new XMLHttpRequest();
            disconnect_xmlhttprequest.open("GET", "disconnect", false);
            disconnect_xmlhttprequest.send(null);

            // Handle result (and check for errors)
            var message = new GuacamoleMessage(disconnect_xmlhttprequest.responseXML);
            handleErrors(message);

            setState(STATE_DISCONNECTED);
        }

    }

    this.disconnect = disconnect;

}
<commit_msg>Using POST instead of GET + seq<commit_after>
/*
 *  Guacamole - Pure JavaScript/HTML VNC Client
 *  Copyright (C) 2010  Michael Jumper
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  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/>.
 */

function VNCClient(display) {

    var STATE_IDLE          = 0;
    var STATE_CONNECTING    = 1;
    var STATE_WAITING       = 2;
    var STATE_CONNECTED     = 3;
    var STATE_DISCONNECTING = 4;
    var STATE_DISCONNECTED  = 5;

    var currentState = STATE_IDLE;
    var stateChangeHandler = null;

    function setState(state) {
        if (state != currentState) {
            currentState = state;
            if (stateChangeHandler)
                stateChangeHandler(currentState);
        }
    }

    this.setOnStateChangeHandler = function(handler) {
        stateChangeHandler = handler;
    }

    function isConnected() {
        return currentState == STATE_CONNECTED
            || currentState == STATE_WAITING;
    }

    var keyIndex = 0;
    var xmlIndex = 0;

    // Layers
    var background = null;
    var cursor = null;

    var cursorImage = null;
    var cursorHotspotX = 0;
    var cursorHotspotY = 0;

    // FIXME: Make object. Clean up.
    var cursorRectX = 0;
    var cursorRectY = 0;
    var cursorRectW = 0;
    var cursorRectH = 0;

    var cursorHidden = 0;

    function redrawCursor() {

        // Hide hardware cursor
        if (cursorHidden == 0) {
            display.className += " hideCursor";
            cursorHidden = 1;
        }

        // Erase old cursor
        cursor.clearRect(cursorRectX, cursorRectY, cursorRectW, cursorRectH);

        // Update rect
        cursorRectX = mouse.getX() - cursorHotspotX;
        cursorRectY = mouse.getY() - cursorHotspotY;
        cursorRectW = cursorImage.width;
        cursorRectH = cursorImage.height;

        // Draw new cursor
        cursor.drawImage(cursorRectX, cursorRectY, cursorImage);
    }

	/*****************************************/
	/*** Keyboard                          ***/
	/*****************************************/

    var keyboard = new GuacamoleKeyboard(document);

    this.disableKeyboard = function() {
        keyboard.setKeyPressedHandler(null);
        keyboard.setKeyReleasedHandler(null);
    };

    this.enableKeyboard = function() {
        keyboard.setKeyPressedHandler(
            function (keysym) {
                sendKeyEvent(1, keysym);
            }
        );

        keyboard.setKeyReleasedHandler(
            function (keysym) {
                sendKeyEvent(0, keysym);
            }
        );
    };

    // Enable keyboard by default
    this.enableKeyboard();

    function sendKeyEvent(pressed, keysym) {

        // Do not send requests if not connected
        if (!isConnected())
            return;

        var key_xmlhttprequest = new XMLHttpRequest();
        key_xmlhttprequest.open("GET",
            "key?index=" + (keyIndex++)
                + "&pressed=" + pressed
                + "&keysym=" + keysym);
        key_xmlhttprequest.send(null);
    }

    this.pressKey = function(keysym) {
        sendKeyEvent(1, keysym);
    };

    this.releaseKey = function(keysym) {
        sendKeyEvent(0, keysym);
    };

	/*****************************************/
	/*** Mouse                             ***/
	/*****************************************/

    var mouse = new GuacamoleMouse(display);
    mouse.setButtonPressedHandler(
        function(mouseState) {
            sendMouseState(mouseState);
        }
    );

    mouse.setButtonReleasedHandler(
        function(mouseState) {
            sendMouseState(mouseState);
        }
    );

    mouse.setMovementHandler(
        function(mouseState) {

            // Draw client-side cursor
            if (cursorImage != null) {
                redrawCursor();
            }

            sendMouseState(mouseState);
        }
    );

    var sendingMouseEvents = 0;
    var mouseEventBuffer = "";

    function sendMouseState(mouseState) {

        // Do not send requests if not connected
        if (!isConnected())
            return;

        // Add event to queue, restart send loop if finished.
        if (mouseEventBuffer.length > 0) mouseEventBuffer += "&";
        mouseEventBuffer += "event=" + mouseState.toString();
        if (sendingMouseEvents == 0)
            sendPendingMouseEvents();

    }

    function sendPendingMouseEvents() {

        // Do not send requests if not connected
        if (!isConnected())
            return;

        if (mouseEventBuffer.length > 0) {

            sendingMouseEvents = 1;

            var mouse_xmlhttprequest = new XMLHttpRequest();
            mouse_xmlhttprequest.open("GET", "pointer?" + mouseEventBuffer);
            mouseEventBuffer = ""; // Clear buffer

            // Once response received, send next queued event.
            mouse_xmlhttprequest.onreadystatechange = function() {
                if (mouse_xmlhttprequest.readyState == 4)
                    sendPendingMouseEvents();
            }

            mouse_xmlhttprequest.send(null);
        }
        else
            sendingMouseEvents = 0;

    }

	/*****************************************/
	/*** Clipboard                         ***/
	/*****************************************/

    this.setClipboard = function(data) {

        // Do not send requests if not connected
        if (!isConnected())
            return;

        var clipboard_xmlhttprequest = new XMLHttpRequest();
        clipboard_xmlhttprequest.open("POST", "clipboard");
        clipboard_xmlhttprequest.send(data);

    }

    function desaturateFilter(data, width, height) {

        for (var i=0; i<data.length; i+=4) {

            // Get RGB values
            var r = data[i];
            var g = data[i+1];
            var b = data[i+2];

            // Desaturate
            var v = Math.max(r, g, b) / 2;
            data[i]   = v;
            data[i+1] = v;
            data[i+2] = v;

        }

    }

    var errorHandler = null;
    this.setErrorHandler = function(handler) {
        errorHandler = handler;
    };

    var errorEncountered = 0;
    function showError(error) {
        // Only display first error (avoid infinite error loops)
        if (errorEncountered == 0) {
            errorEncountered = 1;

            disconnect();

            // In case nothing has been rendered yet, use error style
            display.className += " error";

            // Show error by desaturating display
            if (background)
                background.filter(desaturateFilter);

            if (errorHandler)
                errorHandler(error);
        }
    }

    function handleErrors(message) {
        var errors = message.getErrors();
        for (var errorIndex=0; errorIndex<errors.length; errorIndex++)
            showError(errors[errorIndex].getMessage());
    }

    var clipboardHandler = null;
    var requests = 0;

    this.setClipboardHandler = function(handler) {
        clipboardHandler = handler;
    };

    function handleResponse(xmlhttprequest) {

        var nextRequest = null;

        var instructionStart = 0;
        var startIndex = 0;

        function parseResponse() {

            // Start next request as soon as possible
            if (xmlhttprequest.readyState >= 2 && nextRequest == null)
                nextRequest = makeRequest();

            // Parse stream when data is received and when complete.
            if (xmlhttprequest.readyState == 3 ||
                xmlhttprequest.readyState == 4) {

                // Halt on error during request
                if (xmlhttprequest.status == 0) {
                    showError("Request canceled by browser.");
                    return;
                }
                else if (xmlhttprequest.status != 200) {
                    showError("Error during request (HTTP " + xmlhttprequest.status + "): " + xmlhttprequest.statusText);
                    return;
                }

                var current = xmlhttprequest.responseText;
                var instructionEnd;

                while ((instructionEnd = current.indexOf(";", startIndex)) != -1) {

                    // Start next search at next instruction
                    startIndex = instructionEnd+1;

                    var instruction = current.substr(instructionStart,
                            instructionEnd - instructionStart);

                    instructionStart = startIndex;

                    var opcodeEnd = instruction.indexOf(":");

                    var opcode;
                    var parameters;
                    if (opcodeEnd == -1) {
                        opcode = instruction;
                        parameters = new Array();
                    }
                    else {
                        opcode = instruction.substr(0, opcodeEnd);
                        parameters = instruction.substr(opcodeEnd+1).split(",");
                    }

                    // If we're done parsing, handle the next response.
                    if (opcode.length == 0) {

                        if (isConnected()) {
                            delete xmlhttprequest;
                            if (nextRequest)
                                handleResponse(nextRequest);
                        }

                        break;
                    }

                    // Call instruction handler.
                    doInstruction(opcode, parameters);
                }

                // Start search at end of string.
                startIndex = current.length;

                delete instruction;
                delete parameters;

            }

        }

        xmlhttprequest.onreadystatechange = parseResponse;
        parseResponse();

    }

    function makeRequest() {

        // Download self
        var xmlhttprequest = new XMLHttpRequest();
        xmlhttprequest.open("POST", "instructions");
        xmlhttprequest.send(null);

        return xmlhttprequest;

    }

    function unescapeGuacamoleString(str) {

        var unescapedString = "";

        for (var i=0; i<str.length; i++) {

            var c = str.charAt(i);
            if (c == "\\" && i<str.length-1) {

                var escapeChar = str.charAt(++i);
                if (escapeChar == "c")
                    unescapedString += ",";
                else if (escapeChar == "s")
                    unescapedString += ";";
                else if (escapeChar == "\\")
                    unescapedString += "\\";
                else
                    unescapedString += "\\" + escapeChar;

            }
            else
                unescapedString += c;

        }

        return unescapedString;

    }

    var instructionHandlers = {

        "error": function(parameters) {
            showError(unescapeGuacamoleString(parameters[0]));
        },

        "name": function(parameters) {
            document.title = "Guacamole (" + unescapeGuacamoleString(parameters[0]) + ")";
        },

        "clipboard": function(parameters) {
            clipboardHandler(unescapeGuacamoleString(parameters[0]));
        },

        "size": function(parameters) {

            var width = parseInt(parameters[0]);
            var height = parseInt(parameters[1]);

            // Update (set) display size
            if (display && (background == null || cursor == null)) {
                display.style.width = width + "px";
                display.style.height = height + "px";

                background = new Layer(width, height);
                cursor = new Layer(width, height);

                display.appendChild(background);
                display.appendChild(cursor);
            }

        },

        "rect": function(parameters) {

            var x = parseInt(parameters[0]);
            var y = parseInt(parameters[1]);
            var w = parseInt(parameters[2]);
            var h = parseInt(parameters[3]);
            var color = parameters[4];

            background.drawRect(
                x,
                y,
                w,
                h,
                color
            );

        },

        "png": function(parameters) {

            var x = parseInt(parameters[0]);
            var y = parseInt(parameters[1]);
            var data = parameters[2];

            background.draw(
                x,
                y,
                "data:image/png;base64," + data
            );

            // If received first update, no longer waiting.
            if (currentState == STATE_WAITING)
                setState(STATE_CONNECTED);

        },

        "copy": function(parameters) {

            var srcX = parseInt(parameters[0]);
            var srcY = parseInt(parameters[1]);
            var srcWidth = parseInt(parameters[2]);
            var srcHeight = parseInt(parameters[3]);
            var dstX = parseInt(parameters[4]);
            var dstY = parseInt(parameters[5]);

            background.copyRect(
                srcX,
                srcY,
                srcWidth,
                srcHeight,
                dstX,
                dstY
            );

        },

        "cursor": function(parameters) {

            var x = parseInt(parameters[0]);
            var y = parseInt(parameters[1]);
            var data = parameters[2];

            // Start cursor image load
            var image = new Image();
            image.onload = function() {
                cursorImage = image;
                cursorHotspotX = x;
                cursorHotspotY = y;
                redrawCursor();
            };
            image.src = "data:image/png;base64," + data

        }

    };

    function doInstruction(opcode, parameters) {

        var handler = instructionHandlers[opcode];
        if (handler)
            handler(parameters);

    }

    this.connect = function() {

        // Attempt connection
        var connect_xmlhttprequest = new XMLHttpRequest();
        connect_xmlhttprequest.open("GET", "connect", false);

        setState(STATE_CONNECTING);
        connect_xmlhttprequest.send(null);

        // Handle result (and check for errors)
        var message = new GuacamoleMessage(connect_xmlhttprequest.responseXML);
        if (!message.hasErrors()) {
            setState(STATE_WAITING);
            handleResponse(makeRequest());
        }
        else
            handleErrors(message);

    };

    function disconnect() {

        // Only attempt disconnection not disconnected.
        if (currentState != STATE_DISCONNECTED
                && currentState != STATE_DISCONNECTING) {

            setState(STATE_DISCONNECTING);

            // Attempt disdisconnection
            var disconnect_xmlhttprequest = new XMLHttpRequest();
            disconnect_xmlhttprequest.open("GET", "disconnect", false);
            disconnect_xmlhttprequest.send(null);

            // Handle result (and check for errors)
            var message = new GuacamoleMessage(disconnect_xmlhttprequest.responseXML);
            handleErrors(message);

            setState(STATE_DISCONNECTED);
        }

    }

    this.disconnect = disconnect;

}
<|endoftext|>


<commit_before>var _ = require('underscore'),
	querystring = require('querystring'),
	utils = require('./utils'),
	validDate = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/,
	moment = require('moment-timezone');

function requestOptions(action, params) {
	var url = null,
		number,
		body = {},
		query = {},
		options = { headers: { 'User-Agent': 'SemaphoreJS ' + this.version }, method: 'GET' };

	if (!this.get('api key')) {
		this.lastError = new Error('Missing Semaphore API key');
		return null;
	}

	if (!action) {
		this.lastError = new Error('Action parameters is required');
		return null;
	}

	if (!_.isString(action)) {
		this.lastError = new Error('Action parameter must be a string');
		return null;
	}

	if (params && !_.isObject(params)) {
		this.lastError = new Error('Options parameter must be an object');
		return null;
	}

	query.api = this.get('api key');

	switch (action) {
		case 'sms': {
			if (!params) {
				this.lastError = new Error('Mobile number and message are required');
				return null;
			}

			if (!params.number) {
				this.lastError = new Error('Mobile number is required');
				return null;
			}
			number = utils.validateNumber(params.number);
			if (!number) {
				this.lastError = new Error('Mobile number is not valid');
				return null;
			}
			if (!params.message) {
				this.lastError = new Error('Message is required');
				return null;
			}
			if (!_.isString(params.message)) {
				this.lastError = new Error('Message must be a string');
				return null;
			}
			if (params.message.length < 3) {
				this.lastError = new Error('Message is too short. Must be at least 3 characters');
				return null;
			}
			if (params.from && !_.isString(params.from)) {
				this.lastError = new Error('From must be a string');
				return null;
			}

			body.number = number;
			body.message = params.message;
			if (params.from || this.get('from')) {
				body.from = params.from || this.get('from');
			}

			options.method = 'POST';
			options.body = querystring.stringify(body);

			url = this._url.sms;
			break;
		}

		case 'messages': {
			if (params && params.page && !_.isNumber(params.page)) {
				this.lastError = new Error('Page must be a number');
				return null;
			}

			if (params && params.page < 1) {
				this.lastError = new Error('Page must be greater than 0');
				return null;
			}

			if (params && params.page > 1) {
				query.page = params.page;
			}
			url = this._url.messages;
			break;
		}

		case 'period': {
			if (!params || !params.startsAt || !params.endsAt) {
				this.lastError = new Error('"startsAt" and "endsAt" dates are required');
				return null;
			}

			if (!_.isString(params.startsAt) || !validDate.test(params.startsAt)) {
				this.lastError = new Error('"startsAt" must be a valid ISO date');
				return null;
			}

			if (!_.isString(params.endsAt) || !validDate.test(params.endsAt)) {
				this.lastError = new Error('"endsAt" must be a valid ISO date');
				return null;
			}

			query.starts_at = moment.tz(params.startsAt, 'Asia/Manila').unix();
			query.ends_at = moment.tz(params.endsAt, 'Asia/Manila').unix();

			if (query.starts_at > query.ends_at) {
				this.lastError = new Error('"endsAt" date must occur after "startsAt" date');
				return null;
			}

			url = this._url.period;
			break;
		}

		case 'network': {
			if (!params || !params.telco) {
				this.lastError = new Error('Network name is required');
				return null;
			}
			if (!_.isString(params.telco)) {
				this.lastError = new Error('Network name must be a string');
				return null;
			}
			if (!_.includes(this._networks, params.telco)) {
				this.lastError = new Error('Network must be one of the following: ' + this._networks.join(', '));
				return null;
			}

			query.telco = params.telco;
			url = this._url.network;
			break;
		}

		case 'account': {
			url = this._url.account;
			break;
		}

		default: {
			this.lastError = new Error('Invalid Semaphore API action');
			return null;
		}
	}

	options.url = url + '?' + querystring.stringify(query);

	return options;
}

exports = module.exports = requestOptions;
<commit_msg>refactored requestOptions method to reduce complexity<commit_after>var _ = require('underscore'),
	querystring = require('querystring'),
	utils = require('./utils'),
	validDate = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/,
	moment = require('moment-timezone');

function paramsValid(action, params) {
	if (!this.get('api key')) {
		this.lastError = new Error('Missing Semaphore API key');
		return false;
	}

	if (!action) {
		this.lastError = new Error('Action parameters is required');
		return false;
	}

	if (!_.isString(action)) {
		this.lastError = new Error('Action parameter must be a string');
		return false;
	}

	if (params && !_.isObject(params)) {
		this.lastError = new Error('Options parameter must be an object');
		return false;
	}

	return true;
}

function smsOptions(params, options) {
	var body = {}, number;

	if (!params) {
		this.lastError = new Error('Mobile number and message are required');
		return false;
	}

	if (!params.number) {
		this.lastError = new Error('Mobile number is required');
		return false;
	}
	number = utils.validateNumber(params.number);
	if (!number) {
		this.lastError = new Error('Mobile number is not valid');
		return false;
	}
	if (!params.message) {
		this.lastError = new Error('Message is required');
		return false;
	}
	if (!_.isString(params.message)) {
		this.lastError = new Error('Message must be a string');
		return false;
	}
	if (params.message.length < 3) {
		this.lastError = new Error('Message is too short. Must be at least 3 characters');
		return false;
	}
	if (params.from && !_.isString(params.from)) {
		this.lastError = new Error('From must be a string');
		return false;
	}

	body.number = number;
	body.message = params.message;
	if (params.from || this.get('from')) {
		body.from = params.from || this.get('from');
	}

	options.method = 'POST';
	options.body = querystring.stringify(body);

	return true;
}

function messagesOptions(params, query) {
	if (params && params.page && !_.isNumber(params.page)) {
		this.lastError = new Error('Page must be a number');
		return false;
	}

	if (params && params.page < 1) {
		this.lastError = new Error('Page must be greater than 0');
		return false;
	}

	if (params && params.page > 1) {
		query.page = params.page;
	}

	return true;
}

function periodOptions(params, query) {
	if (!params || !params.startsAt || !params.endsAt) {
		this.lastError = new Error('"startsAt" and "endsAt" dates are required');
		return false;
	}

	if (!_.isString(params.startsAt) || !validDate.test(params.startsAt)) {
		this.lastError = new Error('"startsAt" must be a valid ISO date');
		return false;
	}

	if (!_.isString(params.endsAt) || !validDate.test(params.endsAt)) {
		this.lastError = new Error('"endsAt" must be a valid ISO date');
		return false;;
	}

	query.starts_at = moment.tz(params.startsAt, 'Asia/Manila').unix();
	query.ends_at = moment.tz(params.endsAt, 'Asia/Manila').unix();

	if (query.starts_at > query.ends_at) {
		this.lastError = new Error('"endsAt" date must occur after "startsAt" date');
		return false;
	}

	return true
}

function networkOptions(params, query) {
	if (!params || !params.telco) {
		this.lastError = new Error('Network name is required');
		return false;
	}
	if (!_.isString(params.telco)) {
		this.lastError = new Error('Network name must be a string');
		return false;
	}
	if (!_.includes(this._networks, params.telco)) {
		this.lastError = new Error('Network must be one of the following: ' + this._networks.join(', '));
		return false;
	}

	query.telco = params.telco;
	return true;
}

function requestOptions(action, params) {
	var url = null,
		query = {},
		options = { headers: { 'User-Agent': 'SemaphoreJS ' + this.version }, method: 'GET' };

	if (!paramsValid.call(this, action, params)) {
		return null;
	}

	query.api = this.get('api key');

	switch (action) {
		case 'sms': {
			if (!smsOptions.call(this, params, options)) {
				return null;
			}
			url = this._url.sms;
			break;
		}

		case 'messages': {
			if (!messagesOptions.call(this, params, query)) {
				return null;
			}
			url = this._url.messages;
			break;
		}

		case 'period': {
			if (!periodOptions.call(this, params, query)) {
				return null;
			}
			url = this._url.period;
			break;
		}

		case 'network': {
			if (!networkOptions.call(this, params, query)) {
				return null;
			}
			url = this._url.network;
			break;
		}

		case 'account': {
			url = this._url.account;
			break;
		}

		default: {
			this.lastError = new Error('Invalid Semaphore API action');
			return null;
		}
	}

	options.url = url + '?' + querystring.stringify(query);

	return options;
}

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


<commit_before>import { h, Component } 				from 'preact/preact';
import ShallowCompare	 				from 'shallow-compare/index';

import $JammerTV						from 'external/jammertv/jammertv';

import SVGIcon 							from 'com/svg-icon/icon';
import NavSpinner						from 'com/nav-spinner/spinner';
import IMG	 							from 'com/img2/img2';

export default class SidebarTV extends Component {
	constructor( props ) {
		super(props);

		this.state = {
			active: 0,
			streams: []
		};

		this.services = [
			'null',
			'twitch',
			'youtube',
		];

		this.serviceIcons = [
			(
),						// Null //
			(<SVGIcon>twitch</SVGIcon>),	// Twitch //
			(<SVGIcon>youtube</SVGIcon>),	// YouTube //
			(

),
			(

),
			(

),
			(

),
		];

		this.FailImage = '//'+STATIC_DOMAIN+'/other/asset/TVFail.png';

		this.refreshStreams = this.refreshStreams.bind(this);
	}

	loadStreams() {
		var NewStreams = [];

		// Fetch Ludum Dare streams first
		return $JammerTV.GetLive([
			'ludum-dare',
			'ludum-dare-art',
			'ludum-dare-music',
			'ludum-dare-craft',
			'ludum-dare-play',
			'ludum-dare-talk'
		])
		// Fetch Game Jam streams next (if we don't have enough)
		.then(data => {
			// Parse Data
			if ( data && Array.isArray(data.streams) ) {
				NewStreams = NewStreams.concat(data.streams);
			}

			// hack
			//this.state.streams = this.state.streams.concat(data.streams);

			// Fetch more (if needed)
			if ( NewStreams.length < 3 ) {

//			if ( this.state.streams.length < 3 ) {
				return $JammerTV.GetLive([
					'game-jam',
					'game-jam-art',
					'game-jam-music',
				]);
			}
		})
		// Fetch Game Dev streams last (if we didn't have enough)
		.then(data => {
			// Parse data
			if ( data && Array.isArray(data.streams) ) {
				NewStreams = NewStreams.concat(data.streams);
			}

			// Fetch more (if needed)
			if ( NewStreams.length < 3 ) {
				return $JammerTV.GetLive([
					'game-dev',
					'game-art',
					'game-music'
				]);
			}
		})
		// Wrap up
		.then(data => {
			// Parse Data
			if ( data && Array.isArray(data.streams) ) {
				NewStreams = NewStreams.concat(data.streams);
			}

			// Populate state with streams
			this.setState({
				'streams': NewStreams
			});
		})

//				.then(data2 => {
//					this.setState({
//						'streams': this.state.streams.concat(data2.streams)
//					});
//				})
//				.catch(err => {
//					console.log("sidebar-tv2:",err);
//					this.setState({
//						'error': err
//					});
//					return err;
//				});
//			}
//			else {
//				this.setState({
//					'streams': this.state.streams
//				});
//			}
//
//			return data;
//		})
		.catch(err => {
			console.log("sidebar-tv:",err);
			this.setState({
				error: err
			});
			return err;
		});
	}

	// Called every few minutes, to make sure stream list is fresh
	refreshStreams() {
//		// But only if the window is visible
//		if ( !document.hidden ) {
//			console.log("Streams Refreshed: "+Date.now());
//			this.loadStreams().then(function() {
//				console.log("Queued");
//				this.timer = setTimeout(function() {
//					refreshStreams();
//				}, 2*60*1000);
//			}
//		}
//		else {
//			console.log("Hidden Queue");
//			this.timer = setTimeout(function() {
//				refreshStreams();
//			}, 2*60*1000);
//		}
	}

//	shouldComponentUpdate( nextProps, nextState ) {
//		var com = ShallowCompare(this, nextProps, nextState);
//		console.log("SideBarTV",com,this.state, nextState);
//		return com;
//	}

	componentDidMount() {
//		console.log("SideBarTV: componentDidMount");
		this.loadStreams();
	}

	componentWillUnmount() {
//		console.log("SideBarTV: componentWillUnmount");
	}

	setActive( id, e ) {
		this.setState({ active: id });
	}

	showOthers( others, active ) {
		return others.map( function(other, index) {
			if (other === active) {
				return (

				);
			}
			else {
				return (

				);
			}
		}.bind(this));
	}

	render( props, state ) {
		if ( state.error ) {
			return (

						{""+state.error}

			);
		}
		else if ( state.streams.length == 0 ) {
			return (

					<NavSpinner />

			);
		}
		else {
			let active = state.streams[state.active];
			let others = [
				state.streams[0],
				state.streams[1],
				state.streams[2],
			];

			return (

 {
							console.log('tv');
							/*window.open("https://www.twitch.tv/directory/game/Creative/ldjam", '_blank');*/
							window.location.hash = "#tv/"+this.services[active.service_id]+'/'+active.meta.name;
						}}>

<SVGIcon baseline small>circle</SVGIcon> LIVE

{this.serviceIcons[active.service_id]} {active.meta.name}

<SVGIcon baseline>tv</SVGIcon> {active.viewers}

<SVGIcon>play</SVGIcon>

						<SVGIcon top>quotes-left</SVGIcon>
						<SVGIcon bottom>quotes-right</SVGIcon>

{active.meta.status}

						{this.showOthers(others,active)}

			);
		}
	}
}
<commit_msg>Now autorefreshing live streams<commit_after>import { h, Component } 				from 'preact/preact';
import ShallowCompare	 				from 'shallow-compare/index';

import $JammerTV						from 'external/jammertv/jammertv';

import SVGIcon 							from 'com/svg-icon/icon';
import NavSpinner						from 'com/nav-spinner/spinner';
import IMG	 							from 'com/img2/img2';

export default class SidebarTV extends Component {
	constructor( props ) {
		super(props);

		this.state = {
			active: 0,
			streams: []
		};

		this.services = [
			'null',
			'twitch',
			'youtube',
		];

		this.serviceIcons = [
			(
),						// Null //
			(<SVGIcon>twitch</SVGIcon>),	// Twitch //
			(<SVGIcon>youtube</SVGIcon>),	// YouTube //
			(

),
			(

),
			(

),
			(

),
		];

		this.FailImage = '//'+STATIC_DOMAIN+'/other/asset/TVFail.png';

		this.refreshStreams = this.refreshStreams.bind(this);
	}

	loadStreams() {
		var NewStreams = [];

		// Fetch Ludum Dare streams first
		return $JammerTV.GetLive([
			'ludum-dare',
			'ludum-dare-art',
			'ludum-dare-music',
			'ludum-dare-craft',
			'ludum-dare-play',
			'ludum-dare-talk'
		])
		// Fetch Game Jam streams next (if we don't have enough)
		.then(data => {
			// Parse Data
			if ( data && Array.isArray(data.streams) ) {
				NewStreams = NewStreams.concat(data.streams);
			}

			// Fetch more (if needed)
			if ( NewStreams.length < 10 ) {
				return $JammerTV.GetLive([
					'game-jam',
					'game-jam-art',
					'game-jam-music',
				]);
			}
			return null;
		})
		// Fetch Game Dev streams last (if we didn't have enough)
		.then(data => {
			// Parse data
			if ( data && Array.isArray(data.streams) ) {
				NewStreams = NewStreams.concat(data.streams);
			}

			// Fetch more (if needed)
			if ( NewStreams.length < 10 ) {
				return $JammerTV.GetLive([
					'game-dev',
					'game-art',
					'game-music'
				]);
			}
			return null;
		})
		// Wrap up
		.then(data => {
			// Parse Data
			if ( data && Array.isArray(data.streams) ) {
				NewStreams = NewStreams.concat(data.streams);
			}

			// Populate state with streams
			this.setState({
				'streams': NewStreams
			});
			return null;
		})
		.catch(err => {
			// Error state
			this.setState({
				'error': err
			});
			return err;
		});
	}

	// Called every few minutes, to make sure stream list is fresh
	refreshStreams() {
		// TODO: Raise this, once JammerTV caching is correctly supported
		var StreamRefreshRate = 3*60*1000;

		// But only if the window is visible
		if ( !document.hidden ) {
			//console.log("Streams Refreshed: "+Date.now());

			this.loadStreams().then(function() {
				//console.log("Queued");

				this.timer = setTimeout(function() {
					this.refreshStreams();
				}.bind(this), StreamRefreshRate);
			}.bind(this));
		}
		else {
			//console.log("Hidden Queue");

			this.timer = setTimeout(function() {
				this.refreshStreams();
			}.bind(this), StreamRefreshRate);
		}
	}

//	shouldComponentUpdate( nextProps, nextState ) {
//		var com = ShallowCompare(this, nextProps, nextState);
//		console.log("SideBarTV",com,this.state, nextState);
//		return com;
//	}

	componentDidMount() {
//		console.log("SideBarTV: componentDidMount");
		this.refreshStreams();
	}

	componentWillUnmount() {
//		console.log("SideBarTV: componentWillUnmount");
	}

	setActive( id, e ) {
		this.setState({ active: id });
	}

	showOthers( others, active ) {
		return others.map( function(other, index) {
			if (other === active) {
				return (

				);
			}
			else {
				return (

				);
			}
		}.bind(this));
	}

	render( props, state ) {
		if ( state.error ) {
			return (

						{""+state.error}

			);
		}
		else if ( state.streams.length == 0 ) {
			return (

					<NavSpinner />

			);
		}
		else {
			let active = state.streams[state.active];
			let others = [
				state.streams[0],
				state.streams[1],
				state.streams[2],
			];

			return (

 {
							console.log('tv');
							/*window.open("https://www.twitch.tv/directory/game/Creative/ldjam", '_blank');*/
							window.location.hash = "#tv/"+this.services[active.service_id]+'/'+active.meta.name;
						}}>

<SVGIcon baseline small>circle</SVGIcon> LIVE

{this.serviceIcons[active.service_id]} {active.meta.name}

<SVGIcon baseline>tv</SVGIcon> {active.viewers}

<SVGIcon>play</SVGIcon>

						<SVGIcon top>quotes-left</SVGIcon>
						<SVGIcon bottom>quotes-right</SVGIcon>

{active.meta.status}

						{this.showOthers(others,active)}

			);
		}
	}
}
<|endoftext|>


<commit_before>var EventEmitter = require('events');
var util = require('util');

module.exports = ResourceLoader;

// TODO: Implement custom class for objects as they are much more complex.
var IMAGE_FIELDS = {
  '.material': ['frames', 'platformImage', 'stairImage'],
  '.matmod': ['frames']
};

var IMAGE_FIELD_SUFFIX = '__image';

function ResourceLoader(assetsManager, extension) {
  EventEmitter.call(this);

  this.assets = assetsManager;
  this.extension = extension;

  this.index = null;
  this.images = null;

  this._loadingIndex = false;
  this._pending = [];
  this._emitting = false;
}
util.inherits(ResourceLoader, EventEmitter);

ResourceLoader.prototype.get = function (id) {
  if (!this.index) return null;
  return this.index[id] || null;
};

ResourceLoader.prototype.getImage = function (idOrResource, fieldName) {
  var resource = typeof idOrResource == 'string' ? this.get(idOrResource) : idOrResource;
  if (!resource) return null;

  return resource[fieldName + IMAGE_FIELD_SUFFIX] || null;
};

ResourceLoader.prototype.loadImages = function (ids) {
  var fields = IMAGE_FIELDS[this.extension];
  if (!fields) return;

  if (this._pending) {
    // Ensure that we're loading the index.
    this.loadIndex();

    // Queue the requested ids for later.
    Array.prototype.push.apply(this._pending, ids);

    return;
  }

  for (var i = 0; i < ids.length; i++) {
    var resource = this.index[ids[i]];
    if (!resource) {
      console.warn('Missing ' + this.extension.substr(1) + ' with id ' + ids[i]);
      continue;
    }

    for (var j = 0; j < fields.length; j++) {
      this._loadImage(resource, fields[j]);
    }
  }
};

ResourceLoader.prototype.loadIndex = function () {
  if (this._loadingIndex) return;
  this._loadingIndex = true;

  // TODO: Fat arrows.
  var self = this;
  this.assets.loadResources(this.extension, function (err, index) {
    self._loadingIndex = false;
    self.index = index;

    var pending = self._pending;
    self._pending = null;
    self.loadImages(pending);
  });
};

ResourceLoader.prototype._loadImage = function (resource, field) {
  if (!(field in resource)) return;

  var imageField = field + IMAGE_FIELD_SUFFIX;

  // If the image is already loading, do nothing.
  if (imageField in resource) return;
  resource[imageField] = null;

  // Get an absolute path to the image.
  var path = resource[field];
  if (path[0] != '/') {
    var base = resource.__path__;
    path = base.substr(0, base.lastIndexOf('/') + 1) + path;
  }

  var self = this;
  this.assets.getBlobURL(path, function (err, url) {
    var image = new Image();
    image.src = url;
    image.onload = function () {
      resource[imageField] = image;
      if (!self._emitting) {
        self._emitting = true;
        process.nextTick(function () {
          self.emit('images');
          self._emitting = false;
        });
      }
    };
  });
};
<commit_msg>Improve logic for loading resources<commit_after>var EventEmitter = require('events');
var util = require('util');

module.exports = ResourceLoader;

// TODO: Implement custom class for objects as they are much more complex.
var IMAGE_FIELDS = {
  '.material': ['frames', 'platformImage', 'stairImage'],
  '.matmod': ['frames']
};

var IMAGE_FIELD_SUFFIX = '__image';

function ResourceLoader(assetsManager, extension) {
  EventEmitter.call(this);

  this.assets = assetsManager;
  this.extension = extension;

  this.index = null;
  this.images = null;

  this._loadingIndex = false;
  this._pending = [];
  this._emitting = false;
}
util.inherits(ResourceLoader, EventEmitter);

ResourceLoader.prototype.get = function (id) {
  if (!this.index) return null;
  return this.index[id] || null;
};

ResourceLoader.prototype.getImage = function (idOrResource, fieldName) {
  var resource = typeof idOrResource == 'string' ? this.get(idOrResource) : idOrResource;
  if (!resource) return null;

  return resource[fieldName + IMAGE_FIELD_SUFFIX] || null;
};

ResourceLoader.prototype.loadImages = function (ids) {
  var fields = IMAGE_FIELDS[this.extension];
  if (!fields) return;

  if (!this.index) {
    // Ensure that we're loading the index.
    this.loadIndex();

    // Queue the requested ids for later.
    Array.prototype.push.apply(this._pending, ids);

    return;
  }

  for (var i = 0; i < ids.length; i++) {
    var resource = this.index[ids[i]];
    if (!resource) {
      console.warn('Missing ' + this.extension.substr(1) + ' with id ' + ids[i]);
      continue;
    }

    for (var j = 0; j < fields.length; j++) {
      this._loadImage(resource, fields[j]);
    }
  }
};

ResourceLoader.prototype.loadIndex = function () {
  if (this._loadingIndex) return;
  this._loadingIndex = true;

  // TODO: Fat arrows.
  var self = this;
  this.assets.loadResources(this.extension, function (err, index) {
    self._loadingIndex = false;
    self.index = index;

    self.loadImages(self._pending);
    self._pending = [];
  });
};

ResourceLoader.prototype._loadImage = function (resource, field) {
  if (!(field in resource)) return;

  var imageField = field + IMAGE_FIELD_SUFFIX;

  // If the image is already loading, do nothing.
  if (imageField in resource) return;
  resource[imageField] = null;

  // Get an absolute path to the image.
  var path = resource[field];
  if (path[0] != '/') {
    var base = resource.__path__;
    path = base.substr(0, base.lastIndexOf('/') + 1) + path;
  }

  var self = this;
  this.assets.getBlobURL(path, function (err, url) {
    var image = new Image();
    image.src = url;
    image.onload = function () {
      resource[imageField] = image;
      if (!self._emitting) {
        self._emitting = true;
        process.nextTick(function () {
          self.emit('images');
          self._emitting = false;
        });
      }
    };
  });
};
<|endoftext|>


<commit_before>
const core = require('./core'),
  nodeGetoptLong = require('node-getopt-long'),
  P = require('bluebird'),
  config = require('./config'),
  readline = require('readline'),
  logger = require('./logger')

const commands = {
  'list': list(),
  'create': create(),
  'run': run(),
  'ensure-no-development-scripts': ensureNoDevelopmentScripts()
}

P.try(async function () {
  const cmdLine = parseCommandLine()
  if (cmdLine.options.config) {
    config.setupConfig(require(cmdLine.options.config))
  } else {
    config.findAndLoadConfig()
  }
  await cmdLine.command.action(cmdLine.options)
}).catch(err => {
  if (!err.printed) {
    console.error(err.stack || err)
  }
  process.exit(1)
})

function parseCommandLine() {
  const command = process.argv[2]
  process.argv.splice(2, 1)
  let cmdImpl = commands[command]
  if (!cmdImpl) throw new Error('Invalid command: ' + command + '; available commands are ' + Object.keys(commands).join(', '))

  const defaultOptions = [[
    'config|conf|c=s', 'Configuration file'
  ]]

  const options = nodeGetoptLong.options((cmdImpl.options || []).concat(defaultOptions), {
    name: 'migsi'
  })

  return {
    command: cmdImpl,
    options
  }
}

function list() {
  return {
    options: [],
    action: async function (options) {
      const migrations = await core.loadAllMigrations()
      for (let migration of migrations) {
        logger.log(migration.migsiName, migration.inDevelopment ? 'dev ' : 'prod', migration.runDate || 'to-be-run')
      }
    }
  }
}

function create() {
  return {
    options: [
      ['friendlyName|name|n=s', 'Friendly name for migration script'],
      ['template|t=s', 'Template name']
    ],
    async action({friendlyName, template = 'default'}) {
      if (!friendlyName) {
        friendlyName = await query('Friendly name')
      }
      const filename = await core.createMigrationScript(friendlyName, template)
      logger.log('Migration script created: ' + filename)
    }
  }
}

function run() {
  return {
    options: [
      ['production|prod|p', 'Only run production scripts'],
      ['yes', 'Automatically confirm deployment']
    ],
    action({production = false, yes: confirmed = false}) {
      return core.runMigrations({production, confirmation: confirmed ? undefined : confirmation})
    }
  }
}

function ensureNoDevelopmentScripts() {
  return {
    async action() {
      const migrations = await core.loadAllMigrations()
      if (migrations.any(mig => mig.inDevelopment)) {
        throw new Error('There are migration scripts still in develoment.')
      }
    }
  }
}

async function query(prompt) {
  const readlineImpl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  })
  try {
    return await new Promise(resolve => readlineImpl.question(prompt + ': ', resolve))
  } finally {
    readlineImpl.close()
  }
}

async function confirmation() {
  if (require('tty').isatty(process.stdin)) return true
  if (process.argv.includes('--yes')) return true
  const response = await query('Do you want to run the migrations? [y/N]')
  return ['y', 'yes'].includes(response.toLowerCase())
}<commit_msg>Fix ensure-no-development-scripts<commit_after>
const core = require('./core'),
  nodeGetoptLong = require('node-getopt-long'),
  P = require('bluebird'),
  config = require('./config'),
  readline = require('readline'),
  logger = require('./logger')

const commands = {
  'list': list(),
  'create': create(),
  'run': run(),
  'ensure-no-development-scripts': ensureNoDevelopmentScripts()
}

P.try(async function () {
  const cmdLine = parseCommandLine()
  if (cmdLine.options.config) {
    config.setupConfig(require(cmdLine.options.config))
  } else {
    config.findAndLoadConfig()
  }
  await cmdLine.command.action(cmdLine.options)
}).catch(err => {
  if (!err.printed) {
    console.error(err.stack || err)
  }
  process.exit(1)
})

function parseCommandLine() {
  const command = process.argv[2]
  process.argv.splice(2, 1)
  let cmdImpl = commands[command]
  if (!cmdImpl) throw new Error('Invalid command: ' + command + '; available commands are ' + Object.keys(commands).join(', '))

  const defaultOptions = [[
    'config|conf|c=s', 'Configuration file'
  ]]

  const options = nodeGetoptLong.options((cmdImpl.options || []).concat(defaultOptions), {
    name: 'migsi'
  })

  return {
    command: cmdImpl,
    options
  }
}

function list() {
  return {
    options: [],
    action: async function (options) {
      const migrations = await core.loadAllMigrations()
      for (let migration of migrations) {
        logger.log(migration.migsiName, migration.inDevelopment ? 'dev ' : 'prod', migration.runDate || 'to-be-run')
      }
    }
  }
}

function create() {
  return {
    options: [
      ['friendlyName|name|n=s', 'Friendly name for migration script'],
      ['template|t=s', 'Template name']
    ],
    async action({friendlyName, template = 'default'}) {
      if (!friendlyName) {
        friendlyName = await query('Friendly name')
      }
      const filename = await core.createMigrationScript(friendlyName, template)
      logger.log('Migration script created: ' + filename)
    }
  }
}

function run() {
  return {
    options: [
      ['production|prod|p', 'Only run production scripts'],
      ['yes', 'Automatically confirm deployment']
    ],
    action({production = false, yes: confirmed = false}) {
      return core.runMigrations({production, confirmation: confirmed ? undefined : confirmation})
    }
  }
}

function ensureNoDevelopmentScripts() {
  return {
    async action() {
      const migrations = await core.loadAllMigrations()
      if (migrations.some(mig => mig.inDevelopment)) {
        throw new Error('There are migration scripts still in develoment.')
      }
    }
  }
}

async function query(prompt) {
  const readlineImpl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  })
  try {
    return await new Promise(resolve => readlineImpl.question(prompt + ': ', resolve))
  } finally {
    readlineImpl.close()
  }
}

async function confirmation() {
  if (require('tty').isatty(process.stdin)) return true
  if (process.argv.includes('--yes')) return true
  const response = await query('Do you want to run the migrations? [y/N]')
  return ['y', 'yes'].includes(response.toLowerCase())
}<|endoftext|>


<commit_before>/*
	revealElements plugins works with jQuery
	by aZerato
 */
(function($) {

	/*
		Plugin declaration
	 */
	$.revealElements = function(options) {
		var defaults = {
			revealClass: '.reveal',
			wrapperEl: 'body',
			overlayIndex: 8,
			overlayColor: '#000'
		};

		var plugin = this;

		/*
			publics
		 */
		plugin.init = function() {
			// merge between defaults & options
			plugin.settings = $.extend({}, defaults, options);
			// basic state is false ! never started !
			plugin.state = false;

			plugin.start();
		};

		plugin.start = function() {
			// array of el or only one
			// get size position of wrapperEl
			var wel = $(plugin.settings.wrapperEl),
				el = wel.find(plugin.settings.revealClass),
				welP = wel.position(),
				welW = wel.width(),
				welH = wel.height();
			if(plugin.state === false) {
				if(el.length > 1) {
					for (var i = 0; i < el.length; i++) {
						revealElement( $(el[i]) );
					};
				}
				else
				{
					revealElement(el);
				}

				// On overlay element data annotations with wrapper element & class use for reveal are use
				$(wel).append('

');

				canRemoveOverlay();
			}
			else
			{
				removeReveal('.revealOverlay');
			}
		};

		/*
			privates
		 */
		var revealElement = function(el) {
			// get previous attr style value if exist for no remove it if delete style added with plugin
			var	p = el.position(),
				w = el.width(),
				h = el.height(),
				prevStyle = el.attr('style');

			// adjust style
			p.top += 10;
			p.left -= 5;
			w += 10;
			h += 10;

			// if a style is previously setted don't erase it
			if (!prevStyle) {
				prevStyle = '';
			} else {
				el.attr('data-prevstyle', prevStyle);
			}

			el.addClass('revealElement');
			el.attr('style', 'position: relative; z-index: ' + plugin.settings.overlayIndex + 2 + ' !important; ' + prevStyle);

			$(plugin.settings.wrapperEl).append('

');
		}

		var canRemoveOverlay = function() {
			$('.revealOverlay').click(function() {
				removeReveal($(this));
			});
		};

		var removeReveal = function(overlayEl) {
			//
			var revealWrapper = overlayEl.data('revealwrapper'),
				revealClass = overlayEl.data('revealclass'),
				elWrapper = overlayEl.closest(revealWrapper),
				el = elWrapper.find(revealClass);

			el.removeClass('revealElement');

			// Remove all plugin modifications on dom elements
			for (var i = 0; i < el.length; i++) {
				var elCur = $(el[i]);
				elCur.removeAttr('style', '');
				if(elCur.data('prevstyle')) {
					elCur.attr('style', elCur.data('prevstyle'));
					elCur.removeAttr('data-prevstyle');
				}
			};

			elWrapper.find('.revealOverlay').remove();
			elWrapper.find('.revealOn').remove();
		}

		/*
			initialize
		 */
		plugin.init();
	};

	// Add to jquery functions library
	$.fn.revealElements = function(options) {
		return this.each(function() {
			if (undefined == $(this).data('revealElements')) {
				var plugin = new $.revealElements(this, options);
				$(this).data('revealElements', plugin);
			}
		});
	}
})(jQuery);<commit_msg>Check if current overlay already exist<commit_after>/*
	revealElements plugins works with jQuery
	by aZerato
 */
(function($) {

	/*
		Plugin declaration
	 */
	$.revealElements = function(options) {
		var defaults = {
			revealClass: '.reveal',
			wrapperEl: 'body',
			overlayIndex: 8,
			overlayColor: '#000'
		};

		var plugin = this;

		/*
			publics
		 */
		plugin.init = function() {
			// merge between defaults & options
			plugin.settings = $.extend({}, defaults, options);

			plugin.start();
		};

		plugin.start = function() {
			// array of el or only one
			// get size position of wrapperEl
			var wel = $(plugin.settings.wrapperEl),
				el = wel.find(plugin.settings.revealClass),
				welP = wel.position(),
				welW = wel.width(),
				welH = wel.height(),
				over = wel.find('.revealOverlay');
			// check if an overlay for this wrapperEl already exist
			if(over.length === 0){
				if(el.length > 1) {
					for (var i = 0; i < el.length; i++) {
						revealElement( $(el[i]) );
					};
				}
				else
				{
					revealElement(el);
				}

				// On overlay element data annotations with wrapper element & class use for reveal are use
				$(wel).append('

');

				canRemoveOverlay();
			}
			else
			{
				removeReveal(over);
			}
		};

		/*
			privates
		 */
		var revealElement = function(el) {
			// get previous attr style value if exist for no remove it if delete style added with plugin
			var	p = el.position(),
				w = el.width(),
				h = el.height(),
				prevStyle = el.attr('style');

			// adjust style
			p.top += 10;
			p.left -= 5;
			w += 10;
			h += 10;

			// if a style is previously setted don't erase it
			if (!prevStyle) {
				prevStyle = '';
			} else {
				el.attr('data-prevstyle', prevStyle);
			}

			el.addClass('revealElement');
			el.attr('style', 'position: relative; z-index: ' + plugin.settings.overlayIndex + 2 + ' !important; ' + prevStyle);

			$(plugin.settings.wrapperEl).append('

');
		}

		var canRemoveOverlay = function() {
			$('.revealOverlay').click(function() {
				removeReveal($(this));
			});
		};

		var removeReveal = function(overlayEl) {
			//
			var revealWrapper = overlayEl.data('revealwrapper'),
				revealClass = overlayEl.data('revealclass'),
				elWrapper = overlayEl.closest(revealWrapper),
				el = elWrapper.find(revealClass);

			el.removeClass('revealElement');

			// Remove all plugin modifications on dom elements
			for (var i = 0; i < el.length; i++) {
				var elCur = $(el[i]);
				elCur.removeAttr('style', '');
				if(elCur.data('prevstyle')) {
					elCur.attr('style', elCur.data('prevstyle'));
					elCur.removeAttr('data-prevstyle');
				}
			};

			elWrapper.find('.revealOverlay').remove();
			elWrapper.find('.revealOn').remove();
		}

		/*
			initialize
		 */
		plugin.init();
	};

	// Add to jquery functions library
	$.fn.revealElements = function(options) {
		return this.each(function() {
			if (undefined == $(this).data('revealElements')) {
				var plugin = new $.revealElements(this, options);
				$(this).data('revealElements', plugin);
			}
		});
	}
})(jQuery);<|endoftext|>


<commit_before><commit_msg>renamed dist to vue-pony-uploader<commit_after><|endoftext|>


<commit_before><commit_msg>Small bugfix badges<commit_after><|endoftext|>


<commit_before>var express = require("express");
var fs = require("fs");
var jade = require("jade");
var mkdirp = require("mkdirp");
var path = require("path");
var walk = require("walk");
var url = require("url");

// Not found
function NotFoundError(file) {
    this.name = "NotFoundError";
    this.message = file + " not found";
    this.stack = (new Error(this.message)).stack;
}
NotFoundError.prototype = new Error();

// Not an output file
function NoExtendsError(file) {
    this.name = "NoExtendsError";
    this.message = file + " does not use a template";
    this.stack = (new Error(this.message)).stack;
}
NoExtendsError.prototype = new Error();

function RenderPipe(dir) {
    this.dir = dir || __dirname;
    this.renderRequest = this.renderRequest.bind(this);
    this.readAsset = this.readAsset.bind(this);
    this.readJsonAsset = this.readJsonAsset.bind(this);

    this.assets = {};
    this.jsonAssets = {};
}

RenderPipe.prototype.listen = function (port) {
    if (this.app) {
        return;
    }

    this.app = express();
    this.app.get(/.*/, this.renderRequest);
    this.app.listen(port || 3000);
};

RenderPipe.prototype.renderRequest = function (req, res, next) {
    var reqpath = url.parse(req.url).pathname;
    var file = path.join(this.dir, reqpath).replace(/\.html$/, ".jade");

    this.renderFile(file, function (err, html) {
        if (err) {
            if (err instanceof NotFoundError) {
                return next();
            } else {
                return next(err);
            }
        }

        res.end(html);
    });
};

RenderPipe.prototype.renderFile = function (file, cb) {
    var self = this;

    fs.exists(file, function (exists) {
        if (!exists) {
            return cb(new NotFoundError(file));
        }

        if (!file.match(/\.jade/)) {
            return cb(new Error(file + " does not look like a Jade file"));
        }

        fs.readFile(file, "utf8", function (err, src) {
            if (err) {
                return cb(err);
            }

            if (!src.match(/(^|\n)extends/)) {
                return cb(new NoExtendsError(file));
            }

            try {
                var render = jade.compileFile(file, {});
                cb(null, render({
                    read: self.readAsset,
                    readJson: self.readJsonAsset,
                    filename: path.basename(file),
                }));
            } catch (e) {
                cb(e);
            }
        });
    });
};

RenderPipe.prototype.renderStatic = function (out, cb) {
    var self = this;

    var walker = walk.walk(this.dir);
    walker.on("file", function (root, stats, next) {
        if (!stats.name.match(/\.jade$/)) {
            return next();
        }

        var srcPath = path.join(root, stats.name);
        var outDir = path.join(out, root.replace(self.dir, ""));
        var outName = stats.name.replace(/\.jade$/, ".html");

        mkdirp(outDir, function (err) {
            if (err) {
                return cb(err);
            }

            self.renderFile(srcPath, function (err, html) {
                if (err) {
                    if (err instanceof NoExtendsError) {
                        return next(); // Skip it
                    } else {
                        return cb(err);
                    }
                }

                fs.writeFile(path.join(outDir, outName), html, function (err) {
                    if (err) {
                        cb(err);
                    } else {
                        next();
                    }
                });
            });
        });
    });
    walker.on("end", cb);
};

RenderPipe.prototype.readAsset = function (filename) {
    if (!this.assets[filename]) {
        this.assets[filename] = fs.readFileSync(path.join(this.dir, filename), "utf8");
    }
    return this.assets[filename];
};

RenderPipe.prototype.readJsonAsset = function (filename) {
    if (!this.jsonAssets[filename]) {
        this.jsonAssets[filename] = JSON.parse(this.readAsset(filename));
    }
    return this.jsonAssets[filename];
};

module.exports = RenderPipe;
<commit_msg>Allow flushing rendering cache.<commit_after>var express = require("express");
var fs = require("fs");
var jade = require("jade");
var mkdirp = require("mkdirp");
var path = require("path");
var walk = require("walk");
var url = require("url");

// Not found
function NotFoundError(file) {
    this.name = "NotFoundError";
    this.message = file + " not found";
    this.stack = (new Error(this.message)).stack;
}
NotFoundError.prototype = new Error();

// Not an output file
function NoExtendsError(file) {
    this.name = "NoExtendsError";
    this.message = file + " does not use a template";
    this.stack = (new Error(this.message)).stack;
}
NoExtendsError.prototype = new Error();

function RenderPipe(dir) {
    this.dir = dir || __dirname;
    this.renderRequest = this.renderRequest.bind(this);
    this.readAsset = this.readAsset.bind(this);
    this.readJsonAsset = this.readJsonAsset.bind(this);

    this.assets = {};
    this.jsonAssets = {};
}

RenderPipe.prototype.listen = function (port) {
    if (this.app) {
        return;
    }

    this.app = express();
    this.app.get(/.*/, this.renderRequest);
    this.app.listen(port || 3000);
};

RenderPipe.prototype.renderRequest = function (req, res, next) {
    var reqpath = url.parse(req.url).pathname;
    var file = path.join(this.dir, reqpath).replace(/\.html$/, ".jade");

    this.renderFile(file, function (err, html) {
        if (err) {
            if (err instanceof NotFoundError) {
                return next();
            } else {
                return next(err);
            }
        }

        res.end(html);
    });
};

RenderPipe.prototype.renderFile = function (file, cb) {
    var self = this;

    fs.exists(file, function (exists) {
        if (!exists) {
            return cb(new NotFoundError(file));
        }

        if (!file.match(/\.jade/)) {
            return cb(new Error(file + " does not look like a Jade file"));
        }

        fs.readFile(file, "utf8", function (err, src) {
            if (err) {
                return cb(err);
            }

            if (!src.match(/(^|\n)extends/)) {
                return cb(new NoExtendsError(file));
            }

            try {
                var render = jade.compileFile(file, {});
                cb(null, render({
                    read: self.readAsset,
                    readJson: self.readJsonAsset,
                    filename: path.basename(file),
                }));
            } catch (e) {
                cb(e);
            }
        });
    });
};

RenderPipe.prototype.renderStatic = function (out, cb) {
    var self = this;

    var walker = walk.walk(this.dir);
    walker.on("file", function (root, stats, next) {
        if (!stats.name.match(/\.jade$/)) {
            return next();
        }

        var srcPath = path.join(root, stats.name);
        var outDir = path.join(out, root.replace(self.dir, ""));
        var outName = stats.name.replace(/\.jade$/, ".html");

        mkdirp(outDir, function (err) {
            if (err) {
                return cb(err);
            }

            self.renderFile(srcPath, function (err, html) {
                if (err) {
                    if (err instanceof NoExtendsError) {
                        return next(); // Skip it
                    } else {
                        return cb(err);
                    }
                }

                fs.writeFile(path.join(outDir, outName), html, function (err) {
                    if (err) {
                        cb(err);
                    } else {
                        next();
                    }
                });
            });
        });
    });
    walker.on("end", cb);
};

RenderPipe.prototype.readAsset = function (filename) {
    if (!this.assets[filename]) {
        this.assets[filename] = fs.readFileSync(path.join(this.dir, filename), "utf8");
    }
    return this.assets[filename];
};

RenderPipe.prototype.readJsonAsset = function (filename) {
    if (!this.jsonAssets[filename]) {
        this.jsonAssets[filename] = JSON.parse(this.readAsset(filename));
    }
    return this.jsonAssets[filename];
};

RenderPipe.prototype.flushCache = function () {
    this.assets = {};
    this.jsonAssets = {};
};

module.exports = RenderPipe;
<|endoftext|>


<commit_before>import * as I from 'immutable';

const current = (spec, stack) => stack.last().first();
const result  = (spec, stack) => spec.extract(current(spec, stack));

const step = function step(spec, stack) {
  const children = I.List(spec.children(current(spec, stack)));

  if (children.size > 0)
    return backtracker(spec, stack.push(I.List([
      children.first(), children.rest(), 0
    ])));
  else
    return skip(spec, stack);
};

const skip = function skip(spec, stack) {
  let s = stack;
  while(s.last() && s.last().get(1).size == 0)
    s = s.pop();

  if (s.size > 0) {
    const siblingsLeft = s.last().get(1);
    const branchNr     = s.last().get(2);

    return backtracker(spec, s.pop().push(I.List([
      siblingsLeft.first(), siblingsLeft.rest(), branchNr + 1
    ])));
  }
};

const backtracker = function backtracker(spec, stack) {
  if (stack === undefined)
    return backtracker(spec, I.List([I.List([spec.root, I.List([]), 0])]));
  else {
    return {
      current: () => current(spec, stack),
      result : () => result(spec, stack),
      step   : () => step(spec, stack),
      skip   : () => skip(spec, stack)
    };
  }
};

const results = function results(gen, pred) {
  let g = gen;

  return I.Seq({
    next() {
      while (g) {
        if (!pred || pred(g.current())) {
          const val = g.result();
          g = g.step();
          if (val !== undefined)
            return { done: false, value: val };
        } else
          g = g.skip();
      }
      return { done: true };
    }
  });
};

const empty = function empty() {
  return backtracker({
    root    : null,
    extract : () => {},
    children: () => {}
  });
};

const singleton = function singleton(x) {
  return backtracker({
    root    : x,
    extract : x => x,
    children: () => {}
  });
};

module.exports = {
  backtracker: backtracker,
  results    : results,
  empty      : empty,
  singleton  : singleton
};

if (require.main == module) {
  const n = parseInt(process.argv[2]);

  const gen = backtracker({
    root: {
      xs: [],
      sz: 0,
      mx: 1
    },
    extract(node) {
      if (node.sz == n)
        return node.xs;
    },
    children(node) {
      const ch = [];

      for (let i = node.mx; i < n - node.sz + 1; ++i)
        ch.push({
          xs: node.xs.concat(i),
          sz: node.sz + i,
          mx: Math.max(node.mx, i)
        });

      return ch;
    }
  });

  console.log(JSON.stringify(results(gen)));
}
<commit_msg>use ES6 module syntax<commit_after>import * as I from 'immutable';

const current = (spec, stack) => stack.last().first();
const result  = (spec, stack) => spec.extract(current(spec, stack));

const step = function step(spec, stack) {
  const children = I.List(spec.children(current(spec, stack)));

  if (children.size > 0)
    return backtracker(spec, stack.push(I.List([
      children.first(), children.rest(), 0
    ])));
  else
    return skip(spec, stack);
};

const skip = function skip(spec, stack) {
  let s = stack;
  while(s.last() && s.last().get(1).size == 0)
    s = s.pop();

  if (s.size > 0) {
    const siblingsLeft = s.last().get(1);
    const branchNr     = s.last().get(2);

    return backtracker(spec, s.pop().push(I.List([
      siblingsLeft.first(), siblingsLeft.rest(), branchNr + 1
    ])));
  }
};

export function backtracker(spec, stack) {
  if (stack === undefined)
    return backtracker(spec, I.List([I.List([spec.root, I.List([]), 0])]));
  else {
    return {
      current: () => current(spec, stack),
      result : () => result(spec, stack),
      step   : () => step(spec, stack),
      skip   : () => skip(spec, stack)
    };
  }
};

export function results(gen, pred) {
  let g = gen;

  return I.Seq({
    next() {
      while (g) {
        if (!pred || pred(g.current())) {
          const val = g.result();
          g = g.step();
          if (val !== undefined)
            return { done: false, value: val };
        } else
          g = g.skip();
      }
      return { done: true };
    }
  });
};

export function empty() {
  return backtracker({
    root    : null,
    extract : () => {},
    children: () => {}
  });
};

export function singleton(x) {
  return backtracker({
    root    : x,
    extract : x => x,
    children: () => {}
  });
};

if (require.main == module) {
  const n = parseInt(process.argv[2]);

  const gen = backtracker({
    root: {
      xs: [],
      sz: 0,
      mx: 1
    },
    extract(node) {
      if (node.sz == n)
        return node.xs;
    },
    children(node) {
      const ch = [];

      for (let i = node.mx; i < n - node.sz + 1; ++i)
        ch.push({
          xs: node.xs.concat(i),
          sz: node.sz + i,
          mx: Math.max(node.mx, i)
        });

      return ch;
    }
  });

  console.log(JSON.stringify(results(gen)));
}
<|endoftext|>


<commit_before>/*
  Copyright 2015 Google Inc. All Rights Reserved.
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
      http://www.apache.org/licenses/LICENSE-2.0
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
var zlib = Promise.promisifyAll(require('zlib'));
var path = require('path');
var types = require('./types');
var stream = require('./stream');
var phase = require('./phase-register.js');

var StringDecoder = require('string_decoder').StringDecoder;
var TreeBuilder = require('../lib/tree-builder');
var EjsFabricator = require('../lib/ejs-fabricator');

module.exports.readDir = phase({input: types.string, output: types.string, arity: '1:N'},
  function(dirName, tags) {
    fs.readdirSync(dirName).forEach(function(filename) {
      this.put(path.join(dirName, filename)).tag('filename', filename);
    }.bind(this));
  });

function typeVar(s) { return (function(v) {
  if (!v[s]) {
    v[s] = types.newTypeVar();
  }
  return v[s];
}); }

module.exports.filter = phase({input: types.string, output: types.string, arity: '1:N'},
  function(value) {
    if (this.options.regExp.test(value)) {
      this.put(value);
    }
  },
  { regExp: /.?/ });

module.exports.log = phase({input: typeVar('a'), output: typeVar('a'), arity: '1:1'},
  function(data, tags) {
    this.options.tags.forEach(function(tag) {
      console.log(tag, tags.read(tag));
    });
    console.log(data);
    return data;
  },
  { tags: [] });

module.exports.jsonParse = phase({input: types.string, output: types.JSON, arity: '1:1'}, JSON.parse);

module.exports.jsonStringify = phase({input: types.JSON, output: types.string, arity: '1:1'}, JSON.stringify);

var treeBuilder = function(Type) {
  return function(data) {
    var writer = new Type();
    var builder = new TreeBuilder();
    builder.build(data);
    builder.write(writer);
    return writer.getHTML();
  };
};
var writers = {
  HTMLWriter: require('../lib/html-writer'),
  JSWriter: require('../lib/js-writer'),
  StatsWriter: require('../lib/stats-writer')
};
var filters = {
  StyleFilter: require('../lib/style-filter'),
  StyleMinimizationFilter: require('../lib/style-minimization-filter'),
  StyleTokenizerFilter: require('../lib/style-tokenizer-filter'),
  NukeIFrameFilter: require('../lib/nuke-iframe-filter'),
  StyleDetokenizerFilter: require('../lib/style-detokenizer-filter')
};
var fabricators = {
  SchemaBasedFabricator: require('../lib/schema-based-fabricator'),
};
for (var writer in writers) {
  module.exports[writer] = phase({input: types.JSON, output: types.string, arity: ':1'},
    treeBuilder(writers[writer]));
}
for (var filter in filters) {
  module.exports[filter] = phase({input: types.JSON, output: types.JSON, arity: '1:1'},
    treeBuilder(filters[filter]));
}
for (var fab in fabricators) {
  module.exports[fab] = phase({input: types.JSON, output: types.JSON, arity: '1:1'},
    function(data) {
      var fab = new (fabricators[fab])(data);
      return fab.fabricate();
    });
}

module.exports.ejsFabricator = phase({input: types.string, output: types.string, arity: '1:N'},
    function(data) {
      var result = new EjsFabricator(data, '').fabricate();
      for (key in result) {
        this.put(result[key]);
        this.tags.tag('ejsFabricator', key);
      }
    });

module.exports.writeStringFile = phase({input: types.string, output: types.string, arity: '1:1'},
    function(data, tags) {
      if (this.options.tag == '') {
        var filename = this.options.filename;
      } else {
        var filename = tags.read(this.options.tag);
      }
      fs.writeFileSync(filename, data);
      return data;
    },
    { tag: '', filename: 'result' });

module.exports.input = phase({output: types.string, arity: '0:N'},
    function(tags) {
      if (this.options.tag)
        tags.tag('data', this.options.data);
      this.sendData(this.options.data);
      return Promise.resolve();
    },
    { data: '', tag: true});

module.exports.retag = phase({input: typeVar('a'), output: typeVar('a'), arity: '1:1'},
  function(data, tags) {
    var input = tags.read(this.options.tag);
    if (input !== undefined)
      tags.tag(this.options.tag, input.replace(new RegExp(this.options.in), this.options.out));
    return data;
  },
  { tag: '', in: '', out: ''});

module.exports.dummy = phase({input: typeVar('a'), output: typeVar('a'), arity: '1:1'},
  function(data) { return data; });

// TODO: This is for testing. Does it belong here?
module.exports.compare = phase({input: typeVar('a'), output: typeVar('a'), arity: '1:1'},
  function(data, tags) {
    var input = tags.read(this.options.tag);
    var inFile = fs.readFileSync(input, 'utf8');
    if (typeof data != 'string') {
      inFile = JSON.parse(inFile);
    }
    var assert = require('chai').assert;
    assert.deepEqual(inFile, data);
    return data;
  },
  { tag: ''});

module.exports.fileToBuffer = phase({input: types.string, output: types.buffer, arity: '1:1', async: true},
  function(filename, tags) {
    if (!tags.filename) {
      this.tags.tag('filename', filename);
    }
    return fs.readFileAsync(filename);
  });

module.exports.gunzipAndDecode = phase({
  input: types.buffer,
  output: types.string,
  arity: '1:1',
  async: true,
}, function(buffer) {
  var data;
  function unzipRecursive(buffer) {
    data = buffer;
    return zlib.gunzipAsync(buffer).then(unzipRecursive);
  }
  return unzipRecursive(buffer).catch(function(e) {
    return new StringDecoder('utf8').write(data);
  });
});

module.exports.bufferToString = phase({input: types.buffer, output: types.string, arity: '1:1'}, String);

module.exports.regexReplace = phase({
  name: 'regexReplace',
  input: types.string,
  output: types.string,
  arity: '1:1'
}, function(data) {
  if (this.options.pattern && this.options.pattern !== '') {
    this.tags.tag('regexReplace', this.options.pattern + ' -> ' + this.options.replace);
    return data.replace(new RegExp(this.options.pattern, this.options.flags), this.options.replace);
  }
  this.tags.tag('regexReplace', this.options.match + ' -> ' + this.options.replace);
  return data.replace(this.options.match, this.options.replace);
}, { match: '', replace: '', pattern: '', flags: '' });

module.exports.updateTag = phase({input: typeVar('a'), output: typeVar('a'), arity: '1:1'},
    function(data) {
      var newValue = this.tags.read(this.options.tag).replace(this.options.in, this.options.out);
      this.tags.tag(this.options.tag, newValue);
      return data;
    },
    {tag: '', in: /.?/, out: ""});

module.exports.fork = phase({input: typeVar('a'), output: typeVar('a'), arity: '1:1'},
    function(data) {
      this.newStream();
      return this.yield(data);
    });

module.exports.stdin = phase({input: types.unit, output: types.string, arity: '0:N', parallel: 1},
    function() {
      process.stdin.pipe(require('split')()).on('data', function(line) {
        // We get an emptry string between the last newline and end of stream :(
        if (line != '')
          this.sendData(line);
      }.bind(this)).on('end', function() {
        this.resolve();
      }.bind(this));
      return new Promise(function(resolve, reject) { this.resolve = resolve; }.bind(this));
    });
<commit_msg>Comment -> TODO<commit_after>/*
  Copyright 2015 Google Inc. All Rights Reserved.
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
      http://www.apache.org/licenses/LICENSE-2.0
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
var zlib = Promise.promisifyAll(require('zlib'));
var path = require('path');
var types = require('./types');
var stream = require('./stream');
var phase = require('./phase-register.js');

var StringDecoder = require('string_decoder').StringDecoder;
var TreeBuilder = require('../lib/tree-builder');
var EjsFabricator = require('../lib/ejs-fabricator');

module.exports.readDir = phase({input: types.string, output: types.string, arity: '1:N'},
  function(dirName, tags) {
    fs.readdirSync(dirName).forEach(function(filename) {
      this.put(path.join(dirName, filename)).tag('filename', filename);
    }.bind(this));
  });

function typeVar(s) { return (function(v) {
  if (!v[s]) {
    v[s] = types.newTypeVar();
  }
  return v[s];
}); }

module.exports.filter = phase({input: types.string, output: types.string, arity: '1:N'},
  function(value) {
    if (this.options.regExp.test(value)) {
      this.put(value);
    }
  },
  { regExp: /.?/ });

module.exports.log = phase({input: typeVar('a'), output: typeVar('a'), arity: '1:1'},
  function(data, tags) {
    this.options.tags.forEach(function(tag) {
      console.log(tag, tags.read(tag));
    });
    console.log(data);
    return data;
  },
  { tags: [] });

module.exports.jsonParse = phase({input: types.string, output: types.JSON, arity: '1:1'}, JSON.parse);

module.exports.jsonStringify = phase({input: types.JSON, output: types.string, arity: '1:1'}, JSON.stringify);

var treeBuilder = function(Type) {
  return function(data) {
    var writer = new Type();
    var builder = new TreeBuilder();
    builder.build(data);
    builder.write(writer);
    return writer.getHTML();
  };
};
var writers = {
  HTMLWriter: require('../lib/html-writer'),
  JSWriter: require('../lib/js-writer'),
  StatsWriter: require('../lib/stats-writer')
};
var filters = {
  StyleFilter: require('../lib/style-filter'),
  StyleMinimizationFilter: require('../lib/style-minimization-filter'),
  StyleTokenizerFilter: require('../lib/style-tokenizer-filter'),
  NukeIFrameFilter: require('../lib/nuke-iframe-filter'),
  StyleDetokenizerFilter: require('../lib/style-detokenizer-filter')
};
var fabricators = {
  SchemaBasedFabricator: require('../lib/schema-based-fabricator'),
};
for (var writer in writers) {
  module.exports[writer] = phase({input: types.JSON, output: types.string, arity: ':1'},
    treeBuilder(writers[writer]));
}
for (var filter in filters) {
  module.exports[filter] = phase({input: types.JSON, output: types.JSON, arity: '1:1'},
    treeBuilder(filters[filter]));
}
for (var fab in fabricators) {
  module.exports[fab] = phase({input: types.JSON, output: types.JSON, arity: '1:1'},
    function(data) {
      var fab = new (fabricators[fab])(data);
      return fab.fabricate();
    });
}

module.exports.ejsFabricator = phase({input: types.string, output: types.string, arity: '1:N'},
    function(data) {
      var result = new EjsFabricator(data, '').fabricate();
      for (key in result) {
        this.put(result[key]);
        this.tags.tag('ejsFabricator', key);
      }
    });

module.exports.writeStringFile = phase({input: types.string, output: types.string, arity: '1:1'},
    function(data, tags) {
      if (this.options.tag == '') {
        var filename = this.options.filename;
      } else {
        var filename = tags.read(this.options.tag);
      }
      fs.writeFileSync(filename, data);
      return data;
    },
    { tag: '', filename: 'result' });

module.exports.input = phase({output: types.string, arity: '0:N'},
    function(tags) {
      if (this.options.tag)
        tags.tag('data', this.options.data);
      this.sendData(this.options.data);
      return Promise.resolve();
    },
    { data: '', tag: true});

module.exports.retag = phase({input: typeVar('a'), output: typeVar('a'), arity: '1:1'},
  function(data, tags) {
    var input = tags.read(this.options.tag);
    if (input !== undefined)
      tags.tag(this.options.tag, input.replace(new RegExp(this.options.in), this.options.out));
    return data;
  },
  { tag: '', in: '', out: ''});

module.exports.dummy = phase({input: typeVar('a'), output: typeVar('a'), arity: '1:1'},
  function(data) { return data; });

// TODO: This is for testing. Does it belong here?
module.exports.compare = phase({input: typeVar('a'), output: typeVar('a'), arity: '1:1'},
  function(data, tags) {
    var input = tags.read(this.options.tag);
    var inFile = fs.readFileSync(input, 'utf8');
    if (typeof data != 'string') {
      inFile = JSON.parse(inFile);
    }
    var assert = require('chai').assert;
    assert.deepEqual(inFile, data);
    return data;
  },
  { tag: ''});

module.exports.fileToBuffer = phase({input: types.string, output: types.buffer, arity: '1:1', async: true},
  function(filename, tags) {
    if (!tags.filename) {
      this.tags.tag('filename', filename);
    }
    return fs.readFileAsync(filename);
  });

module.exports.gunzipAndDecode = phase({
  input: types.buffer,
  output: types.string,
  arity: '1:1',
  async: true,
}, function(buffer) {
  var data;
  function unzipRecursive(buffer) {
    data = buffer;
    return zlib.gunzipAsync(buffer).then(unzipRecursive);
  }
  return unzipRecursive(buffer).catch(function(e) {
    return new StringDecoder('utf8').write(data);
  });
});

module.exports.bufferToString = phase({input: types.buffer, output: types.string, arity: '1:1'}, String);

module.exports.regexReplace = phase({
  name: 'regexReplace',
  input: types.string,
  output: types.string,
  arity: '1:1'
}, function(data) {
  if (this.options.pattern && this.options.pattern !== '') {
    this.tags.tag('regexReplace', this.options.pattern + ' -> ' + this.options.replace);
    return data.replace(new RegExp(this.options.pattern, this.options.flags), this.options.replace);
  }
  this.tags.tag('regexReplace', this.options.match + ' -> ' + this.options.replace);
  return data.replace(this.options.match, this.options.replace);
}, { match: '', replace: '', pattern: '', flags: '' });

module.exports.updateTag = phase({input: typeVar('a'), output: typeVar('a'), arity: '1:1'},
    function(data) {
      var newValue = this.tags.read(this.options.tag).replace(this.options.in, this.options.out);
      this.tags.tag(this.options.tag, newValue);
      return data;
    },
    {tag: '', in: /.?/, out: ""});

module.exports.fork = phase({input: typeVar('a'), output: typeVar('a'), arity: '1:1'},
    function(data) {
      this.newStream();
      return this.yield(data);
    });

module.exports.stdin = phase({input: types.unit, output: types.string, arity: '0:N', parallel: 1},
    function() {
      process.stdin.pipe(require('split')()).on('data', function(line) {
        // TODO: replace split so we can actually receive empty strings.
        if (line != '')
          this.sendData(line);
      }.bind(this)).on('end', function() {
        this.resolve();
      }.bind(this));
      return new Promise(function(resolve, reject) { this.resolve = resolve; }.bind(this));
    });
<|endoftext|>