<commit_before>/*
 * Copyright 2012 Sakai Foundation (SF) Licensed under the
 * Educational Community 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.osedu.org/licenses/ECL-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 fs = require('fs');
var readline = require('readline');
var tsung = require('./lib/tsung');
var util = require('util');
var wrench = require('wrench');
var TsungUtil = require('./lib/util');

var optimist = require('optimist')
        .usage('Usage: $0 -o <output dir> -d <data format dir>')
        .alias('o', 'output-dir')
        .describe('o', 'The directory in which the output Tsung package should be generated.')
        .default('o', util.format('./tsung-%s', new Date().getTime()));
var argv = optimist.argv;

if (argv['help']) {
    return console.log(optimist.help());
}

var config = {
    'loglevel': 'notice',
    'version': '1.0',
    'dumpTraffic': false
};

var outputRoot = argv.o;

var suite = null;
var runner = new tsung.Tsung(config);

// Read from the commandline.
var rl = readline.createInterface({
  'input': process.stdin,
  'output': process.stdout
});

/**
 * Parses all the data format files from the specified data directory. There should be a number of files with extension
 * `.format` in this directory. On the first line, the format file specifies how a CSV file (of the same name) will be
 * interpretted by Tsung. The first line of the format file will specify a comma-separated list of variable names that
 * should match the columns of the CSV file. The second line defines how the file should be traversed (`iter` or `random`).
 * The filename will be used as an internal identifier. Make sure the variable names are globally unique across all your
 * data files.
 */
var generateDataConfiguration = function() {
    try {
        var dataConfig = require('./config/data.json');
    } catch (err) {
        // no data config. this is fine I guess.
        return;
    }

    // file servers
    if (dataConfig.files) {
        for (var id in dataConfig.files) {
            var file = dataConfig.files[id];
            runner.addFileServer(id, util.format('data/%s.csv', id), file.vars, ',', file.order);
        }
    }

    // randomized strings
    if (dataConfig.strings) {
        for (var id in dataConfig.strings) {
            runner.addRandomStringGenerator(id, dataConfig.strings[id].length);
        }
    }

    // randomized numbers
    if (dataConfig.numbers) {
        for (var id in dataConfig.numbers) {
            runner.addRandomNumberGenerator(id, dataConfig.numbers[id].start, dataConfig.numbers[id].end);
        }
    }
}

/**
 * Prompts the user for the test suite to run.
 *
 * @param {Function} callback Invoked when the user has set up the suite
 */
var promptSuite = function(callback) {
    rl.question("What suite would you like to run? ", function(answer) {
        suite = answer;
        callback();
    });
}

/**
 * Prompts the user for the tsung clients from which the performance test will be run.
 *
 * @param {Function} callback Invoked when the user has finished specifying their input
 */
var promptClient = function(callback) {
    rl.question("What clients are you driving your tests from? [hostname1,hostname2,...]? ", function(answer) {
        runner.addClient(answer, true, 10000);
        callback();
    });
}

/**
 * Prompts the user for the servers that will be tested by tsung.
 *
 * @param {Function} callback Invoked when the user has finished specifying their input
 */
var promptServer = function(callback) {
    rl.question("What server are you running this test against? [hostname:port] ", function(answer) {
        var server = answer.split(':');
        if (server.length !== 2 || !parseInt(server[1], 10)) {
            console.log('Please provide a valid server.');
            promptServer();
        } else {
            runner.addServer(server[0], server[1]);
            rl.question('Add another server? [y/N] ', function(answer) {
                if (answer === 'y') {
                    promptServer(callback);
                } else {
                    callback();
                }
            });
        }
    });
};

/**
 * Prompts the user for the test phases that should generated by tsung.
 *
 * @param {Function}    callback    Invoked when the user has finished specifying their input
 * @param {Number}      i           The index of the current phase. If starting anew, don't specify this parameter
 */
var promptPhases = function(callback, i) {
    i = i || 1;
    rl.question('Phase ' + i + ': In what unit should the phase time be measured? [hour, minute, second] ', function(answer) {
        if (answer === 'hour' || answer === 'minute' || answer === 'second') {
            var phaseUnit = answer;
            rl.question('Phase ' + i + ': How long should the phase take (in ' + phaseUnit +'s) ? ', function(answer) {
                var time = parseInt(answer, 10);
                if (time > 0) {
                    rl.question('Phase ' + i + ': In what unit should users be added [hour, minute, second] ? ', function(answer) {
                        if (answer === 'hour' || answer === 'minute' || answer === 'second') {
                            var usersUnit = answer;
                            rl.question('Phase ' + i + ': How many users should be added per ' + usersUnit + ' ? ', function(answer) {
                                var newUsers = parseInt(answer, 10);
                                if (newUsers > 0) {
                                    runner.addPhase(time, phaseUnit, newUsers, usersUnit);
                                    rl.question('Add another phase? [y/N]', function(answer) {
                                        if (answer === 'y') {
                                            promptPhases(callback, i+1);
                                        } else {
                                            callback();
                                        }
                                    });
                                } else {
                                    console.log('Invalid amount');
                                    promptPhases(callback, i);
                                }
                            });
                        } else {
                            console.log('Invalid timeunit');
                            promptPhases(callback, i);
                        }
                    });
                } else {
                    console.log('Invalid amount');
                    promptPhases(callback, i);
                }
            });
        } else {
            console.log('Invalid timeunit');
            promptPhases(callback, i);
        }
    });
};

var packageTestRunner = function(xml, callback) {
    wrench.mkdirSyncRecursive(outputRoot, 0777);
    fs.writeFile(util.format('%s/tsung.xml', outputRoot), xml, 'utf-8', callback);
}

var printErr = function(err, msg) {
    console.log(msg);
    console.log(err);
    console.log(err.stack);
}

// first parse and build the data model
generateDataConfiguration();

// then gather user information
promptSuite(function() {
    TsungUtil.generateTestsForSuite(runner, suite, function(err) {
        if (err) {
            return printErr('Error generating tests for suite.');
        }
        promptClient(function() {
            promptServer(function() {
                promptPhases(function() {
                    rl.close();
                    runner.toXml(function(xml) {
                        packageTestRunner(xml, function(err) {
                            if (err) {
                                return printErr('Error packaging up the test runner.', err);
                            }
                            console.log('Test successfully generated in %s', outputRoot);
                        });
                    });
                });
            });
        });
    });
});
<commit_msg>Making main.js executable<commit_after>#!/usr/bin/env node

/*
 * Copyright 2012 Sakai Foundation (SF) Licensed under the
 * Educational Community 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.osedu.org/licenses/ECL-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 fs = require('fs');
var readline = require('readline');
var tsung = require('./lib/tsung');
var util = require('util');
var wrench = require('wrench');
var TsungUtil = require('./lib/util');

var optimist = require('optimist')
        .usage('Usage: $0 -o <output dir> -d <data format dir>')
        .alias('o', 'output-dir')
        .describe('o', 'The directory in which the output Tsung package should be generated.')
        .default('o', util.format('./tsung-%s', new Date().getTime()));
var argv = optimist.argv;

if (argv['help']) {
    return console.log(optimist.help());
}

var config = {
    'loglevel': 'notice',
    'version': '1.0',
    'dumpTraffic': false
};

var outputRoot = argv.o;

var suite = null;
var runner = new tsung.Tsung(config);

// Read from the commandline.
var rl = readline.createInterface({
  'input': process.stdin,
  'output': process.stdout
});

/**
 * Parses all the data format files from the specified data directory. There should be a number of files with extension
 * `.format` in this directory. On the first line, the format file specifies how a CSV file (of the same name) will be
 * interpretted by Tsung. The first line of the format file will specify a comma-separated list of variable names that
 * should match the columns of the CSV file. The second line defines how the file should be traversed (`iter` or `random`).
 * The filename will be used as an internal identifier. Make sure the variable names are globally unique across all your
 * data files.
 */
var generateDataConfiguration = function() {
    try {
        var dataConfig = require('./config/data.json');
    } catch (err) {
        // no data config. this is fine I guess.
        return;
    }

    // file servers
    if (dataConfig.files) {
        for (var id in dataConfig.files) {
            var file = dataConfig.files[id];
            runner.addFileServer(id, util.format('data/%s.csv', id), file.vars, ',', file.order);
        }
    }

    // randomized strings
    if (dataConfig.strings) {
        for (var id in dataConfig.strings) {
            runner.addRandomStringGenerator(id, dataConfig.strings[id].length);
        }
    }

    // randomized numbers
    if (dataConfig.numbers) {
        for (var id in dataConfig.numbers) {
            runner.addRandomNumberGenerator(id, dataConfig.numbers[id].start, dataConfig.numbers[id].end);
        }
    }
}

/**
 * Prompts the user for the test suite to run.
 *
 * @param {Function} callback Invoked when the user has set up the suite
 */
var promptSuite = function(callback) {
    rl.question("What suite would you like to run? ", function(answer) {
        suite = answer;
        callback();
    });
}

/**
 * Prompts the user for the tsung clients from which the performance test will be run.
 *
 * @param {Function} callback Invoked when the user has finished specifying their input
 */
var promptClient = function(callback) {
    rl.question("What clients are you driving your tests from? [hostname1,hostname2,...]? ", function(answer) {
        runner.addClient(answer, true, 10000);
        callback();
    });
}

/**
 * Prompts the user for the servers that will be tested by tsung.
 *
 * @param {Function} callback Invoked when the user has finished specifying their input
 */
var promptServer = function(callback) {
    rl.question("What server are you running this test against? [hostname:port] ", function(answer) {
        var server = answer.split(':');
        if (server.length !== 2 || !parseInt(server[1], 10)) {
            console.log('Please provide a valid server.');
            promptServer();
        } else {
            runner.addServer(server[0], server[1]);
            rl.question('Add another server? [y/N] ', function(answer) {
                if (answer === 'y') {
                    promptServer(callback);
                } else {
                    callback();
                }
            });
        }
    });
};

/**
 * Prompts the user for the test phases that should generated by tsung.
 *
 * @param {Function}    callback    Invoked when the user has finished specifying their input
 * @param {Number}      i           The index of the current phase. If starting anew, don't specify this parameter
 */
var promptPhases = function(callback, i) {
    i = i || 1;
    rl.question('Phase ' + i + ': In what unit should the phase time be measured? [hour, minute, second] ', function(answer) {
        if (answer === 'hour' || answer === 'minute' || answer === 'second') {
            var phaseUnit = answer;
            rl.question('Phase ' + i + ': How long should the phase take (in ' + phaseUnit +'s) ? ', function(answer) {
                var time = parseInt(answer, 10);
                if (time > 0) {
                    rl.question('Phase ' + i + ': In what unit should users be added [hour, minute, second] ? ', function(answer) {
                        if (answer === 'hour' || answer === 'minute' || answer === 'second') {
                            var usersUnit = answer;
                            rl.question('Phase ' + i + ': How many users should be added per ' + usersUnit + ' ? ', function(answer) {
                                var newUsers = parseInt(answer, 10);
                                if (newUsers > 0) {
                                    runner.addPhase(time, phaseUnit, newUsers, usersUnit);
                                    rl.question('Add another phase? [y/N]', function(answer) {
                                        if (answer === 'y') {
                                            promptPhases(callback, i+1);
                                        } else {
                                            callback();
                                        }
                                    });
                                } else {
                                    console.log('Invalid amount');
                                    promptPhases(callback, i);
                                }
                            });
                        } else {
                            console.log('Invalid timeunit');
                            promptPhases(callback, i);
                        }
                    });
                } else {
                    console.log('Invalid amount');
                    promptPhases(callback, i);
                }
            });
        } else {
            console.log('Invalid timeunit');
            promptPhases(callback, i);
        }
    });
};

var packageTestRunner = function(xml, callback) {
    wrench.mkdirSyncRecursive(outputRoot, 0777);
    fs.writeFile(util.format('%s/tsung.xml', outputRoot), xml, 'utf-8', callback);
}

var printErr = function(err, msg) {
    console.log(msg);
    console.log(err);
    console.log(err.stack);
}

// first parse and build the data model
generateDataConfiguration();

// then gather user information
promptSuite(function() {
    TsungUtil.generateTestsForSuite(runner, suite, function(err) {
        if (err) {
            return printErr('Error generating tests for suite.');
        }
        promptClient(function() {
            promptServer(function() {
                promptPhases(function() {
                    rl.close();
                    runner.toXml(function(xml) {
                        packageTestRunner(xml, function(err) {
                            if (err) {
                                return printErr('Error packaging up the test runner.', err);
                            }
                            console.log('Test successfully generated in %s', outputRoot);
                        });
                    });
                });
            });
        });
    });
});
<|endoftext|>


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

(function () {
    "use strict";

    var fs = require("fs"),
        resolve = require("path").resolve,
        mkdirp = require("mkdirp"),
        tmp = require("tmp"),
        Q = require("q"),
        convert = require("./lib/convert"),
        xpm2png = require("./lib/xpm2png");

    var DELAY_TO_WAIT_UNTIL_USER_DONE = 300;

    var _generator = null,
        _photoshopPath = null,
        _assetGenerationDir = null,
        _changeContextPerLayer = {};

    // TODO: Once we get the layer change management/updating right, we should add a
    // big comment at the top of this file explaining how this all works. In particular
    // we should explain what contexts are, and how we manage scheduling updates.

    function getUserHomeDirectory() {
        return process.env[(process.platform === "win32") ? "USERPROFILE" : "HOME"];
    }

    function savePixmap(pixmap, filename) {
        var fileCompleteDeferred = Q.defer();

        _generator.publish("assets.debug.dump", "dumping " + filename);

        var args = ["-", "-size", pixmap.width + "x" + pixmap.height, "png:-"];
        var proc = convert(args, _photoshopPath);
        var fileStream = fs.createWriteStream(filename);
        var stderr = "";

        proc.stderr.on("data", function (chunk) { stderr += chunk; });
        proc.stdout.on("close", function () {
            fileCompleteDeferred.resolve(filename);
        });

        xpm2png(pixmap, proc.stdin.end.bind(proc.stdin));
        proc.stdout.pipe(fileStream);

        proc.stderr.on("close", function () {
            if (stderr) {
                var error = "error from ImageMagick: " + stderr;
                _generator.publish("assets.error.convert", error);
                fileCompleteDeferred.reject(error);
            }
        });

        return fileCompleteDeferred.promise;
    }

    function handleImageChanged(document) {
        if (document.id && document.layers) {
            document.layers.forEach(function (layer) {
                handleImageChangedForLayer(document, layer);
            });
        }
    }

    function handleImageChangedForLayer(document, layer) {
        if (!_assetGenerationDir) {
            return;
        }

        var contextID = document.id + "-" + layer.id;

        if (!_changeContextPerLayer[contextID]) {
            // Initialize the context object for this layer.
            // It will be deleted again once an update has finished
            // without the image changing during the update.
            _changeContextPerLayer[contextID] = {
                // Store the context ID here so the context can be deleted by finishLayerUpdate
                id:                 contextID,
                document:           document,
                layer:              layer,
                updateIsScheduled:  false,
                updateIsObsolete:   false,
                updateDelayTimeout: null
            };
        }

        scheduleLayerUpdate(_changeContextPerLayer[contextID]);
    }

    // Run the update now if none is in progress, or wait until the current one is finished
    function scheduleLayerUpdate(changeContext) {
        // If no update is scheduled or the scheduled update is still being delayed, start from scratch
        if (!changeContext.updateIsScheduled || changeContext.updateDelayTimeout) {
            changeContext.updateIsScheduled = true;
            clearTimeout(changeContext.updateDelayTimeout);

            changeContext.updateDelayTimeout = setTimeout(function () {
                changeContext.updateDelayTimeout = null;
                startLayerUpdate(changeContext).fin(function () {
                    finishLayerUpdate(changeContext);
                });
            }, DELAY_TO_WAIT_UNTIL_USER_DONE);
        }
        // Otherwise, mark the scheduled update as obsolete so we can start over when it's done
        else if (!changeContext.updateIsObsolete) {
            changeContext.updateIsObsolete = true;
        }
    }

    // Start a new update
    function startLayerUpdate(changeContext) {
        var layerUpdatedDeferred = Q.defer();

        var layer    = changeContext.layer,
            fileName = changeContext.document.id + "-" + changeContext.layer.id + ".png",
            path     = resolve(_assetGenerationDir, fileName);

        function deleteLayerImage() {
            // Delete the image for the empty layer
            fs.unlink(path, function (err) {
                if (err) {
                    layerUpdatedDeferred.reject(err);
                } else {
                    layerUpdatedDeferred.resolve();
                }
            });
        }

        function createLayerImage() {
            _generator.getPixmap(changeContext.layer.id, 100).then(
                function (pixmap) {
                    // Prevent an error after deleting a layer's contents, resulting in a 0x0 pixmap
                    if (pixmap.width === 0 || pixmap.height === 0) {
                        deleteLayerImage();
                    }
                    else {
                        tmp.tmpName(function (err, tmpPath) {
                            if (err) {
                                layerUpdatedDeferred.reject(err);
                                return;
                            }
                            // Save the image in a temporary file
                            savePixmap(pixmap, tmpPath)
                                .fail(function (err) {
                                    layerUpdatedDeferred.reject(err);
                                })
                                // When ImageMagick is done
                                .done(function () {
                                    // ...move the temporary file to the desired location
                                    fs.rename(tmpPath, path, function (err) {
                                        if (err) {
                                            layerUpdatedDeferred.reject(err);
                                        } else {
                                            layerUpdatedDeferred.resolve();
                                        }
                                    });
                                });
                        });
                    }
                },
                function (err) {
                    _generator.publish("assets.error.getPixmap", "Error: " + err);
                    layerUpdatedDeferred.reject(err);
                }
            );
        }

        if (layer.added) {
            // Nothing to do since the layer is empty
            layerUpdatedDeferred.resolve();
        }
        else if (layer.removed) {
            // Delete the image if the layer was removed
            deleteLayerImage();
        }
        else if (layer.pixels) {
            // Update the layer image since its pixels were changed
            createLayerImage();
        }
        else {
            console.warn("Unknown type of layer change", layer);
            layerUpdatedDeferred.reject();
        }

        return layerUpdatedDeferred.promise;
    }

    // Run a pending update if necessary
    function finishLayerUpdate(changeContext) {
        changeContext.updateIsScheduled = false;
        // If the update is obsolete, schedule another one right after
        // This update will still be delayed to give Photoshop some time to catch its breath
        if (changeContext.updateIsObsolete) {
            changeContext.updateIsObsolete = false;
            scheduleLayerUpdate(changeContext);
        }
        // This is the final update for now: clean up
        else {
            delete _changeContextPerLayer[changeContext.id];
        }
    }

    function init(generator) {
        _generator = generator;

        // TODO: Much of this initialization is currently temporary. Once
        // we have storage of assets in the correct location implemented, we
        // should rewrite this to be more structured. The steps of init should
        // be something like:
        //
        // 1. Get PS path
        // 2. Register for PS events we care about
        // 3. Get document info on current document
        // 4. Initiate asset generation on current document if enabled
        //

        _generator.getPhotoshopPath().done(
            function (path) {
                _photoshopPath = path;
                _generator.subscribe("photoshop.event.imageChanged", handleImageChanged);
            },
            function (err) {
                _generator.publish(
                    "assets.error.init",
                    "Could not get photoshop path: " + err
                );
            }
        );

        // create a place to save assets
        var homeDir = getUserHomeDirectory();
        if (homeDir) {
            var newDir = resolve(homeDir, "Desktop", "generator-assets");
            mkdirp(newDir, function (err) {
                if (err) {
                    _generator.publish(
                        "assets.error.init",
                        "Could not create directory '" + newDir + "', no assets will be dumped"
                    );
                } else {
                    _assetGenerationDir = newDir;
                }
            });
        } else {
            _generator.publish(
                "assets.error.init",
                "Could not locate home directory in env vars, no assets will be dumped"
            );
        }
    }

    exports.init = init;

}());<commit_msg>Always update layers unless they are removed - just in case there is a visible change. layer.pixels is only set for pixel layers, not for shape layers.<commit_after>/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

(function () {
    "use strict";

    var fs = require("fs"),
        resolve = require("path").resolve,
        mkdirp = require("mkdirp"),
        tmp = require("tmp"),
        Q = require("q"),
        convert = require("./lib/convert"),
        xpm2png = require("./lib/xpm2png");

    var DELAY_TO_WAIT_UNTIL_USER_DONE = 300;

    var _generator = null,
        _photoshopPath = null,
        _assetGenerationDir = null,
        _changeContextPerLayer = {};

    // TODO: Once we get the layer change management/updating right, we should add a
    // big comment at the top of this file explaining how this all works. In particular
    // we should explain what contexts are, and how we manage scheduling updates.

    function getUserHomeDirectory() {
        return process.env[(process.platform === "win32") ? "USERPROFILE" : "HOME"];
    }

    function savePixmap(pixmap, filename) {
        var fileCompleteDeferred = Q.defer();

        _generator.publish("assets.debug.dump", "dumping " + filename);

        var args = ["-", "-size", pixmap.width + "x" + pixmap.height, "png:-"];
        var proc = convert(args, _photoshopPath);
        var fileStream = fs.createWriteStream(filename);
        var stderr = "";

        proc.stderr.on("data", function (chunk) { stderr += chunk; });
        proc.stdout.on("close", function () {
            fileCompleteDeferred.resolve(filename);
        });

        xpm2png(pixmap, proc.stdin.end.bind(proc.stdin));
        proc.stdout.pipe(fileStream);

        proc.stderr.on("close", function () {
            if (stderr) {
                var error = "error from ImageMagick: " + stderr;
                _generator.publish("assets.error.convert", error);
                fileCompleteDeferred.reject(error);
            }
        });

        return fileCompleteDeferred.promise;
    }

    function handleImageChanged(document) {
        if (document.id && document.layers) {
            document.layers.forEach(function (layer) {
                handleImageChangedForLayer(document, layer);
            });
        }
    }

    function handleImageChangedForLayer(document, layer) {
        if (!_assetGenerationDir) {
            return;
        }

        var contextID = document.id + "-" + layer.id;

        if (!_changeContextPerLayer[contextID]) {
            // Initialize the context object for this layer.
            // It will be deleted again once an update has finished
            // without the image changing during the update.
            _changeContextPerLayer[contextID] = {
                // Store the context ID here so the context can be deleted by finishLayerUpdate
                id:                 contextID,
                document:           document,
                layer:              layer,
                updateIsScheduled:  false,
                updateIsObsolete:   false,
                updateDelayTimeout: null
            };
        }

        scheduleLayerUpdate(_changeContextPerLayer[contextID]);
    }

    // Run the update now if none is in progress, or wait until the current one is finished
    function scheduleLayerUpdate(changeContext) {
        // If no update is scheduled or the scheduled update is still being delayed, start from scratch
        if (!changeContext.updateIsScheduled || changeContext.updateDelayTimeout) {
            changeContext.updateIsScheduled = true;
            clearTimeout(changeContext.updateDelayTimeout);

            changeContext.updateDelayTimeout = setTimeout(function () {
                changeContext.updateDelayTimeout = null;
                startLayerUpdate(changeContext).fin(function () {
                    finishLayerUpdate(changeContext);
                });
            }, DELAY_TO_WAIT_UNTIL_USER_DONE);
        }
        // Otherwise, mark the scheduled update as obsolete so we can start over when it's done
        else if (!changeContext.updateIsObsolete) {
            changeContext.updateIsObsolete = true;
        }
    }

    // Start a new update
    function startLayerUpdate(changeContext) {
        var layerUpdatedDeferred = Q.defer();

        var layer    = changeContext.layer,
            fileName = changeContext.document.id + "-" + changeContext.layer.id + ".png",
            path     = resolve(_assetGenerationDir, fileName);

        function deleteLayerImage() {
            // Delete the image for the empty layer
            fs.unlink(path, function (err) {
                if (err) {
                    layerUpdatedDeferred.reject(err);
                } else {
                    layerUpdatedDeferred.resolve();
                }
            });
        }

        function createLayerImage() {
            _generator.getPixmap(changeContext.layer.id, 100).then(
                function (pixmap) {
                    // Prevent an error after deleting a layer's contents, resulting in a 0x0 pixmap
                    if (pixmap.width === 0 || pixmap.height === 0) {
                        deleteLayerImage();
                    }
                    else {
                        tmp.tmpName(function (err, tmpPath) {
                            if (err) {
                                layerUpdatedDeferred.reject(err);
                                return;
                            }
                            // Save the image in a temporary file
                            savePixmap(pixmap, tmpPath)
                                .fail(function (err) {
                                    layerUpdatedDeferred.reject(err);
                                })
                                // When ImageMagick is done
                                .done(function () {
                                    // ...move the temporary file to the desired location
                                    fs.rename(tmpPath, path, function (err) {
                                        if (err) {
                                            layerUpdatedDeferred.reject(err);
                                        } else {
                                            layerUpdatedDeferred.resolve();
                                        }
                                    });
                                });
                        });
                    }
                },
                function (err) {
                    _generator.publish("assets.error.getPixmap", "Error: " + err);
                    layerUpdatedDeferred.reject(err);
                }
            );
        }

        if (layer.removed) {
            // Delete the image if the layer was removed
            deleteLayerImage();
        }
        else {
            // Update the layer image
            // The change could be layer.pixels, layer.added, layer.path, ...
            // Always update if it has been added because it could
            // have been dragged & dropped or copied & pasted,
            // and therefore might not be empty like new layers
            createLayerImage();
        }

        return layerUpdatedDeferred.promise;
    }

    // Run a pending update if necessary
    function finishLayerUpdate(changeContext) {
        changeContext.updateIsScheduled = false;
        // If the update is obsolete, schedule another one right after
        // This update will still be delayed to give Photoshop some time to catch its breath
        if (changeContext.updateIsObsolete) {
            changeContext.updateIsObsolete = false;
            scheduleLayerUpdate(changeContext);
        }
        // This is the final update for now: clean up
        else {
            delete _changeContextPerLayer[changeContext.id];
        }
    }

    function init(generator) {
        _generator = generator;

        // TODO: Much of this initialization is currently temporary. Once
        // we have storage of assets in the correct location implemented, we
        // should rewrite this to be more structured. The steps of init should
        // be something like:
        //
        // 1. Get PS path
        // 2. Register for PS events we care about
        // 3. Get document info on current document
        // 4. Initiate asset generation on current document if enabled
        //

        _generator.getPhotoshopPath().done(
            function (path) {
                _photoshopPath = path;
                _generator.subscribe("photoshop.event.imageChanged", handleImageChanged);
            },
            function (err) {
                _generator.publish(
                    "assets.error.init",
                    "Could not get photoshop path: " + err
                );
            }
        );

        // create a place to save assets
        var homeDir = getUserHomeDirectory();
        if (homeDir) {
            var newDir = resolve(homeDir, "Desktop", "generator-assets");
            mkdirp(newDir, function (err) {
                if (err) {
                    _generator.publish(
                        "assets.error.init",
                        "Could not create directory '" + newDir + "', no assets will be dumped"
                    );
                } else {
                    _assetGenerationDir = newDir;
                }
            });
        } else {
            _generator.publish(
                "assets.error.init",
                "Could not locate home directory in env vars, no assets will be dumped"
            );
        }
    }

    exports.init = init;

}());<|endoftext|>


<commit_before>var geoButton = document.querySelector('.geolocation-button');
geoButton.addEventListener('click', geoFindMe);

function geoFindMe() {
  var output = document.getElementById("out");

  if (!navigator.geolocation){
    output.innerHTML = "
Geolocation is not supported by your browser
";
    return;
  }

  function success(position) {
    var latitude  = position.coords.latitude.toString().split(".")[0];
    var longitude = position.coords.longitude.toString().split(".")[0];
    output.innerHTML = 'Woof!';
    var myUrl = "http://104.198.245.168:3000/weather?lat=" + latitude + "&lon=" + longitude;
    getWeather(myUrl);
  }

  function error() {
    output.innerHTML = "Error getting location";
  }

  output.innerHTML = "
Locating…
";

  navigator.geolocation.getCurrentPosition(success, error);
}

$( document ).ready(function() {
  $('.location-button').click(function(event) {
    event.preventDefault();
    var zipCode = $('.zipcode-input').val();
    var myUrl = "http://104.198.245.168:3000/weather?zip=" + zipCode + ",us";
    getWeather(myUrl);
  });

  $('.fav-location').click(function(event) {
    event.preventDefault();
    var cityId = $( this ).attr( "data-city-id" );
    var myUrl = "http://104.198.245.168:3000/weather?id=" + cityId;
    getWeather(myUrl);
  });

  geoFindMe();

});

function getWeather(myUrl) {
  $.ajax({
    method: "GET",
    url: myUrl,
  })
    .done(function( weatherData ) {
      // console.log( JSON.stringify(weatherData, null, 2) );
      $('.name').text(weatherData.name);
      $('.description').text(weatherData.weather[0].description);
      $('.wind').text(weatherData.wind.speed);
      $('.temp').text(weatherData.main.temp);
      $('.humidity').text(weatherData.main.humidity);

      //update images
      var iconId = weatherData.weather[0].icon;

      $('.yoshi-image').attr("src", 'images/yoshi/' + iconId + '.png');
      $('body').css('background-image', 'url(images/bg/' + iconId + '.jpg)');

      if(iconId === '13d' || iconId === '13n'){
        $('.yoshi-image').addClass('snow');
      } else {
        $('.yoshi-image').removeClass('snow');
      }
    });
}
<commit_msg>setting default location to Maui when using http connection<commit_after>var geoButton = document.querySelector('.geolocation-button');
geoButton.addEventListener('click', geoFindMe);

function geoFindMe() {
  var output = document.getElementById("out");

  if (!navigator.geolocation){
    output.innerHTML = "
Geolocation is not supported by your browser
";
    return;
  }

  function success(position) {
    var latitude  = position.coords.latitude.toString().split(".")[0];
    var longitude = position.coords.longitude.toString().split(".")[0];
    output.innerHTML = 'Woof!';
    var myUrl = "http://104.198.245.168:3000/weather?lat=" + latitude + "&lon=" + longitude;
    getWeather(myUrl);
  }

  function error() {
    output.innerHTML = "Error getting location";
  }

  output.innerHTML = "
Locating…
";

  navigator.geolocation.getCurrentPosition(success, error);
}

$( document ).ready(function() {
  $('.location-button').click(function(event) {
    event.preventDefault();
    var zipCode = $('.zipcode-input').val();
    var myUrl = "http://104.198.245.168:3000/weather?zip=" + zipCode + ",us";
    getWeather(myUrl);
  });

  $('.fav-location').click(function(event) {
    event.preventDefault();
    var cityId = $( this ).attr( "data-city-id" );
    var myUrl = "http://104.198.245.168:3000/weather?id=" + cityId;
    getWeather(myUrl);
  });

  //enable w/ https
  //geoFindMe();

  //use w/ http, default to Hawaii!
  getWeather("http://104.198.245.168:3000/weather?id=5850871");

});

function getWeather(myUrl) {
  $.ajax({
    method: "GET",
    url: myUrl,
  })
    .done(function( weatherData ) {
      // console.log( JSON.stringify(weatherData, null, 2) );
      $('.name').text(weatherData.name);
      $('.description').text(weatherData.weather[0].description);
      $('.wind').text(weatherData.wind.speed);
      $('.temp').text(weatherData.main.temp);
      $('.humidity').text(weatherData.main.humidity);

      //update images
      var iconId = weatherData.weather[0].icon;

      $('.yoshi-image').attr("src", 'images/yoshi/' + iconId + '.png');
      $('body').css('background-image', 'url(images/bg/' + iconId + '.jpg)');

      if(iconId === '13d' || iconId === '13n'){
        $('.yoshi-image').addClass('snow');
      } else {
        $('.yoshi-image').removeClass('snow');
      }
    });
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

/*jshint node:true, indent:2, curly:false, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
regexp:true, undef:true, strict:true, trailing:true, white:true */
/*global X:true, Backbone:true, _:true, XM:true, XT:true*/

Backbone = require("backbone");
_ = require("underscore");

(function () {
  "use strict";

  var options = require("./lib/options"),
    schema = false,
    privs = false,
    sessionObjectLoaded,
    schemaOptions = {},
    privsOptions = {};

  /**
   * Include the X framework.
   */
  require("xt");

  // Loop through files and load the dependencies.
  X.relativeDependsPath = "";
  X.depends = function () {
    var dir = X.relativeDependsPath,
      files = X.$A(arguments),
      pathBeforeRecursion;

    _.each(files, function (file) {
      if (X.fs.statSync(X.path.join(dir, file)).isDirectory()) {
        pathBeforeRecursion = X.relativeDependsPath;
        X.relativeDependsPath = X.path.join(dir, file);
        X.depends("package.js");
        X.relativeDependsPath = pathBeforeRecursion;
      } else {
        require(X.path.join(dir, file));
      }
    });
  };

  // Load other xTuple libraries using X.depends above.
  require("backbone-relational");
  X.relativeDependsPath = X.path.join(X.basePath, "node_modules/tools/source");
  require("tools");
  X.relativeDependsPath = X.path.join(X.basePath, "node_modules/backbone-x/source");
  require("backbone-x");
  Backbone.XM = XM;

  // Argh!!! Hack because `XT` has it's own string format function that
  // is incompatible with `X`....
  String.prototype.f = function () {
    return X.String.format.apply(this, arguments);
  };

  // Another hack: quiet the logs here.
  XT.log = function () {};

  // Make absolutely sure we're going to start.
  options.autoStart = true;

  //X.debugging = true;

  // Set the options and start the servers the OLD way.
  X.setup(options);

  // Initiate the internal session.
  sessionObjectLoaded = function () {
    if (schema && privs) {
      // Start polling for expired user sessions and delete the records.
      X.cachePollingInterval = setInterval(X.Session.pollCache, 10000);
      X.addCleanupTask(function () {
        clearInterval(X.cachePollingInterval);
      });
      // TODO - We need a start up script that handles async in the right order.
      // Sticking this here so it gets called after the session schema is loaded.
      sessionStore.loadCache();
    }
  };
  schemaOptions.success = function () {
    schema = true;
    sessionObjectLoaded();
  };
  privsOptions.success = function () {
    privs = true;
    sessionObjectLoaded();
  };
  privsOptions.username = X.options.globalDatabase.nodeUsername;

  XT.session = Object.create(XT.Session);
  XT.session.loadSessionObjects(XT.session.SCHEMA, schemaOptions);
  XT.session.loadSessionObjects(XT.session.PRIVILEGES, privsOptions);

}());

/**
 * Module dependencies.
 */
var express = require('express'),
    passport = require('passport'),
    oauth2 = require('./oauth2/oauth2'),
    site = require('./oauth2/site'),
    routes = require('./routes/routes'),
    socket = require('socket.io'),
    user = require('./oauth2/user');

/**
 * Express configuration.
 */
var app = express(),
// TODO - can we get to this through express? connect.utils.parseSignedCookie
    connect           = require('connect'),
    parseSignedCookie = connect.utils.parseSignedCookie,
    //MemoryStore = express.session.MemoryStore,
    XTPGStore = require('./oauth2/db/connect-xt-pg')(express),
    cookie = require('express/node_modules/cookie'),
    io,
    //sessionStore = new MemoryStore();
    sessionStore = new XTPGStore({ hybridCache: true });

app.configure(function () {
  "use strict";

  // gzip all files served.
  app.use(express.compress());
  // Add a basic view engine that will render files from "views" directory.
  app.set('view engine', 'ejs');
  // TODO - This outputs access logs like apache2.
  //app.use(express.logger());
  app.use(express.cookieParser());
  app.use(express.bodyParser());
  app.use(express.session({ store: sessionStore, secret: '.T#T@r5EkPM*N@C%9K-iPW!+T' }));
  app.use(passport.initialize());
  app.use(passport.session());
  app.use(app.router);
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

/**
 * Passport configuration.
 */
require('./oauth2/passport');

/**
 * Setup HTTP routes and handlers.
 */
//app.get('/', site.index);
app.use('/assets', express.static('views/login/assets'));
app.use('/client', express.static('www/client'));
app.use('/public-extensions', express.static('www/public-extensions'));
app.use('/private-extensions', express.static('www/private-extensions'));
app.get('/account', site.account);
//app.post('/export', routes.expor); TODO: implement, or delete the route

app.get('/dialog/authorize', oauth2.authorization);
app.post('/dialog/authorize/decision', oauth2.decision);
app.post('/oauth/token', oauth2.token);

app.get('/api/userinfo', user.info);

app.get('/', routes.loginForm);
app.post('/login', routes.login);
app.get('/login/scope', routes.scopeForm);
app.post('/login/scopeSubmit', routes.scope);
app.get('/logout', routes.logout);

app.all('/email', routes.email);
app.all('/extensions', routes.extensions);
app.get('/file', routes.file);
app.get('/maintenance', routes.maintenance);
app.get('/report', routes.report);
app.get('/resetPassword', routes.resetPassword);

/**
 * Start the express server. This is the NEW way.
 */
// TODO - Active browser sessions can make calls to this server when it hasn't fully started.
// Need a way to get everything loaded BEFORE we start listening.  Might just move this to the end...
io = socket.listen(app.listen(2000), {log: false});

// TODO: start up a server on 80 and throw everything to the redirect route
//app.get('/redirect', routes.redirect);

/**
 * Setup socket.io routes and handlers.
 */
io.of('/clientsock').authorization(function (handshakeData, callback) {
  "use strict";

  //console.log("##### socket.io handshakeData: ", handshakeData);

  if (handshakeData.headers.cookie) {
    // save parsedSessionId to handshakeData
    handshakeData.cookie = cookie.parse(handshakeData.headers.cookie);
    handshakeData.sessionId = parseSignedCookie(handshakeData.cookie['connect.sid'], '.T#T@r5EkPM*N@C%9K-iPW!+T');

    //console.log("##### send socket.io to deserializeUser: ", handshakeData.sessionId);

    sessionStore.get(handshakeData.sessionId, function (err, session) {
      var userHash = session[passport._key],
        user = userHash.user,
        username = userHash.username,
        organization = userHash.organization;

      passport.deserializeUser(user, function (err, user) {
        if (err || !user) {
          console.log("##### socket.io deserializeUser FAILED: ", err, user);
          return;
        }
        //console.log("##### socket.io deserializeUser: ", user);
        handshakeData.user = user;
        handshakeData.username = username;
        handshakeData.organization = organization;
        callback(null, true);
      });
    });
  }
  callback(null, true);

  //console.log("##### socket.io handshakeData: ", handshakeData);
  //handshakeData.foo = 'baz';
  callback(null, true);
}).on('connection', function (socket) {
  // XXX TODO we need to get this session data from somewhere

  //console.log("######### socket.io connected with socket: ", socket);

  socket.on('session', function (data, callback) {
    var shake = socket.handshake,
      session = {passport: {username: shake.username, organization: shake.organization, user: shake.user.id}};
    console.log("######### session socket.io with data: ", data);
    callback({data: session});
  });

  // run this from the client:
  // XT.dataSource.retrieveRecord("XM.State", 2, {"id":2,"cascade":true,"databaseType":"instance"});
  socket.on('function/retrieveRecord', function (data, callback) {
    //console.log("######### function/retrieveRecord socket.io with data: ", data);
    var shake = socket.handshake,
      session = {passport: {username: shake.username, organization: shake.organization, user: shake.user.id}};
    routes.retrieveEngine(data.payload, session, callback);
  });

  // run this from client:
  // XT.dataSource.fetch({"query":{"orderBy":[{"attribute":"code"}],"recordType":"XM.Honorific"},"force":true,"parse":true,"databaseType":"instance"});
  socket.on('function/fetch', function (data, callback) {
    //console.log("######### function/fetch socket.io with data: ", data);
    var shake = socket.handshake,
      session = {passport: {username: shake.username, organization: shake.organization, user: shake.user.id}};
    routes.fetchEngine(data.payload, session, callback);
  });

  // run this from client:
  // XT.dataSource.dispatch("XT.Session", "settings", null, {success: function () {console.log(arguments);}, error: function () {console.log(arguments);}});
  socket.on('function/dispatch', function (data, callback) {
    //console.log("######### function/dispatch socket.io with data: ", data);
    var shake = socket.handshake,
      session = {passport: {username: shake.username, organization: shake.organization, user: shake.user.id}};
    routes.dispatchEngine(data.payload, session, callback);
  });

  // run this from the client:
  // ???
  // TODO: the first argument to XT.dataSource.commit() is a model and therefore a bit tough to mock
  // XXX untested
  socket.on('function/commitRecord', function (data, callback) {
    //console.log("######### function/commitRecord socket.io with data: ", data);
    var shake = socket.handshake,
      session = {passport: {username: shake.username, organization: shake.organization, user: shake.user.id}};
    routes.commitEngine(data.payload, session, callback);
  });

  // Tell the client you're connected.
  socket.emit("ok");
  //socket.emit("error");
  //socket.emit("debug");
  //socket.emit("timeout");
  //socket.emit("disconnect");

});

/**
 * Job loading section.
 *
 * The following are jobs that must be started at start up or scheduled to run periodically.
 */

// Load XTPGStore into MemoryStore at startup for caching.
    // TODO - Add an option flag that turns on use of Express's MemoryStore as a memory cache.
    // TODO - If we ever run multiple processes/servers, MemoryStore must be replaced with something
    // all processes/servers can use/share and keep in sync like Redis.
// TODO - Call XTPGStore loadCache function.
// See sessionObjectLoaded() above were this is for now.
//sessionStore.loadCache();

// TODO - Check pid file to see if this is already running.
// Kill process or create  new pid file.
<commit_msg>issue #19291: removed old XXX<commit_after>#!/usr/bin/env node

/*jshint node:true, indent:2, curly:false, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
regexp:true, undef:true, strict:true, trailing:true, white:true */
/*global X:true, Backbone:true, _:true, XM:true, XT:true*/

Backbone = require("backbone");
_ = require("underscore");

(function () {
  "use strict";

  var options = require("./lib/options"),
    schema = false,
    privs = false,
    sessionObjectLoaded,
    schemaOptions = {},
    privsOptions = {};

  /**
   * Include the X framework.
   */
  require("xt");

  // Loop through files and load the dependencies.
  X.relativeDependsPath = "";
  X.depends = function () {
    var dir = X.relativeDependsPath,
      files = X.$A(arguments),
      pathBeforeRecursion;

    _.each(files, function (file) {
      if (X.fs.statSync(X.path.join(dir, file)).isDirectory()) {
        pathBeforeRecursion = X.relativeDependsPath;
        X.relativeDependsPath = X.path.join(dir, file);
        X.depends("package.js");
        X.relativeDependsPath = pathBeforeRecursion;
      } else {
        require(X.path.join(dir, file));
      }
    });
  };

  // Load other xTuple libraries using X.depends above.
  require("backbone-relational");
  X.relativeDependsPath = X.path.join(X.basePath, "node_modules/tools/source");
  require("tools");
  X.relativeDependsPath = X.path.join(X.basePath, "node_modules/backbone-x/source");
  require("backbone-x");
  Backbone.XM = XM;

  // Argh!!! Hack because `XT` has it's own string format function that
  // is incompatible with `X`....
  String.prototype.f = function () {
    return X.String.format.apply(this, arguments);
  };

  // Another hack: quiet the logs here.
  XT.log = function () {};

  // Make absolutely sure we're going to start.
  options.autoStart = true;

  //X.debugging = true;

  // Set the options and start the servers the OLD way.
  X.setup(options);

  // Initiate the internal session.
  sessionObjectLoaded = function () {
    if (schema && privs) {
      // Start polling for expired user sessions and delete the records.
      X.cachePollingInterval = setInterval(X.Session.pollCache, 10000);
      X.addCleanupTask(function () {
        clearInterval(X.cachePollingInterval);
      });
      // TODO - We need a start up script that handles async in the right order.
      // Sticking this here so it gets called after the session schema is loaded.
      sessionStore.loadCache();
    }
  };
  schemaOptions.success = function () {
    schema = true;
    sessionObjectLoaded();
  };
  privsOptions.success = function () {
    privs = true;
    sessionObjectLoaded();
  };
  privsOptions.username = X.options.globalDatabase.nodeUsername;

  XT.session = Object.create(XT.Session);
  XT.session.loadSessionObjects(XT.session.SCHEMA, schemaOptions);
  XT.session.loadSessionObjects(XT.session.PRIVILEGES, privsOptions);

}());

/**
 * Module dependencies.
 */
var express = require('express'),
    passport = require('passport'),
    oauth2 = require('./oauth2/oauth2'),
    site = require('./oauth2/site'),
    routes = require('./routes/routes'),
    socket = require('socket.io'),
    user = require('./oauth2/user');

/**
 * Express configuration.
 */
var app = express(),
// TODO - can we get to this through express? connect.utils.parseSignedCookie
    connect           = require('connect'),
    parseSignedCookie = connect.utils.parseSignedCookie,
    //MemoryStore = express.session.MemoryStore,
    XTPGStore = require('./oauth2/db/connect-xt-pg')(express),
    cookie = require('express/node_modules/cookie'),
    io,
    //sessionStore = new MemoryStore();
    sessionStore = new XTPGStore({ hybridCache: true });

app.configure(function () {
  "use strict";

  // gzip all files served.
  app.use(express.compress());
  // Add a basic view engine that will render files from "views" directory.
  app.set('view engine', 'ejs');
  // TODO - This outputs access logs like apache2.
  //app.use(express.logger());
  app.use(express.cookieParser());
  app.use(express.bodyParser());
  app.use(express.session({ store: sessionStore, secret: '.T#T@r5EkPM*N@C%9K-iPW!+T' }));
  app.use(passport.initialize());
  app.use(passport.session());
  app.use(app.router);
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

/**
 * Passport configuration.
 */
require('./oauth2/passport');

/**
 * Setup HTTP routes and handlers.
 */
//app.get('/', site.index);
app.use('/assets', express.static('views/login/assets'));
app.use('/client', express.static('www/client'));
app.use('/public-extensions', express.static('www/public-extensions'));
app.use('/private-extensions', express.static('www/private-extensions'));
app.get('/account', site.account);
//app.post('/export', routes.expor); TODO: implement, or delete the route

app.get('/dialog/authorize', oauth2.authorization);
app.post('/dialog/authorize/decision', oauth2.decision);
app.post('/oauth/token', oauth2.token);

app.get('/api/userinfo', user.info);

app.get('/', routes.loginForm);
app.post('/login', routes.login);
app.get('/login/scope', routes.scopeForm);
app.post('/login/scopeSubmit', routes.scope);
app.get('/logout', routes.logout);

app.all('/email', routes.email);
app.all('/extensions', routes.extensions);
app.get('/file', routes.file);
app.get('/maintenance', routes.maintenance);
app.get('/report', routes.report);
app.get('/resetPassword', routes.resetPassword);

/**
 * Start the express server. This is the NEW way.
 */
// TODO - Active browser sessions can make calls to this server when it hasn't fully started.
// Need a way to get everything loaded BEFORE we start listening.  Might just move this to the end...
io = socket.listen(app.listen(2000), {log: false});

// TODO: start up a server on 80 and throw everything to the redirect route
//app.get('/redirect', routes.redirect);

/**
 * Setup socket.io routes and handlers.
 */
io.of('/clientsock').authorization(function (handshakeData, callback) {
  "use strict";

  //console.log("##### socket.io handshakeData: ", handshakeData);

  if (handshakeData.headers.cookie) {
    // save parsedSessionId to handshakeData
    handshakeData.cookie = cookie.parse(handshakeData.headers.cookie);
    handshakeData.sessionId = parseSignedCookie(handshakeData.cookie['connect.sid'], '.T#T@r5EkPM*N@C%9K-iPW!+T');

    //console.log("##### send socket.io to deserializeUser: ", handshakeData.sessionId);

    sessionStore.get(handshakeData.sessionId, function (err, session) {
      var userHash = session[passport._key],
        user = userHash.user,
        username = userHash.username,
        organization = userHash.organization;

      passport.deserializeUser(user, function (err, user) {
        if (err || !user) {
          console.log("##### socket.io deserializeUser FAILED: ", err, user);
          return;
        }
        //console.log("##### socket.io deserializeUser: ", user);
        handshakeData.user = user;
        handshakeData.username = username;
        handshakeData.organization = organization;
        callback(null, true);
      });
    });
  }
  callback(null, true);

  //console.log("##### socket.io handshakeData: ", handshakeData);
  //handshakeData.foo = 'baz';
  callback(null, true);
}).on('connection', function (socket) {
  //console.log("######### socket.io connected with socket: ", socket);

  socket.on('session', function (data, callback) {
    var shake = socket.handshake,
      session = {passport: {username: shake.username, organization: shake.organization, user: shake.user.id}};
    console.log("######### session socket.io with data: ", data);
    callback({data: session});
  });

  // run this from the client:
  // XT.dataSource.retrieveRecord("XM.State", 2, {"id":2,"cascade":true,"databaseType":"instance"});
  socket.on('function/retrieveRecord', function (data, callback) {
    //console.log("######### function/retrieveRecord socket.io with data: ", data);
    var shake = socket.handshake,
      session = {passport: {username: shake.username, organization: shake.organization, user: shake.user.id}};
    routes.retrieveEngine(data.payload, session, callback);
  });

  // run this from client:
  // XT.dataSource.fetch({"query":{"orderBy":[{"attribute":"code"}],"recordType":"XM.Honorific"},"force":true,"parse":true,"databaseType":"instance"});
  socket.on('function/fetch', function (data, callback) {
    //console.log("######### function/fetch socket.io with data: ", data);
    var shake = socket.handshake,
      session = {passport: {username: shake.username, organization: shake.organization, user: shake.user.id}};
    routes.fetchEngine(data.payload, session, callback);
  });

  // run this from client:
  // XT.dataSource.dispatch("XT.Session", "settings", null, {success: function () {console.log(arguments);}, error: function () {console.log(arguments);}});
  socket.on('function/dispatch', function (data, callback) {
    //console.log("######### function/dispatch socket.io with data: ", data);
    var shake = socket.handshake,
      session = {passport: {username: shake.username, organization: shake.organization, user: shake.user.id}};
    routes.dispatchEngine(data.payload, session, callback);
  });

  // run this from the client:
  // ???
  // TODO: the first argument to XT.dataSource.commit() is a model and therefore a bit tough to mock
  // XXX untested
  socket.on('function/commitRecord', function (data, callback) {
    //console.log("######### function/commitRecord socket.io with data: ", data);
    var shake = socket.handshake,
      session = {passport: {username: shake.username, organization: shake.organization, user: shake.user.id}};
    routes.commitEngine(data.payload, session, callback);
  });

  // Tell the client you're connected.
  socket.emit("ok");
  //socket.emit("error");
  //socket.emit("debug");
  //socket.emit("timeout");
  //socket.emit("disconnect");

});

/**
 * Job loading section.
 *
 * The following are jobs that must be started at start up or scheduled to run periodically.
 */

// Load XTPGStore into MemoryStore at startup for caching.
    // TODO - Add an option flag that turns on use of Express's MemoryStore as a memory cache.
    // TODO - If we ever run multiple processes/servers, MemoryStore must be replaced with something
    // all processes/servers can use/share and keep in sync like Redis.
// TODO - Call XTPGStore loadCache function.
// See sessionObjectLoaded() above were this is for now.
//sessionStore.loadCache();

// TODO - Check pid file to see if this is already running.
// Kill process or create  new pid file.
<|endoftext|>


<commit_before>'use strict';

let config = require('config'),
    // Dash button Setup
    dash_button = require('node-dash-button'),
    dash = dash_button(config.get('dash.mac'), null, null, 'all'),
    // Plug Setup
    plugApi = require('hs100-api'),
    plugClient = new plugApi.Client(),
    plug = plugClient.getPlug(config.get('plug')),
    // Kodi Setup
    kodi = require('kodi-ws'),
    kodiConf = config.get('kodi'),
    playMusic = function()
    {
      let url = 'plugin:\/\/plugin.video.youtube\/?path=\/root\/search&action=play_video&videoid=' + kodiConf.video;
      return kodi(kodiConf.host, kodiConf.port).then(function(connection)
      {
        /* Start the video */
        return connection.Player.Open(
          {
            item :
            {
              file : url
            }
          });
      });
    },
    stopMusic = function()
    {
      return connection.Player.GetActivePlayers()
      .then(function(players)
      {
    		/* Stop everything thats playing */
    		return Promise.all(players.map(function(player)
        {
    			return connection.Player.Stop(player.playerid);
    		}));
  	 });
    }
    ;

// Main application
dash.on("detected", () =>
{
  console.log('Dash Button : pressed');
  plug.getPowerState()
      .then((state) =>
      {
        plug.setPowerState(!state);
        if(state)
        {
          console.log('Smart Plug : switch off');
          stopMusic()
          .then(function()
          {
            console.log('Kodi : Stop all players');
          })
          .catch(function(error)
          {
            console.error(error);
          });
        }
        else
        {
          console.log('Smart Plug : switch on');
          playMusic()
          .then(function()
          {
            console.log('Kodi : Playing Music');
          })
          .catch(function(error)
          {
            console.error('Kodi : ' + error);
          });
        }
      })
      .catch(function(error)
      {
        console.error('Smart Plug : ' + error);
      })
      ;
});

console.log('Back Home Ready');
<commit_msg>fix stop players<commit_after>'use strict';

let config = require('config'),
    // Dash button Setup
    dash_button = require('node-dash-button'),
    dash = dash_button(config.get('dash.mac'), null, null, 'all'),
    // Plug Setup
    plugApi = require('hs100-api'),
    plugClient = new plugApi.Client(),
    plug = plugClient.getPlug(config.get('plug')),
    // Kodi Setup
    kodi = require('kodi-ws'),
    kodiConf = config.get('kodi'),
    playMusic = function()
    {
      let url = 'plugin:\/\/plugin.video.youtube\/?path=\/root\/search&action=play_video&videoid=' + kodiConf.video;
      return kodi(kodiConf.host, kodiConf.port).then(function(connection)
      {
        /* Start the video */
        return connection.Player.Open(
          {
            item :
            {
              file : url
            }
          });
      });
    },
    stopMusic = function()
    {
      return kodi(kodiConf.host, kodiConf.port).then(function(connection)
      {
        return connection.Player.GetActivePlayers()
        .then(function(players)
        {
      		/* Stop everything thats playing */
      		return Promise.all(players.map(function(player)
          {
      			return connection.Player.Stop(player.playerid);
      		}));
    	 });
     });
    }
    ;

// Main application
dash.on("detected", () =>
{
  console.log('Dash Button : pressed');
  plug.getPowerState()
      .then((state) =>
      {
        plug.setPowerState(!state);
        if(state)
        {
          console.log('Smart Plug : switch off');
          stopMusic()
          .then(function()
          {
            console.log('Kodi : Stop all players');
          })
          .catch(function(error)
          {
            console.error(error);
          });
        }
        else
        {
          console.log('Smart Plug : switch on');
          playMusic()
          .then(function()
          {
            console.log('Kodi : Playing Music');
          })
          .catch(function(error)
          {
            console.error('Kodi : ' + error);
          });
        }
      })
      .catch(function(error)
      {
        console.error('Smart Plug : ' + error);
      })
      ;
});

console.log('Back Home Ready');
<|endoftext|>


<commit_before>const svgNamespace = 'http://www.w3.org/2000/svg'

const weakMap = new WeakMap()

const resolve = (obj) => {
  let afterUpdate

  if (typeof obj === 'function') {
    obj = obj((cb) => {
      afterUpdate = cb
    })

    if (afterUpdate != null) {
      obj.afterUpdate = afterUpdate
    }
  }

  return obj
}

const viewZero = {
  type: 'node',
  view: 0,
  dynamic: false
}

export const skipUpdate = () => viewZero

const getListener = (key) => (e) => {
  const map = weakMap.get(e.target)

  if (map && map[key]) {
    map[key](e)
  }
}

const morphAttribute = (target, key, value, {meta, listeners}) => {
  const document = target.ownerDocument
  const remove = value == null || value === false

  if (key.indexOf('on') === 0) {
    if (!meta._read) {
      Object.assign(meta, weakMap.get(target) || {})

      meta._read = true
    }

    if (remove) {
      if (meta[key]) {
        meta[key] = null
      }
    } else {
      meta[key] = value

      if (!listeners.includes(key)) {
        listeners.push(key)

        const type = key.substring(2)

        document.addEventListener(type, getListener(key))
      }
    }
  } else {
    if (remove) {
      target.removeAttribute(key)
    } else {
      const stringified = value === true ? '' : value

      if (target.getAttribute(key) !== stringified) {
        target.setAttribute(key, stringified)
      }
    }

    if (key === 'value' || value === true || value === false) {
      if (target[key] !== value) {
        target[key] = value
      }
    }
  }
}

const morphChild = (
  target,
  childNode,
  next,
  {variables, isSameView, forceAppend, listeners}
) => {
  const document = target.ownerDocument

  const append = forceAppend || childNode == null

  let replace = false

  let currentChild = childNode

  if (next.type === 'text') {
    if (!append && childNode.nodeType !== 3) {
      replace = true
    }

    if (append || replace) {
      currentChild = document.createTextNode(next.value)
    } else if (childNode.data !== next.value) {
      childNode.data = next.value
    }
  } else {
    const tag = next.tag

    if (
      !append &&
      (childNode.nodeType !== 1 || childNode.nodeName.toLowerCase() !== tag)
    ) {
      replace = true
    }

    if (append || replace) {
      const isSvg = tag === 'svg' || target.namespaceURI === svgNamespace

      currentChild = isSvg
        ? document.createElementNS(svgNamespace, tag)
        : document.createElement(tag)
    }

    if (next.view != null) {
      morphRoot(currentChild, next, {listeners})
    } else {
      const meta = {_read: false}

      morph(currentChild, next, {variables, isSameView, meta, listeners})
    }
  }

  if (forceAppend && childNode) {
    childNode.before(currentChild)
  } else if (append) {
    target.append(currentChild)
  } else if (replace) {
    childNode.replaceWith(currentChild)
  }

  if (currentChild != null && next.afterUpdate) {
    next.afterUpdate(currentChild)
  }

  return currentChild?.nextSibling
}

const morph = (target, next, {variables, isSameView, meta, listeners}) => {
  if (isSameView && !next.dynamic) {
    return
  }

  const attributesLength = next.attributes.length

  if (attributesLength) {
    for (let i = 0, length = attributesLength; i < length; i++) {
      const attribute = next.attributes[i]

      if (isSameView && !attribute.variable) {
        continue
      }

      let value = attribute.value

      if (attribute.variable) {
        value = variables[value]
      }

      if (attribute.key) {
        morphAttribute(target, attribute.key, value, {meta, listeners})
      } else {
        for (const key of Object.keys(value)) {
          morphAttribute(target, key, value[key], {meta, listeners})
        }
      }
    }
  }

  const childrenLength = next.children.length
  let childNode = target.firstChild

  if (!meta._read) {
    Object.assign(meta, weakMap.get(target) || {})

    meta._read = true
  }

  let keys

  let prevKeys

  if (childrenLength) {
    let deopt = !isSameView

    for (let childIndex = 0; childIndex < childrenLength; childIndex++) {
      let child = next.children[childIndex]

      if (child == null) {
        continue
      }

      if (!deopt && !child.dynamic && !child.variable) {
        childNode = childNode?.nextSibling

        continue
      }

      deopt = true

      if (child.variable) {
        const variableValue = child.value

        child = variables[variableValue]

        if (child?.[Symbol.iterator] == null || typeof child === 'string') {
          child = [child]
        }

        let keyIndex = 0
        let lengthDifference

        for (let grand of child) {
          grand = resolve(grand)

          if (grand == null) grand = ''

          let keysMatch = true

          if (grand.key) {
            if (!meta._read) {
              Object.assign(meta, weakMap.get(target) || {})

              meta._read = true
            }

            if (!keys) {
              keys = {}

              prevKeys = meta.keys ?? {}
            }

            if (!keys[variableValue]) {
              keys[variableValue] = []
            }

            keys[variableValue].push(grand.key)

            keysMatch = prevKeys[variableValue]?.[keyIndex] === grand.key

            grand = grand.value

            lengthDifference =
              (child.length || 0) - (prevKeys[variableValue]?.length || 0)
          }

          keyIndex++

          if (grand.type == null) {
            grand = {type: 'text', value: grand}
          }

          if (!keysMatch && lengthDifference < 0 && childNode != null) {
            lengthDifference++

            const extraNode = childNode

            childNode = childNode.nextSibling

            extraNode.remove()
          }

          if (isSameView && grand.view != null && !grand.dynamic) {
            childNode = childNode?.nextSibling
          } else {
            const forceAppend = !keysMatch && lengthDifference > 0 && childNode

            if (forceAppend) {
              lengthDifference--

              keyIndex--
            }

            childNode = morphChild(target, childNode, grand, {
              variables,
              isSameView,
              forceAppend,
              listeners
            })
          }
        }
      } else {
        childNode = morphChild(target, childNode, child, {
          variables,
          isSameView,
          listeners
        })
      }
    }
  }

  if (keys) {
    meta.keys = keys
  }

  if (childNode) {
    while (childNode.nextSibling) {
      childNode.nextSibling.remove()
    }

    childNode.remove()
  }

  weakMap.set(target, meta)
}

const morphRoot = (target, next, {listeners}) => {
  const meta = weakMap.get(target) ?? {}

  meta._read = true

  const isSameView = next.view === 0 || next.view === meta.view

  if (isSameView && next.view != null && !next.dynamic) {
    return
  }

  if (!isSameView) {
    meta.view = next.view
  }

  morph(target, next, {variables: next.variables, isSameView, meta, listeners})
}

export const createDomView = (target, view) => {
  const listeners = []

  return (state) => {
    const current = resolve(view(state))

    morphRoot(target, current, {listeners})

    if (current.afterUpdate) {
      current.afterUpdate(target)
    }
  }
}

const isSpaceChar = (char) => /\s/.test(char)
const isOfClose = (char) => char === '/' || char === '>' || isSpaceChar(char)
const isOfTag = (char) => !isOfClose(char)
const isOfKey = (char) => char !== '=' && !isOfClose(char)
const isQuoteChar = (char) => char === '"' || char === "'"

const endToken = {
  type: 'end'
}

const tokenizer = {
  *get(acc, strs, vlength) {
    for (let index = 0, length = strs.length; index < length; index++) {
      const str = strs[index]

      yield* this.tokenize(acc, str, index > 0)

      if (index < vlength) {
        yield {
          type: 'variable',
          value: index
        }
      }
    }
  },
  *tokenize(acc, str, first) {
    let tag = acc.tag
    let i = 0

    const current = () => str.charAt(i)
    const next = () => str.charAt(i + 1)

    while (current()) {
      if (!tag && current() === '<') {
        let value = ''
        let end = false

        if (next() === '/') {
          end = true

          i++
        }

        while (next() && isOfTag(next())) {
          i++

          value += current()
        }

        yield {
          type: !end ? 'tag' : 'endtag',
          value
        }

        first = false

        tag = value

        i++

        continue
      }

      if (tag && isSpaceChar(current())) {
        i++

        continue
      }

      if (tag && current() === '/' && next() === '>') {
        yield* [
          endToken,
          {
            type: 'endtag',
            value: tag
          },
          endToken
        ]

        first = false

        tag = false

        i += 2

        continue
      }

      if (tag && current() === '>') {
        yield endToken

        first = false

        tag = false

        i++

        continue
      }

      if (tag && isOfKey(current())) {
        let value = ''

        i--

        while (next() && isOfKey(next())) {
          i++

          value += current()
        }

        yield {
          type: 'key',
          value
        }

        first = false

        if (next() === '=') {
          i++

          let quote = ''
          let value = ''

          if (next() && isQuoteChar(next())) {
            i++

            quote = current()

            while (next() !== quote) {
              if (next()) {
                i++

                value += current()
              } else {
                throw Error('Quote mismatch')
              }
            }

            i++

            yield {
              type: 'value',
              value
            }
          } else if (next()) {
            throw Error('Quote expected')
          }
        } else {
          yield {
            type: 'value',
            value: true
          }
        }

        i++

        continue
      }

      if (!tag) {
        let value = ''

        while (current() && current() !== '<') {
          value += current()

          i++
        }

        let trim = true

        if (!current() && first) {
          trim = false
        }

        if ((!trim && value) || (trim && value.trim())) {
          yield {
            type: 'text',
            value
          }
        }

        continue
      }

      i++
    }

    acc.tag = tag
  }
}

const parse = (tokens, parent, tag) => {
  const child = {
    type: 'node',
    tag,
    dynamic: false,
    attributes: [],
    children: []
  }

  let current

  for (;;) {
    current = tokens.next()

    if (current.done) break

    const token = current.value

    if (token.type === 'end') {
      break
    } else if (token.type === 'key') {
      const next = (tokens.next() ?? {value: true}).value

      if (next.type === 'value') {
        child.attributes.push({key: token.value, value: next.value})
      } else {
        const value = next.value

        child.dynamic = true

        child.attributes.push({
          key: token.value,
          variable: true,
          value
        })
      }
    } else if (token.type === 'variable') {
      child.dynamic = true

      child.attributes.push({
        key: false,
        variable: true,
        value: token.value
      })
    }
  }

  for (;;) {
    current = tokens.next()

    if (current.done) break

    const token = current.value

    if (token.type === 'endtag' && token.value === child.tag) {
      break
    } else if (token.type === 'tag') {
      const d = parse(tokens, child, token.value)

      child.dynamic = child.dynamic || d
    } else if (token.type === 'text') {
      child.children.push({
        type: 'text',
        value: token.value
      })
    } else if (token.type === 'variable') {
      child.dynamic = true

      child.children.push({
        type: 'variable',
        variable: true,
        value: token.value
      })
    }
  }

  parent.children.push(child)

  return child.dynamic
}

let view = 1

const toTemplate = (strs, vlength) => {
  const acc = {
    tag: false
  }

  const tokens = tokenizer.get(acc, strs, vlength)

  const children = []

  let current

  for (;;) {
    current = tokens.next()

    if (current.done) break

    const token = current.value

    if (token.type === 'tag') {
      parse(tokens, {children}, token.value)
    } else if (token.type === 'text') {
      children.push({value: token.value})
    }
  }

  if (children.length !== 1) {
    throw Error(`Found ${children.length} root nodes. Expected 1.`)
  } else if (children[0].type !== 'node') {
    throw Error(`Found '${children[0].type}'. Expected 'node'.`)
  }

  children[0].view = view++

  return children[0]
}

export const html = (strs, ...variables) => {
  let result = weakMap.get(strs)

  if (!result) {
    result = toTemplate(strs, variables.length)

    weakMap.set(strs, result)
  }

  return Object.assign({variables}, result)
}

export const createApp = (state) => {
  let viewCalled = false
  let view

  const app = {
    render(v) {
      view = v

      viewCalled = false

      return Promise.resolve().then(() => {
        if (!viewCalled) {
          viewCalled = true

          view(state)
        }
      })
    },
    commit(arg) {
      if (typeof arg === 'function') {
        state = arg(state) ?? state
      } else {
        state = arg
      }

      viewCalled = true

      if (view != null) {
        view(state)
      }
    }
  }

  return app
}
<commit_msg>Optimize some things<commit_after>const svgNamespace = 'http://www.w3.org/2000/svg'

const weakMap = new WeakMap()

const resolve = (obj) => {
  let afterUpdate

  if (typeof obj === 'function') {
    obj = obj((cb) => {
      afterUpdate = cb
    })

    if (afterUpdate != null) {
      obj.afterUpdate = afterUpdate
    }
  }

  return obj
}

const getListener = (key) => (e) => {
  const map = weakMap.get(e.target)

  if (map && map[key]) {
    map[key](e)
  }
}

const morphAttribute = (target, key, value, meta, listeners) => {
  const document = target.ownerDocument
  const remove = value == null || value === false

  if (key.indexOf('on') === 0) {
    if (!meta._read) {
      Object.assign(meta, weakMap.get(target) || {})

      meta._read = true
    }

    if (remove) {
      if (meta[key]) {
        meta[key] = null
      }
    } else {
      meta[key] = value

      if (!listeners.includes(key)) {
        listeners.push(key)

        const type = key.substring(2)

        document.addEventListener(type, getListener(key))
      }
    }
  } else {
    if (remove) {
      target.removeAttribute(key)
    } else {
      const stringified = value === true ? '' : value

      if (target.getAttribute(key) !== stringified) {
        target.setAttribute(key, stringified)
      }
    }

    if (key === 'value' || value === true || value === false) {
      if (target[key] !== value) {
        target[key] = value
      }
    }
  }
}

const morphChild = (
  target,
  childNode,
  next,
  variables,
  isSameView,
  forceAppend,
  listeners
) => {
  const document = target.ownerDocument

  const append = forceAppend || childNode == null

  let replace = false

  let currentChild = childNode

  if (next.type === 'text') {
    if (!append && childNode.nodeType !== 3) {
      replace = true
    }

    if (append || replace) {
      currentChild = document.createTextNode(next.value)
    } else if (childNode.data !== next.value) {
      childNode.data = next.value
    }
  } else {
    const tag = next.tag

    if (
      !append &&
      (childNode.nodeType !== 1 || childNode.nodeName.toLowerCase() !== tag)
    ) {
      replace = true
    }

    if (append || replace) {
      const isSvg = tag === 'svg' || target.namespaceURI === svgNamespace

      currentChild = isSvg
        ? document.createElementNS(svgNamespace, tag)
        : document.createElement(tag)
    }

    if (next.view != null) {
      morphRoot(currentChild, next, listeners)
    } else {
      const meta = {_read: false}

      morph(currentChild, next, variables, isSameView, meta, listeners)
    }
  }

  if (forceAppend && childNode) {
    childNode.before(currentChild)
  } else if (append) {
    target.append(currentChild)
  } else if (replace) {
    childNode.replaceWith(currentChild)
  }

  if (currentChild != null && next.afterUpdate) {
    next.afterUpdate(currentChild)
  }

  return currentChild?.nextSibling
}

const morph = (target, next, variables, isSameView, meta, listeners) => {
  if (isSameView && !next.dynamic) {
    return
  }

  const attributesLength = next.attributes.length

  if (attributesLength) {
    for (let i = 0, length = attributesLength; i < length; i++) {
      const attribute = next.attributes[i]

      if (isSameView && !attribute.variable) {
        continue
      }

      let value = attribute.value

      if (attribute.variable) {
        value = variables[value]
      }

      if (attribute.key) {
        morphAttribute(target, attribute.key, value, meta, listeners)
      } else {
        for (const key of Object.keys(value)) {
          morphAttribute(target, key, value[key], meta, listeners)
        }
      }
    }
  }

  const childrenLength = next.children.length
  let childNode = target.firstChild

  if (!meta._read) {
    Object.assign(meta, weakMap.get(target) || {})

    meta._read = true
  }

  let keys

  let prevKeys

  if (childrenLength) {
    let deopt = !isSameView

    for (let childIndex = 0; childIndex < childrenLength; childIndex++) {
      let child = next.children[childIndex]

      if (child == null) {
        continue
      }

      if (!deopt && !child.dynamic && !child.variable) {
        childNode = childNode?.nextSibling

        continue
      }

      deopt = true

      if (child.variable) {
        const variableValue = child.value

        child = variables[variableValue]

        if (child?.[Symbol.iterator] == null || typeof child === 'string') {
          child = [child]
        }

        let keyIndex = 0
        let lengthDifference

        for (let grand of child) {
          grand = resolve(grand)

          if (grand == null) grand = ''

          let keysMatch = true

          if (grand.key) {
            if (!meta._read) {
              Object.assign(meta, weakMap.get(target) || {})

              meta._read = true
            }

            if (!keys) {
              keys = {}

              prevKeys = meta.keys ?? {}
            }

            if (!keys[variableValue]) {
              keys[variableValue] = []
            }

            keys[variableValue].push(grand.key)

            keysMatch = prevKeys[variableValue]?.[keyIndex] === grand.key

            grand = grand.value

            lengthDifference =
              (child.length || 0) - (prevKeys[variableValue]?.length || 0)
          }

          keyIndex++

          if (grand.type == null) {
            grand = {type: 'text', value: grand}
          }

          if (!keysMatch && lengthDifference < 0 && childNode != null) {
            lengthDifference++

            const extraNode = childNode

            childNode = childNode.nextSibling

            extraNode.remove()
          }

          if (isSameView && grand.view != null && !grand.dynamic) {
            childNode = childNode?.nextSibling
          } else {
            const forceAppend = !keysMatch && lengthDifference > 0 && childNode

            if (forceAppend) {
              lengthDifference--

              keyIndex--
            }

            childNode = morphChild(
              target,
              childNode,
              grand,
              variables,
              isSameView,
              forceAppend,
              listeners
            )
          }
        }
      } else {
        childNode = morphChild(
          target,
          childNode,
          child,
          variables,
          isSameView,
          false,
          listeners
        )
      }
    }
  }

  if (keys) {
    meta.keys = keys
  }

  if (childNode) {
    while (childNode.nextSibling) {
      childNode.nextSibling.remove()
    }

    childNode.remove()
  }

  weakMap.set(target, meta)
}

const morphRoot = (target, next, listeners) => {
  const meta = weakMap.get(target) ?? {}

  meta._read = true

  const isSameView = next.view === 0 || next.view === meta.view

  if (isSameView && next.view != null && !next.dynamic) {
    return
  }

  if (!isSameView) {
    meta.view = next.view
  }

  morph(target, next, next.variables, isSameView, meta, listeners)
}

export const createDomView = (target, view) => {
  const listeners = []

  return (state) => {
    const current = resolve(view(state))

    morphRoot(target, current, listeners)

    if (current.afterUpdate) {
      current.afterUpdate(target)
    }
  }
}

const viewZero = {
  type: 'node',
  view: 0,
  dynamic: false
}

export const skipUpdate = () => viewZero

const valueTrue = {
  type: 'value',
  value: true
}

const END = Symbol('end')

const isSpaceChar = (char) => /\s/.test(char)
const isOfClose = (char) => char === '/' || char === '>' || isSpaceChar(char)
const isOfTag = (char) => !isOfClose(char)
const isOfKey = (char) => char !== '=' && !isOfClose(char)
const isQuoteChar = (char) => char === '"' || char === "'"

const tokenizer = {
  *get(acc, strs, vlength) {
    for (let index = 0, length = strs.length; index < length; index++) {
      const str = strs[index]
      let first = index > 0

      let tag = acc.tag
      let i = 0

      const current = () => str.charAt(i)
      const next = () => str.charAt(i + 1)

      while (current()) {
        if (!tag && current() === '<') {
          let value = ''
          let end = false

          if (next() === '/') {
            end = true

            i++
          }

          while (next() && isOfTag(next())) {
            i++

            value += current()
          }

          yield {
            type: !end ? 'tag' : 'endtag',
            value
          }

          first = false

          tag = value

          i++

          continue
        }

        if (tag && isSpaceChar(current())) {
          i++

          continue
        }

        if (tag && current() === '/' && next() === '>') {
          yield* [
            END,
            {
              type: 'endtag',
              value: tag
            },
            END
          ]

          first = false

          tag = false

          i += 2

          continue
        }

        if (tag && current() === '>') {
          yield END

          first = false

          tag = false

          i++

          continue
        }

        if (tag && isOfKey(current())) {
          let value = ''

          i--

          while (next() && isOfKey(next())) {
            i++

            value += current()
          }

          yield {
            type: 'key',
            value
          }

          first = false

          if (next() === '=') {
            i++

            let quote = ''
            let value = ''

            if (next() && isQuoteChar(next())) {
              i++

              quote = current()

              while (next() !== quote) {
                if (next()) {
                  i++

                  value += current()
                } else {
                  throw Error('Quote mismatch')
                }
              }

              i++

              yield {
                type: 'value',
                value
              }
            } else if (next()) {
              throw Error('Quote expected')
            }
          } else {
            yield valueTrue
          }

          i++

          continue
        }

        if (!tag) {
          let value = ''

          while (current() && current() !== '<') {
            value += current()

            i++
          }

          let trim = true

          if (!current() && first) {
            trim = false
          }

          if ((!trim && value) || (trim && value.trim())) {
            yield {
              type: 'text',
              value
            }
          }

          continue
        }

        i++
      }

      acc.tag = tag

      if (index < vlength) {
        yield {
          type: 'variable',
          value: index
        }
      }
    }
  }
}

const parse = (tokens, parent, tag) => {
  const child = {
    tag,
    type: 'node',
    dynamic: false,
    attributes: [],
    children: []
  }

  let current

  for (;;) {
    current = tokens.next()

    if (current.done) break

    const token = current.value

    if (token === END) {
      break
    } else if (token.type === 'key') {
      const next = (tokens.next() ?? {value: true}).value

      if (next.type === 'value') {
        child.attributes.push({key: token.value, value: next.value})
      } else {
        const value = next.value

        child.dynamic = true

        child.attributes.push({
          key: token.value,
          variable: true,
          value
        })
      }
    } else if (token.type === 'variable') {
      child.dynamic = true

      child.attributes.push({
        key: false,
        variable: true,
        value: token.value
      })
    }
  }

  for (;;) {
    current = tokens.next()

    if (current.done) break

    const token = current.value

    if (token.type === 'endtag' && token.value === child.tag) {
      break
    } else if (token.type === 'tag') {
      const d = parse(tokens, child, token.value)

      child.dynamic = child.dynamic || d
    } else if (token.type === 'text') {
      child.children.push({
        type: 'text',
        value: token.value
      })
    } else if (token.type === 'variable') {
      child.dynamic = true

      child.children.push({
        type: 'variable',
        variable: true,
        value: token.value
      })
    }
  }

  parent.children.push(child)

  return child.dynamic
}

let view = 1

const toTemplate = (strs, vlength) => {
  const acc = {
    tag: false
  }

  const tokens = tokenizer.get(acc, strs, vlength)

  const children = []

  let current

  for (;;) {
    current = tokens.next()

    if (current.done) break

    const token = current.value

    if (token.type === 'tag') {
      parse(tokens, {children}, token.value)
    } else if (token.type === 'text') {
      children.push({value: token.value})
    }
  }

  if (children.length !== 1) {
    throw Error(`Found ${children.length} root nodes. Expected 1.`)
  } else if (children[0].type !== 'node') {
    throw Error(`Found '${children[0].type}'. Expected 'node'.`)
  }

  children[0].view = view++

  return children[0]
}

export const html = (strs, ...variables) => {
  let result = weakMap.get(strs)

  if (!result) {
    result = toTemplate(strs, variables.length)

    weakMap.set(strs, result)
  }

  return Object.assign({variables}, result)
}

export const createApp = (state) => {
  let viewCalled = false
  let view

  const app = {
    render(v) {
      view = v

      viewCalled = false

      return Promise.resolve().then(() => {
        if (!viewCalled) {
          viewCalled = true

          view(state)
        }
      })
    },
    commit(arg) {
      if (typeof arg === 'function') {
        state = arg(state) ?? state
      } else {
        state = arg
      }

      viewCalled = true

      if (view != null) {
        view(state)
      }
    }
  }

  return app
}
<|endoftext|>