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

/////// Required environment variables:

// APP_NAME - application name (e.g. 'docs')
// APP_ID - application ID unique within application name (e.g. '1')
// COREOS_HOST - IP or hostname of the VM the Docker container is running on
// IMAGE - the Docker image this container is running

/////// Optional environment variables:

// APP_HEALTH_URL = URL path that must respond with HTTP 200 for the sevice to be healthy
// APP_PORT - the TCP port the application listens on within the container; if not provided,
//            the foreman will not wait for the Docker port mapping

/////// Required command line parameters:

// The command and arguments to start the backend, e.g.
// auth0-foreman node server.js
// (This would be typically specifified in Dockerfile)

// Validate environment

var assert = require('assert');

var options = {};
['APP_NAME', 'APP_ID', 'COREOS_HOST', 'IMAGE']
    .forEach(function (v) {
        assert.ok(process.env[v] !== undefined, v + ' environment variable not set.');
        options[v.toLowerCase()] = process.env[v];
    });

// Test URL path is optional

options.app_health_url = process.env.APP_HEALTH_URL;
options.app_port = process.env.APP_PORT;

// Obtain command and arguments to start the backend

process.argv.shift();
process.argv.shift();
options.backend_cmd = process.argv.shift();
options.backend_args = process.argv;
assert.ok(options.backend_cmd, 'The command and optional arguments to start the backend must be specified as command line parameters.');

// Establish the mechanism the foreman will use to activate the service:
// - `exec` means external executable
// - `inproc` means in-process node.js module with child_process.spawn-like API contract

process.env.ACTIVATION_METHOD = options.activation_method =
    process.env.ACTIVATION_METHOD || (options.backend_cmd.indexOf('.js') > 0 ? 'inproc' : 'exec');
assert.ok(options.activation_method === 'exec' || options.activation_method === 'inproc',
    'ACTIVATION_METHOD environment variable must be either `exec` or `inproc`.');

// Establish signal file name to allow the backend to signal readiness. This is only
// used when ACTIVATION_METHOD === 'exec'

process.env.SIGNAL_FILE = options.signal_file =
    process.env.SIGNAL_FILE || '/data/backend_signal';

var async = require('async')
    , fs = require('fs')
    , http = require('http')
    , host = process.env.COREOS_HOST || '127.0.0.1'
    , etcd = new (require('node-etcd'))(host)
    , docker = new (require('dockerode'))({ host: host, port: 2375 })
    , coreos_tools = require('../lib/index');

var logger = coreos_tools.create_logger(
    'foreman-' + options.app_name + '-' + options.app_id,
    [ 'app-' + options.app_name, 'system' ]);

logger.info(options, 'starting');

// Container name and config path.
var container_name = options.app_name + '-' + options.app_id;
var foreman_container_name = container_name + '-foreman';
var config_path = '/routes/' + options.app_name + '/' + options.app_id;
var target_port = options.app_port + '/tcp';
var container = docker.getContainer(container_name);

var route;
var route_timer;
var config;
var backend;

// Register to clean up on process exit
process.on('uncaughtException', function (e) {
    logger.error(e, 'uncaught exception');
    last_resort_cleanup(function () {
        process.exit(1);
    });
});
process.on('exit', function () {
    last_resort_cleanup(function () {});
});

async.series([
    function (callback) {
        // Get etcd config
        coreos_tools.etcd_get('/config', function (error, result) {
            if (error)
                logger.error(error, 'cannot obtain etcd config');
            config = result;
            callback(error);
        });
    },
    function (callback) {
        // Etablish signal file and watch it, then start the backend
        // and wait for it to signal back, or for the startup timer to expire.

        // Add ETCD config to environment
        process.env.JSON_CONFIG = JSON.stringify(coreos_tools.upper(config));

        var watch, spawn_impl;
        if (options.activation_method === 'exec') {
            // Get the child process spawn command
            spawn_impl = require('child_process').spawn;
            // Wait for changes to the SIGNAL_FILE made by the server
            fs.writeFileSync(options.signal_file, '');
            watch = fs.watch(options.signal_file, { persistent: false });
            watch.once('change', function () { done('signal'); });
        }
        else // 'inproc'
            // Get the inproc activation method that mimics the spawn contract
            spawn_impl = require(options.backend_cmd).spawn;

        backend = spawn_impl(options.backend_cmd, options.backend_args, {
            env: process.env,
            stdio: 'inherit'
        });

        backend.once('exit', function () { done('exit'); });
        backend.once('error', function () { done('exit'); });
        // The `ready` event is a mechanism used by inproc servers (e.g. sandbox)
        // to signal readiness
        backend.once('ready', function () { done('signal'); });
        var timeout = setTimeout(function () {
            done('timeout');
        }, +config.container_registration_timeout * 1000);

        var done_reason;
        function done(reason) {
            logger.info({ reason: reason }, 'finished waiting for component to startup');
            if (done_reason) return;
            done_reason = reason;
            backend.removeAllListeners();
            if (done_reason !== 'timeout')
                clearTimeout(timeout);
            if (reason === 'exit')
                callback(new Error('Backend process terminated unexpectedly during startup.'));
            else {
                // Timeout may indicate backend that does not know how to signal,
                // so assume innocence for now.

                // Register to detect backend's exit going forward:
                backend.once('exit', backend_exited);
                backend.once('error', backend_exited);

                callback();
            }
        }
    },
    function (callback) {
        // Wait for the docker port mapping if requested
        if (!options.app_port) {
            route = 'default';
            return callback();
        }

        var attempts = +config.docker_port_registration_retry_count;
        var delay = +config.docker_port_registration_retry_delay;
        var backoff = +config.docker_port_registration_retry_backoff;
        async.doUntil(
            function (callback) {
                // Check if docker container port had been mapped.
                container.inspect(function (error, info) {
                    logger.info({ attempts: attempts, error: error }, 'waiting for Docker port mapping');
                    if (error)
                        return callback(error);
                    attempts--;
                    delay *= Math.floor(delay * backoff);
                    if (info.NetworkSettings
                        && typeof info.NetworkSettings.Ports === 'object'
                        && info.NetworkSettings.Ports
                        && Array.isArray(info.NetworkSettings.Ports[target_port])
                        && info.NetworkSettings.Ports[target_port].length === 1
                        && info.NetworkSettings.Ports[target_port][0].HostPort)
                            route = info.NetworkSettings.Ports[target_port][0].HostPort;
                    if (route)
                        callback();
                    else if (attempts === 0)
                        callback(new Error('Timeout waiting for the container '
                            + container_name + ' to establish a port mapping.'));
                    else
                        setTimeout(callback, delay);
                });
            },
            function () {
                // Stop querying docker for port mapping only when mapped.
                return route !== undefined;
            },
            callback);
    },
    function (callback) {
        logger.info({ port: route }, 'established port mapping');
        // Send the test HTTP GET request to validate the backend
        if (!options.app_health_url)
            return callback();

        var url = 'http://' + options.coreos_host + ':' + route + options.app_health_url;
        logger.info({ url: url }, 'probing health endpoint');
        http.get({
            agent: false, // don't use keep-alive which prevents server from closing down
            host: options.coreos_host,
            port: route,
            path: options.app_health_url
        }, function (res) {
            logger.info({ status: res.statusCode }, 'health endpoint response');
            callback(res.statusCode === 200
                ? undefined
                : new Error('Backend did not respond to health check at '
                    + url + ' with status code 200: ' + res.statusCode))
        }).on('error', function (error) {
            logger.error(error, 'health endpoint error');
            callback(new Error('Backend failed to respond to health check at ' + url));
        });
    },
    function (callback) {
        // Register static metadata in etcd
        etcd.set(config_path + '/created', Date.now(), callback);
    },
    function (callback) {
        logger.info({ path: config_path }, 'created routing entry in etcd')
        // Keep container route registration in etcd current with a TTL
        var route_config_path = config_path + '/port';
        async.forever(
            function (next) {
                etcd.set(route_config_path, route, { ttl: 60 }, function (error) {
                    if (error) return next(error);
                    route_timer = setTimeout(next, 45000);
                });
            },
            function (error) {
                logger.error(error, 'error updating route entry in etcd');
                recycle('SIGTERM', 105);
            });

        // Detect externally initiated graceful termintion of the container
        process
            .once('SIGTERM', function () { recycle('SIGTERM', 0); })
            .once('SIGINT', function () { recycle('SIGINT', 0); });

        function recycle(signal, exitCode) {
            logger.info({ signal: signal, exitCode: exitCode }, 'recycling');
            if (backend)
                backend.graceful_exit_code = exitCode;

            // Remove etcd registration
            if (route_timer) {
                clearTimeout(route_timer);
                route_timer = undefined;
            }

            async.series([
                function (callback) {
                    // Mark the container as recycling in etcd
                    etcd.set(config_path + '/recycling', Date.now(), callback);
                },
                function (callback) {
                    // Remove routing information from etcd
                    etcd.del(config_path + '/port', callback);
                },
                function (callback) {
                    // Gracefully stop the server after cooldown timeout
                    setTimeout(function () {
                        logger.info({ backend_exists: backend !== undefined, signal: signal }, 'cooldown time elapsed, killing backend');
                        if (backend)
                            backend.kill(signal);
                    }, +config.cooldown_timeout * 1000);
                    logger.info({ timeout: +config.cooldown_timeout }, 'initiated cooldown');

                    // Let active requests complete up to the graceful shutdown timeout
                    setTimeout(function () {
                        logger.info('graceful shutdown time elapsed, exiting foreman');
                        last_resort_cleanup(function () {
                            process.exit(exitCode);
                        });
                    }, +config.graceful_shutdown_timeout * 1000);
                    logger.info({ timeout: +config.graceful_shutdown_timeout }, 'initiated graceful shutdown');
                }
            ], callback);
        }
    }
], function (error) {
    logger.error(error, 'foreman error');
    last_resort_cleanup(function () {
        throw error;
    });
});

function backend_exited(code) {
    logger.info({ code: code }, 'backend exited');
    assert.ok(backend);

    var graceful_exit_code = backend.graceful_exit_code;
    last_resort_cleanup(function () {
        process.exit(graceful_exit_code !== undefined ? graceful_exit_code : code);
    });
}

function last_resort_cleanup(callback) {
    logger.info('last resort cleanup entered');

    if (route_timer) {
        logger.info('last resort cleanup: stopping route update timer');
        clearTimeout(route_timer);
        route_timer = undefined;
    }

    async.series([
        function (callback) {
            // Terminate backed and clean up etcd
            async.parallel([
                function (callback) {
                    // Terminate backend
                    if (backend) {
                        var tmp = backend;
                        backend = undefined;
                        logger.info('last resort cleanup: killing backend');
                        tmp.removeAllListeners();
                        if (tmp.kill.is_async) {
                            // This is the Docker based implementation which is async
                            // because it uses Docker client.
                            tmp.kill('SIGKILL', callback);
                        }
                        else {
                            // This is the child_process based implementation
                            try { tmp.kill('SIGKILL'); } catch(e) {}
                            // Remove the child Docker container
                            container.remove({ force: true }, function () {
                                callback();
                            });
                        }
                    }
                    else
                        callback();
                },
                function (callback) {
                    // Clean up etcd
                    logger.info('last resort cleanup: removing routing entry in etcd');
                    etcd.del(config_path, { recursive: true }, function () {
                        callback();
                    });
                }
            ], callback);
        },
        function (callback) {
            // Remove own container via Docker
            // TODO: tjanczuk: capture logs before removing foreman
            if (foreman_container_name) {
                logger.info({ container: foreman_container_name }, 'removing own container');
                var tmp = foreman_container_name;
                foreman_container_name = undefined;
                var foreman = docker.getContainer(tmp);
                foreman.remove({ force: true }, function () {
                    callback();
                });
            }
            else
                callback();
        }
    ], callback);
}
<commit_msg>remove volumes along with containers<commit_after>#!/usr/bin/env node

/////// Required environment variables:

// APP_NAME - application name (e.g. 'docs')
// APP_ID - application ID unique within application name (e.g. '1')
// COREOS_HOST - IP or hostname of the VM the Docker container is running on
// IMAGE - the Docker image this container is running

/////// Optional environment variables:

// APP_HEALTH_URL = URL path that must respond with HTTP 200 for the sevice to be healthy
// APP_PORT - the TCP port the application listens on within the container; if not provided,
//            the foreman will not wait for the Docker port mapping

/////// Required command line parameters:

// The command and arguments to start the backend, e.g.
// auth0-foreman node server.js
// (This would be typically specifified in Dockerfile)

// Validate environment

var assert = require('assert');

var options = {};
['APP_NAME', 'APP_ID', 'COREOS_HOST', 'IMAGE']
    .forEach(function (v) {
        assert.ok(process.env[v] !== undefined, v + ' environment variable not set.');
        options[v.toLowerCase()] = process.env[v];
    });

// Test URL path is optional

options.app_health_url = process.env.APP_HEALTH_URL;
options.app_port = process.env.APP_PORT;

// Obtain command and arguments to start the backend

process.argv.shift();
process.argv.shift();
options.backend_cmd = process.argv.shift();
options.backend_args = process.argv;
assert.ok(options.backend_cmd, 'The command and optional arguments to start the backend must be specified as command line parameters.');

// Establish the mechanism the foreman will use to activate the service:
// - `exec` means external executable
// - `inproc` means in-process node.js module with child_process.spawn-like API contract

process.env.ACTIVATION_METHOD = options.activation_method =
    process.env.ACTIVATION_METHOD || (options.backend_cmd.indexOf('.js') > 0 ? 'inproc' : 'exec');
assert.ok(options.activation_method === 'exec' || options.activation_method === 'inproc',
    'ACTIVATION_METHOD environment variable must be either `exec` or `inproc`.');

// Establish signal file name to allow the backend to signal readiness. This is only
// used when ACTIVATION_METHOD === 'exec'

process.env.SIGNAL_FILE = options.signal_file =
    process.env.SIGNAL_FILE || '/data/backend_signal';

var async = require('async')
    , fs = require('fs')
    , http = require('http')
    , host = process.env.COREOS_HOST || '127.0.0.1'
    , etcd = new (require('node-etcd'))(host)
    , docker = new (require('dockerode'))({ host: host, port: 2375 })
    , coreos_tools = require('../lib/index');

var logger = coreos_tools.create_logger(
    'foreman-' + options.app_name + '-' + options.app_id,
    [ 'app-' + options.app_name, 'system' ]);

logger.info(options, 'starting');

// Container name and config path.
var container_name = options.app_name + '-' + options.app_id;
var foreman_container_name = container_name + '-foreman';
var config_path = '/routes/' + options.app_name + '/' + options.app_id;
var target_port = options.app_port + '/tcp';
var container = docker.getContainer(container_name);

var route;
var route_timer;
var config;
var backend;

// Register to clean up on process exit
process.on('uncaughtException', function (e) {
    logger.error(e, 'uncaught exception');
    last_resort_cleanup(function () {
        process.exit(1);
    });
});
process.on('exit', function () {
    last_resort_cleanup(function () {});
});

async.series([
    function (callback) {
        // Get etcd config
        coreos_tools.etcd_get('/config', function (error, result) {
            if (error)
                logger.error(error, 'cannot obtain etcd config');
            config = result;
            callback(error);
        });
    },
    function (callback) {
        // Etablish signal file and watch it, then start the backend
        // and wait for it to signal back, or for the startup timer to expire.

        // Add ETCD config to environment
        process.env.JSON_CONFIG = JSON.stringify(coreos_tools.upper(config));

        var watch, spawn_impl;
        if (options.activation_method === 'exec') {
            // Get the child process spawn command
            spawn_impl = require('child_process').spawn;
            // Wait for changes to the SIGNAL_FILE made by the server
            fs.writeFileSync(options.signal_file, '');
            watch = fs.watch(options.signal_file, { persistent: false });
            watch.once('change', function () { done('signal'); });
        }
        else // 'inproc'
            // Get the inproc activation method that mimics the spawn contract
            spawn_impl = require(options.backend_cmd).spawn;

        backend = spawn_impl(options.backend_cmd, options.backend_args, {
            env: process.env,
            stdio: 'inherit'
        });

        backend.once('exit', function () { done('exit'); });
        backend.once('error', function () { done('exit'); });
        // The `ready` event is a mechanism used by inproc servers (e.g. sandbox)
        // to signal readiness
        backend.once('ready', function () { done('signal'); });
        var timeout = setTimeout(function () {
            done('timeout');
        }, +config.container_registration_timeout * 1000);

        var done_reason;
        function done(reason) {
            logger.info({ reason: reason }, 'finished waiting for component to startup');
            if (done_reason) return;
            done_reason = reason;
            backend.removeAllListeners();
            if (done_reason !== 'timeout')
                clearTimeout(timeout);
            if (reason === 'exit')
                callback(new Error('Backend process terminated unexpectedly during startup.'));
            else {
                // Timeout may indicate backend that does not know how to signal,
                // so assume innocence for now.

                // Register to detect backend's exit going forward:
                backend.once('exit', backend_exited);
                backend.once('error', backend_exited);

                callback();
            }
        }
    },
    function (callback) {
        // Wait for the docker port mapping if requested
        if (!options.app_port) {
            route = 'default';
            return callback();
        }

        var attempts = +config.docker_port_registration_retry_count;
        var delay = +config.docker_port_registration_retry_delay;
        var backoff = +config.docker_port_registration_retry_backoff;
        async.doUntil(
            function (callback) {
                // Check if docker container port had been mapped.
                container.inspect(function (error, info) {
                    logger.info({ attempts: attempts, error: error }, 'waiting for Docker port mapping');
                    if (error)
                        return callback(error);
                    attempts--;
                    delay *= Math.floor(delay * backoff);
                    if (info.NetworkSettings
                        && typeof info.NetworkSettings.Ports === 'object'
                        && info.NetworkSettings.Ports
                        && Array.isArray(info.NetworkSettings.Ports[target_port])
                        && info.NetworkSettings.Ports[target_port].length === 1
                        && info.NetworkSettings.Ports[target_port][0].HostPort)
                            route = info.NetworkSettings.Ports[target_port][0].HostPort;
                    if (route)
                        callback();
                    else if (attempts === 0)
                        callback(new Error('Timeout waiting for the container '
                            + container_name + ' to establish a port mapping.'));
                    else
                        setTimeout(callback, delay);
                });
            },
            function () {
                // Stop querying docker for port mapping only when mapped.
                return route !== undefined;
            },
            callback);
    },
    function (callback) {
        logger.info({ port: route }, 'established port mapping');
        // Send the test HTTP GET request to validate the backend
        if (!options.app_health_url)
            return callback();

        var url = 'http://' + options.coreos_host + ':' + route + options.app_health_url;
        logger.info({ url: url }, 'probing health endpoint');
        http.get({
            agent: false, // don't use keep-alive which prevents server from closing down
            host: options.coreos_host,
            port: route,
            path: options.app_health_url
        }, function (res) {
            logger.info({ status: res.statusCode }, 'health endpoint response');
            callback(res.statusCode === 200
                ? undefined
                : new Error('Backend did not respond to health check at '
                    + url + ' with status code 200: ' + res.statusCode))
        }).on('error', function (error) {
            logger.error(error, 'health endpoint error');
            callback(new Error('Backend failed to respond to health check at ' + url));
        });
    },
    function (callback) {
        // Register static metadata in etcd
        etcd.set(config_path + '/created', Date.now(), callback);
    },
    function (callback) {
        logger.info({ path: config_path }, 'created routing entry in etcd')
        // Keep container route registration in etcd current with a TTL
        var route_config_path = config_path + '/port';
        async.forever(
            function (next) {
                etcd.set(route_config_path, route, { ttl: 60 }, function (error) {
                    if (error) return next(error);
                    route_timer = setTimeout(next, 45000);
                });
            },
            function (error) {
                logger.error(error, 'error updating route entry in etcd');
                recycle('SIGTERM', 105);
            });

        // Detect externally initiated graceful termintion of the container
        process
            .once('SIGTERM', function () { recycle('SIGTERM', 0); })
            .once('SIGINT', function () { recycle('SIGINT', 0); });

        function recycle(signal, exitCode) {
            logger.info({ signal: signal, exitCode: exitCode }, 'recycling');
            if (backend)
                backend.graceful_exit_code = exitCode;

            // Remove etcd registration
            if (route_timer) {
                clearTimeout(route_timer);
                route_timer = undefined;
            }

            async.series([
                function (callback) {
                    // Mark the container as recycling in etcd
                    etcd.set(config_path + '/recycling', Date.now(), callback);
                },
                function (callback) {
                    // Remove routing information from etcd
                    etcd.del(config_path + '/port', callback);
                },
                function (callback) {
                    // Gracefully stop the server after cooldown timeout
                    setTimeout(function () {
                        logger.info({ backend_exists: backend !== undefined, signal: signal }, 'cooldown time elapsed, killing backend');
                        if (backend)
                            backend.kill(signal);
                    }, +config.cooldown_timeout * 1000);
                    logger.info({ timeout: +config.cooldown_timeout }, 'initiated cooldown');

                    // Let active requests complete up to the graceful shutdown timeout
                    setTimeout(function () {
                        logger.info('graceful shutdown time elapsed, exiting foreman');
                        last_resort_cleanup(function () {
                            process.exit(exitCode);
                        });
                    }, +config.graceful_shutdown_timeout * 1000);
                    logger.info({ timeout: +config.graceful_shutdown_timeout }, 'initiated graceful shutdown');
                }
            ], callback);
        }
    }
], function (error) {
    logger.error(error, 'foreman error');
    last_resort_cleanup(function () {
        throw error;
    });
});

function backend_exited(code) {
    logger.info({ code: code }, 'backend exited');
    assert.ok(backend);

    var graceful_exit_code = backend.graceful_exit_code;
    last_resort_cleanup(function () {
        process.exit(graceful_exit_code !== undefined ? graceful_exit_code : code);
    });
}

function last_resort_cleanup(callback) {
    logger.info('last resort cleanup entered');

    if (route_timer) {
        logger.info('last resort cleanup: stopping route update timer');
        clearTimeout(route_timer);
        route_timer = undefined;
    }

    async.series([
        function (callback) {
            // Terminate backed and clean up etcd
            async.parallel([
                function (callback) {
                    // Terminate backend
                    if (backend) {
                        var tmp = backend;
                        backend = undefined;
                        logger.info('last resort cleanup: killing backend');
                        tmp.removeAllListeners();
                        if (tmp.kill.is_async) {
                            // This is the Docker based implementation which is async
                            // because it uses Docker client.
                            tmp.kill('SIGKILL', callback);
                        }
                        else {
                            // This is the child_process based implementation
                            try { tmp.kill('SIGKILL'); } catch(e) {}
                            // Remove the child Docker container
                            container.remove({ v: true, force: true }, function () {
                                callback();
                            });
                        }
                    }
                    else
                        callback();
                },
                function (callback) {
                    // Clean up etcd
                    logger.info('last resort cleanup: removing routing entry in etcd');
                    etcd.del(config_path, { recursive: true }, function () {
                        callback();
                    });
                }
            ], callback);
        },
        function (callback) {
            // Remove own container via Docker
            // TODO: tjanczuk: capture logs before removing foreman
            if (foreman_container_name) {
                logger.info({ container: foreman_container_name }, 'removing own container');
                var tmp = foreman_container_name;
                foreman_container_name = undefined;
                var foreman = docker.getContainer(tmp);
                foreman.remove({ v: true, force: true }, function () {
                    callback();
                });
            }
            else
                callback();
        }
    ], callback);
}
<|endoftext|>


<commit_before>const path = require('path')
const fs = require('fs')
const async = require('async')
const DllPlugin = require('webpack/lib/DllPlugin')
const { execSync } = require('child_process')
const { green, yellow, red } = require('chalk')

const rootPath = process.cwd()
const cacheDependenciesFile = path.join(__dirname, '_dependencies_cache.json')
const packageJSONFile = path.join(__dirname, '../../package.json')

function WebpackBuildDllPlugin(newOptions) {
	const defaultOptions = { dllConfigPath: '', forceBuild: false }
	const options = { ...defaultOptions, ...newOptions }
	this.options = options

	let { dllConfigPath } = options
	if (dllConfigPath) {
		dllConfigPath = path.isAbsolute(dllConfigPath) ? dllConfigPath : path.join(rootPath, dllConfigPath)
		if (!fs.existsSync(dllConfigPath)) {
			throw Error('[webpack-build-dll-plugin] Not Found DllConfigPath.\n')
		}
	} else {
		throw Error('[webpack-build-dll-plugin] Please set DllConfigPath.\n')
	}

	const dllConfig = require(dllConfigPath)
	const configKeys = ['entry', 'output', 'plugins']
	for (const configKey of configKeys) {
		if (!configKey in dllConfig) {
			throw Error(`DllReference config is required: ${configKey}`)
		}
	}

	const { entry, output, plugins } = dllConfig
	let dllPlugin
	for (const plugin of plugins) {
		if (plugin instanceof DllPlugin) {
			dllPlugin = plugin
			break
		}
	}

	if (dllPlugin === void 0) {
		throw Error('Not Found DllReference Plugin.')
	}

	const entryNames = Object.keys(entry)
	const buildJSFiles = []
	const manifestFiles = []
	for (const entryName of entryNames) {
		buildJSFiles.push(
			getJoinPaths(rootPath, output.path, output.filename.replace('[name]', entryName) )
		)
		manifestFiles.push(
			getJoinPaths(rootPath, dllPlugin.options.path.replace('[name]', entryName))
		)
	}
	const allBuildFiles = [...buildJSFiles, ...manifestFiles]
	if (options.forceBuild || !allBuildFiles.every(buildFile => fs.existsSync(buildFile))) {
		buildDllReferenceFiles(dllConfigPath)
	} else {
		checkDependencies(dllConfigPath)
	}
}

function buildDllReferenceFiles(dllConfigPath) {
	console.log(green(
		execSync(`cross-env NODE_ENV=${process.env.NODE_ENV || 'development'} webpack --config ${dllConfigPath}`)
	))
	fs.readFile(packageJSONFile, 'utf8', (err, data) => {
		if (!err) {
			fs.writeFile(cacheDependenciesFile, JSON.stringify(JSON.parse(data).dependencies || {}))
		}
	})
	console.log(yellow('[webpack-build-dll-plugin] DllReference is builded.\n'))
}

function getJoinPaths(rootPath, ...joinPaths) {
	return path.join(rootPath, ...joinPaths.map(joinPath => (
		path.isAbsolute(joinPath) ? path.relative(joinPath, rootPath) : joinPath
	)))
}

function checkDependencies(dllConfigPath) {
	console.log(yellow('[webpack-build-dll-plugin] DllReference Files is already builded.\n'))
	console.log(yellow('Now Checking Manifest Files Different...\n'))
	if (fs.existsSync(packageJSONFile)) {
		const dependencies = JSON.parse(fs.readFileSync(packageJSONFile)).dependencies
		const oldDependencies = JSON.parse(fs.readFileSync(cacheDependenciesFile))
		const dependenciesNames = Object.keys(dependencies)
		const oldDependenciesNames = Object.keys(oldDependencies)
		if (dependenciesNames.length !== oldDependenciesNames.length) {
			buildDllReferenceFiles(dllConfigPath)
		} else {
			let isSameDependencies = true
			for (const name of dependenciesNames) {
				if (dependencies[name] !== oldDependencies[name]) {
					console.log(green('[webpack-build-dll-plugin] your package.json dependencies is changed, will rebuild DllReference.'))
					buildDllReferenceFiles(dllConfigPath)
					isSameDependencies = false
					break
				}
			}
			if (isSameDependencies) {
				console.log(green('[webpack-build-dll-plugin] your package.json dependencies is look the same, DllReference will not rebuild.'))
			}
		}
	} else {
		console.error(red('[webpack-build-dll-plugin] Cannot find package.json file...'))
	}
}

WebpackBuildDllPlugin.prototype.apply = function(compiler) {}

module.exports = WebpackBuildDllPlugin
<commit_msg>Fixed Not find CACHE File Bug<commit_after>const path = require('path')
const fs = require('fs')
const async = require('async')
const DllPlugin = require('webpack/lib/DllPlugin')
const { execSync } = require('child_process')
const { green, yellow, red } = require('chalk')

const rootPath = process.cwd()
const cacheDependenciesFile = path.join(__dirname, '_dependencies_cache.json')
const packageJSONFile = path.join(__dirname, '../../package.json')

function WebpackBuildDllPlugin(newOptions) {
	const defaultOptions = { dllConfigPath: '', forceBuild: false }
	const options = { ...defaultOptions, ...newOptions }
	this.options = options

	let { dllConfigPath } = options
	if (dllConfigPath) {
		dllConfigPath = path.isAbsolute(dllConfigPath) ? dllConfigPath : path.join(rootPath, dllConfigPath)
		if (!fs.existsSync(dllConfigPath)) {
			throw Error('[webpack-build-dll-plugin] Not Found DllConfigPath.\n')
		}
	} else {
		throw Error('[webpack-build-dll-plugin] Please set DllConfigPath.\n')
	}

	const dllConfig = require(dllConfigPath)
	const configKeys = ['entry', 'output', 'plugins']
	for (const configKey of configKeys) {
		if (!configKey in dllConfig) {
			throw Error(`DllReference config is required: ${configKey}`)
		}
	}

	const { entry, output, plugins } = dllConfig
	let dllPlugin
	for (const plugin of plugins) {
		if (plugin instanceof DllPlugin) {
			dllPlugin = plugin
			break
		}
	}

	if (dllPlugin === void 0) {
		throw Error('Not Found DllReference Plugin.')
	}

	const entryNames = Object.keys(entry)
	const buildJSFiles = []
	const manifestFiles = []
	for (const entryName of entryNames) {
		buildJSFiles.push(
			getJoinPaths(rootPath, output.path, output.filename.replace('[name]', entryName) )
		)
		manifestFiles.push(
			getJoinPaths(rootPath, dllPlugin.options.path.replace('[name]', entryName))
		)
	}
	const allBuildFiles = [...buildJSFiles, ...manifestFiles]
	if (options.forceBuild || !allBuildFiles.every(buildFile => fs.existsSync(buildFile))) {
		buildDllReferenceFiles(dllConfigPath)
	} else {
		checkDependencies(dllConfigPath)
	}
}

function buildDllReferenceFiles(dllConfigPath) {
	console.log(green(
		execSync(`cross-env NODE_ENV=${process.env.NODE_ENV || 'development'} webpack --config ${dllConfigPath}`)
	))
	fs.readFile(packageJSONFile, 'utf8', (err, data) => {
		if (!err) {
			fs.writeFile(cacheDependenciesFile, JSON.stringify(JSON.parse(data).dependencies || {}))
		}
	})
	console.log(yellow('[webpack-build-dll-plugin] DllReference is builded.\n'))
}

function getJoinPaths(rootPath, ...joinPaths) {
	return path.join(rootPath, ...joinPaths.map(joinPath => (
		path.isAbsolute(joinPath) ? path.relative(joinPath, rootPath) : joinPath
	)))
}

function checkDependencies(dllConfigPath) {
	console.log(yellow('[webpack-build-dll-plugin] DllReference Files is already builded.\n'))
	console.log(yellow('Now Checking Manifest Files Different...\n'))
	if (fs.existsSync(packageJSONFile) && fs.existsSync(cacheDependenciesFile)) {
		const dependencies = JSON.parse(fs.readFileSync(packageJSONFile)).dependencies
		const oldDependencies = JSON.parse(fs.readFileSync(cacheDependenciesFile))
		const dependenciesNames = Object.keys(dependencies)
		const oldDependenciesNames = Object.keys(oldDependencies)
		if (dependenciesNames.length !== oldDependenciesNames.length) {
			buildDllReferenceFiles(dllConfigPath)
		} else {
			let isSameDependencies = true
			for (const name of dependenciesNames) {
				if (dependencies[name] !== oldDependencies[name]) {
					console.log(green('[webpack-build-dll-plugin] your package.json dependencies is changed, will rebuild DllReference.'))
					buildDllReferenceFiles(dllConfigPath)
					isSameDependencies = false
					break
				}
			}
			if (isSameDependencies) {
				console.log(green('[webpack-build-dll-plugin] your package.json dependencies is look the same, DllReference will not rebuild.'))
			}
		}
	} else {
		console.error(red('[webpack-build-dll-plugin] Cannot find package.json file...'))
		buildDllReferenceFiles(dllConfigPath)
	}
}

WebpackBuildDllPlugin.prototype.apply = function(compiler) {}

module.exports = WebpackBuildDllPlugin
<|endoftext|>


<commit_before>function resetDockBadge() {
    if (arguments.length > 0) {
        console.log(arguments);
    }

    window.fluid.dockBadge = '\u25fc';
    return false;
}

function update(state) {
    state.playing = updatePlayingStatus();
    state.playingNow = state.playing
                     ? notifyPlayingNow(state.playingNow)
                     : state.playingNow;

    setTimeout(update, 500, state);
}

function notify(title, text) {
    if (!window.Notification) {
        console.log('Notifications not supported.');
        return;
    }

    if (Notification.permission === 'denied') {
        console.log('Notifications denied.')
    } else if (Notification.permission === 'default') {
        Notification.requestPermission(function() {
            notify(text);
        });
    } else if (Notification.permission === 'granted') {
        var image = null;
        var notification = window.webkitNotifications.createNotification(image, title, text);

        /*notification.onclick = function() {
            alert(1);
        };*/

        // console.log(notification);

        try {
            notification.show();
        } catch (e) {
            console.error(e);
        }
    }
}

function notifyPlayingNow(playingPrevious) {
    var playbackSoundBadge = document.querySelector('.playbackSoundBadge span[aria-hidden="true"]');

    if (!playbackSoundBadge) {
        console.error('Found no playback sound badge.')
        return null;
    }

    var playingNow = playbackSoundBadge.innerText;

    if (playingNow !== playingPrevious) {
        console.log('New song detected. Notifying!', playingPrevious, playingNow);
        var artist = 'SoundCloud Now Playing';
        var song = playingNow;

        var sendDefaultNotification = function() {
            var artistElement = document.querySelector('.soundTitle__usernameText');
            var songElement = document.querySelector('.compactTrackListItem__trackTitle');
            artist = artistElement ? artistElement.innerText : artist;
            song = songElement ? songElement.innerText : song;

            notify(artist, song);
        };

        try {
            var nowPlayingAvatarElement = document.querySelector('.playbackSoundBadge__avatar');
            if (!nowPlayingAvatarElement) {
                throw 'NowPlayingAvatarElementNotFoundException';
            }

            var artistUrl = nowPlayingAvatarElement.getAttribute('href');

            if (!artistUrl) {
                throw 'ArtistUrlNotFoundException';
            }

            var xhr = new XMLHttpRequest();
            xhr.addEventListener('load', function(e) {
                /*var doc = new DocumentFragment();
                doc.innerHTML = this.responseText;*/

                var doc = document.createElement('div');
                doc.innerHTML = this.responseText;

                var artistElement = doc.querySelector('meta[property="og:title"]');
                if (!artistElement) {
                    console.error('Found no artist element.', doc);
                    sendDefaultNotification();
                    return;
                }

                console.log('Found artist', artistElement);
                artist = artistElement.getAttribute('content');

                notify(artist, song);
            });
            xhr.open('GET', artistUrl);
            xhr.send();
        } catch (e) {
            console.error(e);
            sendDefaultNotification();
        }
    }

    return playingNow;
}

function updatePlayingStatus() {
    var playControlsContainer = document.querySelector('.playControls.m-visible');

    if (!playControlsContainer) {
        return resetDockBadge('Found no visible play controls.');
    }

    var playControls = document.getElementsByClassName('playControl');

    if (!playControls || playControls.length == 0) {
        return resetDockBadge('Found no play control.');
    }

    var playControl = playControls[0];
    var playing = playControl.title.match(/pause/i);
    var paused = playControl.title.match(/play/i);

    if (playing) {
        window.fluid.dockBadge = '\u25b6';
        return true;
    } else if (paused) {
        window.fluid.dockBadge = 'II';
    } else {
        return resetDockBadge('The play control had an unexpected title.', playControl);
    }

    return false;
}

setTimeout(update, 1000, {});<commit_msg>Use the OEmbed "API" to extract the artist name via JSON<commit_after>function update(state) {
    state.playing = updatePlayingStatus();
    state.playingNow = state.playing
                     ? notifyPlayingNow(state.playingNow)
                     : state.playingNow;

    setTimeout(update, 500, state);
}

function notify(title, text) {
    if (!window.Notification) {
        console.log('Notifications not supported.');
        return;
    }

    if (Notification.permission === 'denied') {
        console.log('Notifications denied.')
    } else if (Notification.permission === 'default') {
        Notification.requestPermission(function() {
            notify(text);
        });
    } else if (Notification.permission === 'granted') {
        var image = null;
        var notification = window.webkitNotifications.createNotification(image, title, text);

        /*notification.onclick = function() {
            alert(1);
        };*/

        // console.log(notification);

        try {
            notification.show();
        } catch (e) {
            console.error(e);
        }
    }
}

function notifyPlayingNow(playingPrevious) {
    var playbackSoundBadge = document.querySelector('.playbackSoundBadge span[aria-hidden="true"]');

    if (!playbackSoundBadge) {
        console.error('Found no playback sound badge.')
        return null;
    }

    var playingNow = playbackSoundBadge.innerText;

    if (playingNow !== playingPrevious) {
        console.log('New song detected. Notifying!', playingPrevious, playingNow);
        var artist = 'SoundCloud Now Playing';
        var song = playingNow;

        var sendDefaultNotification = function() {
            var artistElement = document.querySelector('.soundTitle__usernameText');
            var songElement = document.querySelector('.compactTrackListItem__trackTitle');
            artist = artistElement ? artistElement.innerText : artist;
            song = songElement ? songElement.innerText : song;

            notify(artist, song);
        };

        try {
            var nowPlayingAvatarElement = document.querySelector('.playbackSoundBadge__avatar');
            if (!nowPlayingAvatarElement) {
                throw new NotificationException('Now playing avatar element not found');
            }

            var songUrl = nowPlayingAvatarElement.getAttribute('href');

            if (!songUrl) {
                throw new NotificationException('Song URL not found');
            }

            var q = songUrl.indexOf('?');
            songUrl = q > -1 ? songUrl.substring(0, q) : songUrl;

            var xhr = new XMLHttpRequest();
            xhr.addEventListener('load', function(e) {
                try {
                    var json = JSON.parse(this.responseText);

                    if (!json || !json['author_name']) {
                        throw new NotificationException('Invalid JSON', { json : json, text : this.responseText });
                    }

                    artist = json['author_name'];
                } catch (e) {
                    console.error(e);
                }

                notify(artist, song);
            });
            xhr.open('GET', 'https://soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com' + encodeURI(songUrl));
            xhr.send();
        } catch (e) {
            console.error(e);
            sendDefaultNotification();
        }
    }

    return playingNow;
}

function updatePlayingStatus() {
    try {
        var playControlsContainer = document.querySelector('.playControls.m-visible');
        if (!playControlsContainer) {
            throw new PlayControlException('Found no visible play controls.');
        }

        var playControls = document.getElementsByClassName('playControl');

        if (!playControls || playControls.length == 0) {
            throw 'Found no play control.';
        }

        var playControl = playControls[0];
        var playing = playControl.title.match(/pause/i);
        var paused = playControl.title.match(/play/i);

        if (playing) {
            window.fluid.dockBadge = '\u25b6';
            return true;
        } else if (paused) {
            window.fluid.dockBadge = 'II';
        } else {
            throw new PlayControlException('The play control had an unexpected title.', playControls);
        }
    } catch (e) {
        console.error(e);
        window.fluid.dockBadge = '\u25fc';
    }

    return false;
}

function NotificationException(message, data) {
    this.message = message;
    this.data = data;
    this.toString = function() {
        return this.message;
    };
}

function PlayControlException(message, playControls) {
    this.message = message;
    this.playControls = playControls;
    this.toString = function() {
        return this.message;
    };
}

setTimeout(update, 1000, {});<|endoftext|>


<commit_before>enyo.kind({
	name: "App",
	kind: "FittableRows",
	fit: true,
  tokens: [],
	components:[
    {kind: WebLetter.Config, name: "config"},
		{kind: "onyx.Toolbar", content: "WebLetter"},
		{kind: "enyo.Scroller", fit: true,
      components: [
			{name: "main", classes: "nice-padding", allowHtml: true, components: [
        {kind: "onyx.Groupbox", name: "settings", components: [
          {kind: "WebLetter.BooleanToken", key: "folding", description: "Folding Marks"},
          {kind: "WebLetter.BooleanToken", key: "footer", description: "Footer"},
          {kind: "WebLetter.BooleanToken", key: "logo", description: "Logo"}
          ]
        },
        {kind: "WebLetter.AddressToken", name: "addresser", description: "Addresser", key: "from"},
        {kind: "WebLetter.AddressToken", name: "addressee", description: "Addressee", key: "to"},
        {kind: "onyx.Groupbox", name: "contentGroup", components: [
          {kind: "WebLetter.TextToken", description: "Subject", key: "subject", isOptional: true},
          {kind: "WebLetter.TextToken", description: "Opening", key: "opening"},
          {kind: "WebLetter.TextAreaToken", name: "lettercontent", allowHtml: true, key: "content", description: "Content", inputWidth: "100%", inputHeight: "400px"},
          {kind: "WebLetter.TextToken", key: "closing", description: "Closing"},
          {kind: "WebLetter.TextToken", key: "signatureaddition", description: "Signature Addition", isOptional: true, isEnabled: false},
          {kind: "WebLetter.TextToken", key: "ps", description: "PS", isOptional: true, isEnabled: false}
          ]
        },
        {tag: "form", name: "pdfform", showing: false},
          {kind: "onyx.Button", name: "go", content: "Generate PDF", ontap: "go"}
        ]
      }
		]}
	],
  create: function() {
    this.tokens = new Array();
    this.inherited(arguments);
    // set up form
    this.$.pdfform.attributes.method=this.$.config.getMethod();
    this.$.pdfform.attributes.action=this.$.config.getServerUrl();
  },
  handlers: {
    onNewToken: "registerToken",
    onDisableToken: "deregisterToken"
  },
  registerToken: function(inSender, inEvent) {
    this.tokens.push(inEvent.originator);
    return true;
  },
  deregisterToken: function(inSender, inEvent) {
    // do nothing since disabled tokens might be optional and
    // need the %nopt-token to be replaced
    return true;
  },
  go: function() {
    //this.$.mylogger.addContent(this.$.lettercontent.getValue());
    this.$.pdfform.destroyComponents();
    for (i in this.tokens) {
      if (this.tokens[i].isEnabled) {
        // add opt-token if the token is optional
        if (this.tokens[i].isOptional) {
          var key = "%opt-" + this.tokens[i].key;
          this.$.pdfform.createComponent({tag: "input", attributes: {name: key, value: ""}});
        }
        if (this.tokens[i].topToken) {
          var type;
          var content = "";
          var value = "";
          if (this.tokens[i].multiline) {
            type = "textarea";
            content = this.tokens[i].getInput();
          } else {
            type = "input";
            value = this.tokens[i].getInput();
          }
          var keyprefix = "token-";
          var key = keyprefix + this.tokens[i].key;
          this.$.pdfform.createComponent({tag: type, attributes: {name: key, value: value}, content: content});
        }
      }
      else { // !this.tokens[i].isEnabled
        // only add the nopt token
        if (this.tokens[i].isOptional) {
          var key = "%nopt-" + this.tokens[i].key;
          this.$.pdfform.createComponent({tag: "input", attributes: {name: key, value: ""}});
        }
      }
    }
    this.$.pdfform.render();
    if (this.$.config.getDebugging()) {
      var components = this.$.pdfform.getComponents();
      for (i in components) {
        var attrs = components[i].attributes;
        enyo.log("'" + attrs.name + "': '" + attrs.value + "'");
      }
    }
    else {
      document.getElementById(this.$.pdfform.getAttribute("id")).submit();
    }
  }

});
<commit_msg>main app: add enclosure<commit_after>enyo.kind({
	name: "App",
	kind: "FittableRows",
	fit: true,
  tokens: [],
	components:[
    {kind: WebLetter.Config, name: "config"},
		{kind: "onyx.Toolbar", content: "WebLetter"},
		{kind: "enyo.Scroller", fit: true,
      components: [
			{name: "main", classes: "nice-padding", allowHtml: true, components: [
        {kind: "onyx.Groupbox", name: "settings", components: [
          {kind: "WebLetter.BooleanToken", key: "folding", description: "Folding Marks"},
          {kind: "WebLetter.BooleanToken", key: "footer", description: "Footer"},
          {kind: "WebLetter.BooleanToken", key: "logo", description: "Logo"}
          ]
        },
        {kind: "WebLetter.AddressToken", name: "addresser", description: "Addresser", key: "from"},
        {kind: "WebLetter.AddressToken", name: "addressee", description: "Addressee", key: "to"},
        {kind: "onyx.Groupbox", name: "contentGroup", components: [
          {kind: "WebLetter.TextToken", description: "Subject", key: "subject", isOptional: true},
          {kind: "WebLetter.TextToken", description: "Opening", key: "opening"},
          {kind: "WebLetter.TextAreaToken", name: "lettercontent", allowHtml: true, key: "content", description: "Content", inputWidth: "100%", inputHeight: "400px"},
          {kind: "WebLetter.TextToken", key: "closing", description: "Closing"},
          {kind: "WebLetter.TextToken", key: "signatureaddition", description: "Signature Addition", isOptional: true, isEnabled: false},
          {kind: "WebLetter.TextToken", key: "ps", description: "PS", isOptional: true, isEnabled: false},
          {kind: "WebLetter.TextToken", key: "encl", description: "Enclosure", isOptional: true, isEnabled: false}
          ]
        },
        {tag: "form", name: "pdfform", showing: false},
          {kind: "onyx.Button", name: "go", content: "Generate PDF", ontap: "go"}
        ]
      }
		]}
	],
  create: function() {
    this.tokens = new Array();
    this.inherited(arguments);
    // set up form
    this.$.pdfform.attributes.method=this.$.config.getMethod();
    this.$.pdfform.attributes.action=this.$.config.getServerUrl();
  },
  handlers: {
    onNewToken: "registerToken",
    onDisableToken: "deregisterToken"
  },
  registerToken: function(inSender, inEvent) {
    this.tokens.push(inEvent.originator);
    return true;
  },
  deregisterToken: function(inSender, inEvent) {
    // do nothing since disabled tokens might be optional and
    // need the %nopt-token to be replaced
    return true;
  },
  go: function() {
    //this.$.mylogger.addContent(this.$.lettercontent.getValue());
    this.$.pdfform.destroyComponents();
    for (i in this.tokens) {
      if (this.tokens[i].isEnabled) {
        // add opt-token if the token is optional
        if (this.tokens[i].isOptional) {
          var key = "%opt-" + this.tokens[i].key;
          this.$.pdfform.createComponent({tag: "input", attributes: {name: key, value: ""}});
        }
        if (this.tokens[i].topToken) {
          var type;
          var content = "";
          var value = "";
          if (this.tokens[i].multiline) {
            type = "textarea";
            content = this.tokens[i].getInput();
          } else {
            type = "input";
            value = this.tokens[i].getInput();
          }
          var keyprefix = "token-";
          var key = keyprefix + this.tokens[i].key;
          this.$.pdfform.createComponent({tag: type, attributes: {name: key, value: value}, content: content});
        }
      }
      else { // !this.tokens[i].isEnabled
        // only add the nopt token
        if (this.tokens[i].isOptional) {
          var key = "%nopt-" + this.tokens[i].key;
          this.$.pdfform.createComponent({tag: "input", attributes: {name: key, value: ""}});
        }
      }
    }
    this.$.pdfform.render();
    if (this.$.config.getDebugging()) {
      var components = this.$.pdfform.getComponents();
      for (i in components) {
        var attrs = components[i].attributes;
        enyo.log("'" + attrs.name + "': '" + attrs.value + "'");
      }
    }
    else {
      document.getElementById(this.$.pdfform.getAttribute("id")).submit();
    }
  }

});
<|endoftext|>


<commit_before>
/*** ghacks-user.js troubleshooter.js v1.3 ***/

(function() {

  if("undefined" === typeof(Services)) {
    alert("about:config needs to be the active tab!");
    return;
  }

  function getMyList(arr) {
    let aRet = [];
    let dummy = 0;
    for (let i = 0, len = arr.length; i < len; i++) {
      if (Services.prefs.prefHasUserValue(arr[i])) {
        dummy = Services.prefs.getPrefType(arr[i]);
        switch (dummy) {
          case 32: // string (see https://dxr.mozilla.org/mozilla-central/source/modules/libpref/nsIPrefBranch.idl#31)
            dummy = Services.prefs.getCharPref(arr[i]);
            aRet.push({'name':arr[i],'value': dummy,'type':32});
            break;
          case 64: // int
            dummy = Services.prefs.getIntPref(arr[i]);
            aRet.push({'name':arr[i],'value': dummy,'type':64});
            break;
          case 128: // boolean
            dummy = Services.prefs.getBoolPref(arr[i]);
            aRet.push({'name':arr[i],'value': dummy,'type':128});
            break;
          default:
            console.log("error detecting pref-type for '"+arr[i]+"' !");
        }
      }
    }
    return aRet;
  }

  function reapply(arr) {
    for (let i = 0, len = arr.length; i < len; i++) {
      switch (arr[i].type) {
        case 32: // string
          Services.prefs.setCharPref(arr[i].name, arr[i].value);
          break;
        case 64: // int
          Services.prefs.setIntPref(arr[i].name, arr[i].value);
          break;
        case 128: // boolean
          Services.prefs.setBoolPref(arr[i].name, arr[i].value);
          break;
        default:
          console.log("error re-appyling value for '"+arr[i].name+"' !"); // should never happen
      }
    }
  }

  function myreset(arr) {
    for (let i = 0, len = arr.length; i < len; i++) {
      Services.prefs.clearUserPref(arr[i].name);
    }
  }

  let ops = [

    /* known culprits */
    'network.cookie.cookieBehavior',
    'network.http.referer.XOriginPolicy',
    'privacy.firstparty.isolate',
    'privacy.resistFingerprinting',
    'security.mixed_content.block_display_content',
    'svg.disabled',

    /* Storage + Cache */
    'browser.cache.offline.enable',
    'dom.indexedDB.enabled',
    'dom.storage.enabled',
    'browser.storageManager.enabled',
    'dom.storageManager.enabled',

    /* Workers, Web + Push Notifications */
    'dom.caches.enabled',
    'dom.push.connection.enabled',
    'dom.push.enabled',
    'dom.push.serverURL',
    'dom.serviceWorkers.enabled',
    'dom.workers.enabled',
    'dom.webnotifications.enabled',
    'dom.webnotifications.serviceworker.enabled',

    /* Fonts */
    'browser.display.use_document_fonts',
    'font.blacklist.underline_offset',
    'gfx.downloadable_fonts.woff2.enabled',
    'gfx.font_rendering.graphite.enabled',
    'gfx.font_rendering.opentype_svg.enabled',
    'layout.css.font-loading-api.enabled',

    /* Misc */
    'browser.link.open_newwindow.restriction',
    'canvas.capturestream.enabled',
    'dom.event.clipboardevents.enabled',
    'dom.event.contextmenu.enabled',
    'dom.idle-observers-api.enabled',
    'dom.IntersectionObserver.enabled',
    'dom.popup_allowed_events',
    'full-screen-api.enabled',
    'geo.wifi.uri',
    'intl.accept_languages',
    'javascript.options.asmjs',
    'javascript.options.wasm',
    'network.cookie.thirdparty.sessionOnly',
    'security.csp.experimentalEnabled',

    /* Hardware */
    'dom.vr.enabled',
    'media.ondevicechange.enabled',

    /* Audio + Video */
    'dom.webaudio.enabled',
    'media.autoplay.enabled',
    'media.flac.enabled',
    'media.mp4.enabled',
    'media.ogg.enabled',
    'media.opus.enabled',
    'media.raw.enabled',
    'media.wave.enabled',
    'media.webm.enabled',
    'media.wmf.enabled',

    /* Forms */
    'browser.formfill.enable',
    'signon.autofillForms',
    'signon.formlessCapture.enabled',

    /* HTTPS */
    'security.cert_pinning.enforcement_level',
    'security.family_safety.mode',
    'security.mixed_content.use_hsts',
    'security.OCSP.require',
    'security.pki.sha1_enforcement_level',
    'security.ssl.require_safe_negotiation',
    'security.ssl.treat_unsafe_negotiation_as_broken',
    'security.ssl3.dhe_rsa_aes_128_sha',
    'security.ssl3.dhe_rsa_aes_256_sha',
    'security.ssl3.ecdhe_ecdsa_aes_128_sha',
    'security.ssl3.ecdhe_rsa_aes_128_sha',
    'security.ssl3.rsa_aes_128_sha',
    'security.ssl3.rsa_aes_256_sha',
    'security.ssl3.rsa_des_ede3_sha',
    'security.tls.enable_0rtt_data',
    'security.tls.version.max',
    'security.tls.version.min',

    /* Plugins + Flash */
    'plugin.default.state',
    'plugin.defaultXpi.state',
    'plugin.sessionPermissionNow.intervalInMinutes',
    'plugin.state.flash',
    'security.xpconnect.plugin.unrestricted',

    /* unlikely to cause problems */
    'browser.tabs.remote.allowLinkedWebInFileUriProcess',
    'dom.popup_maximum',
    'layout.css.visited_links_enabled',
    'mathml.disabled',
    'network.auth.subresource-img-cross-origin-http-auth-allow',
    'network.http.redirection-limit',
    'network.protocol-handler.external.ms-windows-store',
    'privacy.trackingprotection.enabled',
    'security.data_uri.block_toplevel_data_uri_navigations',

    /* FF User-Interface */
    'browser.search.suggest.enabled',
    'browser.urlbar.autoFill',
    'browser.urlbar.autoFill.typed',
    'browser.urlbar.oneOffSearches',
    'browser.urlbar.suggest.searches',
    'keyword.enabled',

    'last.one.without.comma'
  ]

  // reset prefs that set the same value as FFs default value
  let aTEMP = getMyList(ops);
  myreset(aTEMP);
  reapply(aTEMP);

  const aBACKUP = getMyList(ops);
  //console.log(aBACKUP.length, "user-set prefs from our list detected and their values stored.");

  let myArr = aBACKUP;
  let found = false;
  let aDbg = [];
  focus();
  myreset(aBACKUP); // reset all detected prefs
  if (confirm("all detected prefs reset.\n\n!! KEEP THIS PROMPT OPEN AND TEST THE SITE IN ANOTHER TAB !!\n\nIF the problem still exists, this script can't help you - click cancel to re-apply your values and exit.\n\nClick OK if your problem is fixed.")) {
    aDbg = myArr;
    reapply(aBACKUP);
    myreset(myArr.slice(0, parseInt(myArr.length/2)));
    while (myArr.length >= 2) {
      alert("NOW TEST AGAIN !");
      if (confirm("if the problem still exists click OK, otherwise click cancel.")) {
        myArr = myArr.slice(parseInt(myArr.length/2));
        if (myArr.length == 1) {
          alert("The problem is caused by more than 1 pref !\n\nNarrowed it down to "+ aDbg.length.toString() +" prefs, check the console ...");
          break;
        }
      } else {
        myArr = myArr.slice(0, parseInt(myArr.length/2));
        aDbg = myArr;
        if (myArr.length == 1) { found = true; break; }
      }
      reapply(aBACKUP);
      myreset(myArr.slice(0, parseInt(myArr.length/2))); // reset half of the remaining prefs
    }
    reapply(aBACKUP);
  }
  else {
    reapply(aBACKUP);
    return;
  }

  if (found) {
    alert("narrowed it down to:\n\n"+myArr[0].name+"\n");
    myreset(myArr); // reset the culprit
  }
  else {
    console.log("the problem is caused by a combination of the following prefs:");
    for (let i = 0, len = aDbg.length; i < len; i++) {
      console.log(aDbg[i].name);
    }
  }

})();
<commit_msg>Update troubleshooter.js<commit_after>
/*** ghacks-user.js troubleshooter.js v1.3 ***/

(function() {

  if("undefined" === typeof(Services)) {
    alert("about:config needs to be the active tab!");
    return;
  }

  function getMyList(arr) {
    let aRet = [];
    let dummy = 0;
    for (let i = 0, len = arr.length; i < len; i++) {
      if (Services.prefs.prefHasUserValue(arr[i])) {
        dummy = Services.prefs.getPrefType(arr[i]);
        switch (dummy) {
          case 32: // string (see https://dxr.mozilla.org/mozilla-central/source/modules/libpref/nsIPrefBranch.idl#31)
            dummy = Services.prefs.getCharPref(arr[i]);
            aRet.push({'name':arr[i],'value': dummy,'type':32});
            break;
          case 64: // int
            dummy = Services.prefs.getIntPref(arr[i]);
            aRet.push({'name':arr[i],'value': dummy,'type':64});
            break;
          case 128: // boolean
            dummy = Services.prefs.getBoolPref(arr[i]);
            aRet.push({'name':arr[i],'value': dummy,'type':128});
            break;
          default:
            console.log("error detecting pref-type for '"+arr[i]+"' !");
        }
      }
    }
    return aRet;
  }

  function reapply(arr) {
    for (let i = 0, len = arr.length; i < len; i++) {
      switch (arr[i].type) {
        case 32: // string
          Services.prefs.setCharPref(arr[i].name, arr[i].value);
          break;
        case 64: // int
          Services.prefs.setIntPref(arr[i].name, arr[i].value);
          break;
        case 128: // boolean
          Services.prefs.setBoolPref(arr[i].name, arr[i].value);
          break;
        default:
          console.log("error re-appyling value for '"+arr[i].name+"' !"); // should never happen
      }
    }
  }

  function myreset(arr) {
    for (let i = 0, len = arr.length; i < len; i++) {
      Services.prefs.clearUserPref(arr[i].name);
    }
  }

  let ops = [

    /* known culprits */
    'network.cookie.cookieBehavior',
    'network.http.referer.XOriginPolicy',
    'privacy.firstparty.isolate',
    'privacy.resistFingerprinting',
    'security.mixed_content.block_display_content',
    'svg.disabled',

    /* Storage + Cache */
    'browser.cache.offline.enable',
    'dom.indexedDB.enabled',
    'dom.storage.enabled',
    'browser.storageManager.enabled',
    'dom.storageManager.enabled',

    /* Workers, Web + Push Notifications */
    'dom.caches.enabled',
    'dom.push.connection.enabled',
    'dom.push.enabled',
    'dom.push.serverURL',
    'dom.serviceWorkers.enabled',
    'dom.workers.enabled',
    'dom.webnotifications.enabled',
    'dom.webnotifications.serviceworker.enabled',

    /* Fonts */
    'browser.display.use_document_fonts',
    'font.blacklist.underline_offset',
    'gfx.downloadable_fonts.woff2.enabled',
    'gfx.font_rendering.graphite.enabled',
    'gfx.font_rendering.opentype_svg.enabled',
    'layout.css.font-loading-api.enabled',

    /* Misc */
    'browser.link.open_newwindow.restriction',
    'canvas.capturestream.enabled',
    'dom.event.clipboardevents.enabled',
    'dom.event.contextmenu.enabled',
    'dom.IntersectionObserver.enabled',
    'dom.popup_allowed_events',
    'full-screen-api.enabled',
    'geo.wifi.uri',
    'intl.accept_languages',
    'javascript.options.asmjs',
    'javascript.options.wasm',
    'network.cookie.thirdparty.sessionOnly',
    'security.csp.experimentalEnabled',

    /* Hardware */
    'dom.vr.enabled',
    'media.ondevicechange.enabled',

    /* Audio + Video */
    'dom.webaudio.enabled',
    'media.autoplay.enabled',
    'media.flac.enabled',
    'media.mp4.enabled',
    'media.ogg.enabled',
    'media.opus.enabled',
    'media.raw.enabled',
    'media.wave.enabled',
    'media.webm.enabled',
    'media.wmf.enabled',

    /* Forms */
    'browser.formfill.enable',
    'signon.autofillForms',
    'signon.formlessCapture.enabled',

    /* HTTPS */
    'security.cert_pinning.enforcement_level',
    'security.family_safety.mode',
    'security.mixed_content.use_hsts',
    'security.OCSP.require',
    'security.pki.sha1_enforcement_level',
    'security.ssl.require_safe_negotiation',
    'security.ssl.treat_unsafe_negotiation_as_broken',
    'security.ssl3.dhe_rsa_aes_128_sha',
    'security.ssl3.dhe_rsa_aes_256_sha',
    'security.ssl3.ecdhe_ecdsa_aes_128_sha',
    'security.ssl3.ecdhe_rsa_aes_128_sha',
    'security.ssl3.rsa_aes_128_sha',
    'security.ssl3.rsa_aes_256_sha',
    'security.ssl3.rsa_des_ede3_sha',
    'security.tls.enable_0rtt_data',
    'security.tls.version.max',
    'security.tls.version.min',

    /* Plugins + Flash */
    'plugin.default.state',
    'plugin.defaultXpi.state',
    'plugin.sessionPermissionNow.intervalInMinutes',
    'plugin.state.flash',

    /* unlikely to cause problems */
    'browser.tabs.remote.allowLinkedWebInFileUriProcess',
    'dom.popup_maximum',
    'layout.css.visited_links_enabled',
    'mathml.disabled',
    'network.auth.subresource-img-cross-origin-http-auth-allow',
    'network.http.redirection-limit',
    'network.protocol-handler.external.ms-windows-store',
    'privacy.trackingprotection.enabled',
    'security.data_uri.block_toplevel_data_uri_navigations',

    /* FF User-Interface */
    'browser.search.suggest.enabled',
    'browser.urlbar.autoFill',
    'browser.urlbar.autoFill.typed',
    'browser.urlbar.oneOffSearches',
    'browser.urlbar.suggest.searches',
    'keyword.enabled',

    'last.one.without.comma'
  ]

  // reset prefs that set the same value as FFs default value
  let aTEMP = getMyList(ops);
  myreset(aTEMP);
  reapply(aTEMP);

  const aBACKUP = getMyList(ops);
  //console.log(aBACKUP.length, "user-set prefs from our list detected and their values stored.");

  let myArr = aBACKUP;
  let found = false;
  let aDbg = [];
  focus();
  myreset(aBACKUP); // reset all detected prefs
  if (confirm("all detected prefs reset.\n\n!! KEEP THIS PROMPT OPEN AND TEST THE SITE IN ANOTHER TAB !!\n\nIF the problem still exists, this script can't help you - click cancel to re-apply your values and exit.\n\nClick OK if your problem is fixed.")) {
    aDbg = myArr;
    reapply(aBACKUP);
    myreset(myArr.slice(0, parseInt(myArr.length/2)));
    while (myArr.length >= 2) {
      alert("NOW TEST AGAIN !");
      if (confirm("if the problem still exists click OK, otherwise click cancel.")) {
        myArr = myArr.slice(parseInt(myArr.length/2));
        if (myArr.length == 1) {
          alert("The problem is caused by more than 1 pref !\n\nNarrowed it down to "+ aDbg.length.toString() +" prefs, check the console ...");
          break;
        }
      } else {
        myArr = myArr.slice(0, parseInt(myArr.length/2));
        aDbg = myArr;
        if (myArr.length == 1) { found = true; break; }
      }
      reapply(aBACKUP);
      myreset(myArr.slice(0, parseInt(myArr.length/2))); // reset half of the remaining prefs
    }
    reapply(aBACKUP);
  }
  else {
    reapply(aBACKUP);
    return;
  }

  if (found) {
    alert("narrowed it down to:\n\n"+myArr[0].name+"\n");
    myreset(myArr); // reset the culprit
  }
  else {
    console.log("the problem is caused by a combination of the following prefs:");
    for (let i = 0, len = aDbg.length; i < len; i++) {
      console.log(aDbg[i].name);
    }
  }

})();
<|endoftext|>


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

couchTests.attachments_multipart= function(debug) {
  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
  db.deleteDb();
  db.createDb();
  if (debug) debugger;

  // mime multipart

  xhr = CouchDB.request("PUT", "/test_suite_db/multipart", {
    headers: {"Content-Type": "multipart/related;boundary=\"abc123\""},
    body:
      "--abc123\r\n" +
      "content-type: application/json\r\n" +
      "\r\n" +
      JSON.stringify({
        "body":"This is a body.",
        "_attachments":{
          "foo.txt": {
            "follows":true,
            "content_type":"text/plain",
            "length":21
            },
          "bar.txt": {
            "follows":true,
            "content_type":"text/plain",
            "length":20
            },
          "baz.txt": {
            "follows":true,
            "content_type":"text/plain",
            "length":19
            }
          }
        }) +
      "\r\n--abc123\r\n" +
      "\r\n" +
      "this is 21 chars long" +
      "\r\n--abc123\r\n" +
      "\r\n" +
      "this is 20 chars lon" +
      "\r\n--abc123\r\n" +
      "\r\n" +
      "this is 19 chars lo" +
      "\r\n--abc123--"
    });

  var result = JSON.parse(xhr.responseText);

  T(result.ok);

  TEquals(201, xhr.status, "should send 201 Accepted");

  xhr = CouchDB.request("GET", "/test_suite_db/multipart/foo.txt");

  T(xhr.responseText == "this is 21 chars long");

  xhr = CouchDB.request("GET", "/test_suite_db/multipart/bar.txt");

  T(xhr.responseText == "this is 20 chars lon");

  xhr = CouchDB.request("GET", "/test_suite_db/multipart/baz.txt");

  T(xhr.responseText == "this is 19 chars lo");

  // now edit an attachment

  var doc = db.open("multipart");
  var firstrev = doc._rev;

  T(doc._attachments["foo.txt"].stub == true);
  T(doc._attachments["bar.txt"].stub == true);
  T(doc._attachments["baz.txt"].stub == true);

  //lets change attachment bar
  delete doc._attachments["bar.txt"].stub; // remove stub member (or could set to false)
  doc._attachments["bar.txt"].length = 18;
  doc._attachments["bar.txt"].follows = true;
  //lets delete attachment baz:
  delete doc._attachments["baz.txt"];

  var xhr = CouchDB.request("PUT", "/test_suite_db/multipart", {
    headers: {"Content-Type": "multipart/related;boundary=\"abc123\""},
    body:
      "--abc123\r\n" +
      "content-type: application/json\r\n" +
      "\r\n" +
      JSON.stringify(doc) +
      "\r\n--abc123\r\n" +
      "\r\n" +
      "this is 18 chars l" +
      "\r\n--abc123--"
    });

  xhr = CouchDB.request("GET", "/test_suite_db/multipart/bar.txt");

  T(xhr.responseText == "this is 18 chars l");

  xhr = CouchDB.request("GET", "/test_suite_db/multipart/baz.txt");
  T(xhr.status == 404);

  // now test receiving multipart docs

  function getBoundary(xhr) {
    if (xhr instanceof XMLHttpRequest) {
      var ctype = xhr.getResponseHeader("Content-Type");
    } else {
      var ctype = xhr.headers['Content-Type'];
    }
    var ctypeArgs = ctype.split("; ").slice(1);
    var boundary = null;
    for(var i=0; i<ctypeArgs.length; i++) {
      if (ctypeArgs[i].indexOf("boundary=") == 0) {
        boundary = ctypeArgs[i].split("=")[1];
        if (boundary.charAt(0) == '"') {
          // stringified boundary, parse as json
          // (will maybe not if there are escape quotes)
          boundary = JSON.parse(boundary);
        }
      }
    }
    return boundary;
  }

  function parseMultipart(xhr) {
    var boundary = getBoundary(xhr);
    if (xhr instanceof XMLHttpRequest) {
      var mimetext = xhr.responseText;
    } else {
      var mimetext = xhr.body;
    }
    // strip off leading boundary
    var leading = "--" + boundary + "\r\n";
    var last = "\r\n--" + boundary + "--";

    // strip off leading and trailing boundary
    var leadingIdx = mimetext.indexOf(leading) + leading.length;
    var trailingIdx = mimetext.indexOf(last);
    mimetext = mimetext.slice(leadingIdx, trailingIdx);

    // now split the sections
    var sections = mimetext.split(new RegExp("\\r\\n--" + boundary));

    // spilt out the headers for each section
    for(var i=0; i < sections.length; i++) {
      var section = sections[i];
      var headerEndIdx = section.indexOf("\r\n\r\n");
      var headersraw = section.slice(0, headerEndIdx).split(/\r\n/);
      var body = section.slice(headerEndIdx + 4);
      var headers = {};
      for(var j=0; j<headersraw.length; j++) {
        var tmp = headersraw[j].split(": ");
        headers[tmp[0]] = tmp[1];
      }
      sections[i] = {"headers":headers, "body":body};
    }

    return sections;
  }

  xhr = CouchDB.request("GET", "/test_suite_db/multipart?attachments=true",
    {headers:{"accept": "multipart/related,*/*;"}});

  T(xhr.status == 200);

  // parse out the multipart

  var sections = parseMultipart(xhr);

  T(sections.length == 3);

  // The first section is the json doc. Check it's content-type. It contains
  // the metadata for all the following attachments

  T(sections[0].headers['content-type'] == "application/json");

  var doc = JSON.parse(sections[0].body);

  T(doc._attachments['foo.txt'].follows == true);
  T(doc._attachments['bar.txt'].follows == true);

  T(sections[1].body == "this is 21 chars long");
  T(sections[2].body == "this is 18 chars l");

  // now get attachments incrementally (only the attachments changes since
  // a certain rev).

  xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"" + firstrev + "\"]",
    {headers:{"accept": "multipart/related, */*"}});

  T(xhr.status == 200);

  var sections = parseMultipart(xhr);

  T(sections.length == 2);

  var doc = JSON.parse(sections[0].body);

  T(doc._attachments['foo.txt'].stub == true);
  T(doc._attachments['bar.txt'].follows == true);

  T(sections[1].body == "this is 18 chars l");

  // try the atts_since parameter together with the open_revs parameter
  xhr = CouchDB.request(
    "GET",
    '/test_suite_db/multipart?open_revs=["' +
      doc._rev + '"]&atts_since=["' + firstrev + '"]',
    {headers: {"accept": "multipart/mixed"}}
  );

  T(xhr.status === 200);

  sections = parseMultipart(xhr);
  // 1 section, with a multipart/related Content-Type
  T(sections.length === 1);
  T(sections[0].headers['Content-Type'].indexOf('multipart/related;') === 0);

  var innerSections = parseMultipart(sections[0]);
  // 2 inner sections: a document body section plus an attachment data section
  T(innerSections.length === 2);
  T(innerSections[0].headers['content-type'] === 'application/json');

  doc = JSON.parse(innerSections[0].body);

  T(doc._attachments['foo.txt'].stub === true);
  T(doc._attachments['bar.txt'].follows === true);

  T(innerSections[1].body === "this is 18 chars l");

  // try it with a rev that doesn't exist (should get all attachments)

  xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"1-2897589\"]",
    {headers:{"accept": "multipart/related,*/*;"}});

  T(xhr.status == 200);

  var sections = parseMultipart(xhr);

  T(sections.length == 3);

  var doc = JSON.parse(sections[0].body);

  T(doc._attachments['foo.txt'].follows == true);
  T(doc._attachments['bar.txt'].follows == true);

  T(sections[1].body == "this is 21 chars long");
  T(sections[2].body == "this is 18 chars l");

  // try it with a rev that doesn't exist, and one that does

  xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"1-2897589\",\"" + firstrev + "\"]",
    {headers:{"accept": "multipart/related,*/*;"}});

  T(xhr.status == 200);

  var sections = parseMultipart(xhr);

  T(sections.length == 2);

  var doc = JSON.parse(sections[0].body);

  T(doc._attachments['foo.txt'].stub == true);
  T(doc._attachments['bar.txt'].follows == true);

  T(sections[1].body == "this is 18 chars l");

  // check that with the document multipart/mixed API it's possible to receive
  // attachments in compressed form (if they're stored in compressed form)

  var server_config = [
    {
      section: "attachments",
      key: "compression_level",
      value: "8"
    },
    {
      section: "attachments",
      key: "compressible_types",
      value: "text/plain"
    }
  ];

  function testMultipartAttCompression() {
    var doc = { _id: "foobar" };
    var lorem =
      CouchDB.request("GET", "/_utils/script/test/lorem.txt").responseText;
    var helloData = "hello world";

    TEquals(true, db.save(doc).ok);

    var firstRev = doc._rev;
    var xhr = CouchDB.request(
      "PUT",
      "/" + db.name + "/" + doc._id + "/data.bin?rev=" + firstRev,
      {
        body: helloData,
        headers: {"Content-Type": "application/binary"}
      }
    );
    TEquals(201, xhr.status);

    var secondRev = db.open(doc._id)._rev;
    xhr = CouchDB.request(
      "PUT",
      "/" + db.name + "/" + doc._id + "/lorem.txt?rev=" + secondRev,
      {
        body: lorem,
        headers: {"Content-Type": "text/plain"}
      }
    );
    TEquals(201, xhr.status);

    var thirdRev = db.open(doc._id)._rev;

    xhr = CouchDB.request(
      "GET",
      '/' + db.name + '/' + doc._id + '?open_revs=["' + thirdRev + '"]',
      {
        headers: {
          "Accept": "multipart/mixed",
          "X-CouchDB-Send-Encoded-Atts": "true"
        }
      }
    );
    TEquals(200, xhr.status);

    var sections = parseMultipart(xhr);
    // 1 section, with a multipart/related Content-Type
    TEquals(1, sections.length);
    TEquals(0,
      sections[0].headers['Content-Type'].indexOf('multipart/related;'));

    var innerSections = parseMultipart(sections[0]);
    // 3 inner sections: a document body section plus 2 attachment data sections
    TEquals(3, innerSections.length);
    TEquals('application/json', innerSections[0].headers['content-type']);

    doc = JSON.parse(innerSections[0].body);

    TEquals(true, doc._attachments['lorem.txt'].follows);
    TEquals("gzip", doc._attachments['lorem.txt'].encoding);
    TEquals(true, doc._attachments['data.bin'].follows);
    T(doc._attachments['data.bin'] !== "gzip");

    if (innerSections[1].body === helloData) {
      T(innerSections[2].body !== lorem);
    } else if (innerSections[2].body === helloData) {
      T(innerSections[1].body !== lorem);
    } else {
      T(false, "Could not found data.bin attachment data");
    }

    // now test that it works together with the atts_since parameter

    xhr = CouchDB.request(
      "GET",
      '/' + db.name + '/' + doc._id + '?open_revs=["' + thirdRev + '"]' +
        '&atts_since=["' + secondRev + '"]',
      {
        headers: {
          "Accept": "multipart/mixed",
          "X-CouchDB-Send-Encoded-Atts": "true"
        }
      }
    );
    TEquals(200, xhr.status);

    sections = parseMultipart(xhr);
    // 1 section, with a multipart/related Content-Type
    TEquals(1, sections.length);
    TEquals(0,
      sections[0].headers['Content-Type'].indexOf('multipart/related;'));

    innerSections = parseMultipart(sections[0]);
    // 2 inner sections: a document body section plus 1 attachment data section
    TEquals(2, innerSections.length);
    TEquals('application/json', innerSections[0].headers['content-type']);

    doc = JSON.parse(innerSections[0].body);

    TEquals(true, doc._attachments['lorem.txt'].follows);
    TEquals("gzip", doc._attachments['lorem.txt'].encoding);
    TEquals("undefined", typeof doc._attachments['data.bin'].follows);
    TEquals(true, doc._attachments['data.bin'].stub);
    T(innerSections[1].body !== lorem);
  }

  run_on_modified_server(server_config, testMultipartAttCompression);

  // cleanup
  db.deleteDb();
};
<commit_msg>Make the doc multipart GET APIs always send attachments compressed<commit_after>// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

couchTests.attachments_multipart= function(debug) {
  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
  db.deleteDb();
  db.createDb();
  if (debug) debugger;

  // mime multipart

  xhr = CouchDB.request("PUT", "/test_suite_db/multipart", {
    headers: {"Content-Type": "multipart/related;boundary=\"abc123\""},
    body:
      "--abc123\r\n" +
      "content-type: application/json\r\n" +
      "\r\n" +
      JSON.stringify({
        "body":"This is a body.",
        "_attachments":{
          "foo.txt": {
            "follows":true,
            "content_type":"application/test",
            "length":21
            },
          "bar.txt": {
            "follows":true,
            "content_type":"application/test",
            "length":20
            },
          "baz.txt": {
            "follows":true,
            "content_type":"application/test",
            "length":19
            }
          }
        }) +
      "\r\n--abc123\r\n" +
      "\r\n" +
      "this is 21 chars long" +
      "\r\n--abc123\r\n" +
      "\r\n" +
      "this is 20 chars lon" +
      "\r\n--abc123\r\n" +
      "\r\n" +
      "this is 19 chars lo" +
      "\r\n--abc123--"
    });

  var result = JSON.parse(xhr.responseText);

  T(result.ok);

  TEquals(201, xhr.status, "should send 201 Accepted");

  xhr = CouchDB.request("GET", "/test_suite_db/multipart/foo.txt");

  T(xhr.responseText == "this is 21 chars long");

  xhr = CouchDB.request("GET", "/test_suite_db/multipart/bar.txt");

  T(xhr.responseText == "this is 20 chars lon");

  xhr = CouchDB.request("GET", "/test_suite_db/multipart/baz.txt");

  T(xhr.responseText == "this is 19 chars lo");

  // now edit an attachment

  var doc = db.open("multipart");
  var firstrev = doc._rev;

  T(doc._attachments["foo.txt"].stub == true);
  T(doc._attachments["bar.txt"].stub == true);
  T(doc._attachments["baz.txt"].stub == true);

  //lets change attachment bar
  delete doc._attachments["bar.txt"].stub; // remove stub member (or could set to false)
  doc._attachments["bar.txt"].length = 18;
  doc._attachments["bar.txt"].follows = true;
  //lets delete attachment baz:
  delete doc._attachments["baz.txt"];

  var xhr = CouchDB.request("PUT", "/test_suite_db/multipart", {
    headers: {"Content-Type": "multipart/related;boundary=\"abc123\""},
    body:
      "--abc123\r\n" +
      "content-type: application/json\r\n" +
      "\r\n" +
      JSON.stringify(doc) +
      "\r\n--abc123\r\n" +
      "\r\n" +
      "this is 18 chars l" +
      "\r\n--abc123--"
    });

  xhr = CouchDB.request("GET", "/test_suite_db/multipart/bar.txt");

  T(xhr.responseText == "this is 18 chars l");

  xhr = CouchDB.request("GET", "/test_suite_db/multipart/baz.txt");
  T(xhr.status == 404);

  // now test receiving multipart docs

  function getBoundary(xhr) {
    if (xhr instanceof XMLHttpRequest) {
      var ctype = xhr.getResponseHeader("Content-Type");
    } else {
      var ctype = xhr.headers['Content-Type'];
    }
    var ctypeArgs = ctype.split("; ").slice(1);
    var boundary = null;
    for(var i=0; i<ctypeArgs.length; i++) {
      if (ctypeArgs[i].indexOf("boundary=") == 0) {
        boundary = ctypeArgs[i].split("=")[1];
        if (boundary.charAt(0) == '"') {
          // stringified boundary, parse as json
          // (will maybe not if there are escape quotes)
          boundary = JSON.parse(boundary);
        }
      }
    }
    return boundary;
  }

  function parseMultipart(xhr) {
    var boundary = getBoundary(xhr);
    if (xhr instanceof XMLHttpRequest) {
      var mimetext = xhr.responseText;
    } else {
      var mimetext = xhr.body;
    }
    // strip off leading boundary
    var leading = "--" + boundary + "\r\n";
    var last = "\r\n--" + boundary + "--";

    // strip off leading and trailing boundary
    var leadingIdx = mimetext.indexOf(leading) + leading.length;
    var trailingIdx = mimetext.indexOf(last);
    mimetext = mimetext.slice(leadingIdx, trailingIdx);

    // now split the sections
    var sections = mimetext.split(new RegExp("\\r\\n--" + boundary));

    // spilt out the headers for each section
    for(var i=0; i < sections.length; i++) {
      var section = sections[i];
      var headerEndIdx = section.indexOf("\r\n\r\n");
      var headersraw = section.slice(0, headerEndIdx).split(/\r\n/);
      var body = section.slice(headerEndIdx + 4);
      var headers = {};
      for(var j=0; j<headersraw.length; j++) {
        var tmp = headersraw[j].split(": ");
        headers[tmp[0]] = tmp[1];
      }
      sections[i] = {"headers":headers, "body":body};
    }

    return sections;
  }

  xhr = CouchDB.request("GET", "/test_suite_db/multipart?attachments=true",
    {headers:{"accept": "multipart/related,*/*;"}});

  T(xhr.status == 200);

  // parse out the multipart

  var sections = parseMultipart(xhr);

  T(sections.length == 3);

  // The first section is the json doc. Check it's content-type. It contains
  // the metadata for all the following attachments

  T(sections[0].headers['content-type'] == "application/json");

  var doc = JSON.parse(sections[0].body);

  T(doc._attachments['foo.txt'].follows == true);
  T(doc._attachments['bar.txt'].follows == true);

  T(sections[1].body == "this is 21 chars long");
  T(sections[2].body == "this is 18 chars l");

  // now get attachments incrementally (only the attachments changes since
  // a certain rev).

  xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"" + firstrev + "\"]",
    {headers:{"accept": "multipart/related, */*"}});

  T(xhr.status == 200);

  var sections = parseMultipart(xhr);

  T(sections.length == 2);

  var doc = JSON.parse(sections[0].body);

  T(doc._attachments['foo.txt'].stub == true);
  T(doc._attachments['bar.txt'].follows == true);

  T(sections[1].body == "this is 18 chars l");

  // try the atts_since parameter together with the open_revs parameter
  xhr = CouchDB.request(
    "GET",
    '/test_suite_db/multipart?open_revs=["' +
      doc._rev + '"]&atts_since=["' + firstrev + '"]',
    {headers: {"accept": "multipart/mixed"}}
  );

  T(xhr.status === 200);

  sections = parseMultipart(xhr);
  // 1 section, with a multipart/related Content-Type
  T(sections.length === 1);
  T(sections[0].headers['Content-Type'].indexOf('multipart/related;') === 0);

  var innerSections = parseMultipart(sections[0]);
  // 2 inner sections: a document body section plus an attachment data section
  T(innerSections.length === 2);
  T(innerSections[0].headers['content-type'] === 'application/json');

  doc = JSON.parse(innerSections[0].body);

  T(doc._attachments['foo.txt'].stub === true);
  T(doc._attachments['bar.txt'].follows === true);

  T(innerSections[1].body === "this is 18 chars l");

  // try it with a rev that doesn't exist (should get all attachments)

  xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"1-2897589\"]",
    {headers:{"accept": "multipart/related,*/*;"}});

  T(xhr.status == 200);

  var sections = parseMultipart(xhr);

  T(sections.length == 3);

  var doc = JSON.parse(sections[0].body);

  T(doc._attachments['foo.txt'].follows == true);
  T(doc._attachments['bar.txt'].follows == true);

  T(sections[1].body == "this is 21 chars long");
  T(sections[2].body == "this is 18 chars l");

  // try it with a rev that doesn't exist, and one that does

  xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"1-2897589\",\"" + firstrev + "\"]",
    {headers:{"accept": "multipart/related,*/*;"}});

  T(xhr.status == 200);

  var sections = parseMultipart(xhr);

  T(sections.length == 2);

  var doc = JSON.parse(sections[0].body);

  T(doc._attachments['foo.txt'].stub == true);
  T(doc._attachments['bar.txt'].follows == true);

  T(sections[1].body == "this is 18 chars l");

  // check that with the document multipart/mixed API it's possible to receive
  // attachments in compressed form (if they're stored in compressed form)

  var server_config = [
    {
      section: "attachments",
      key: "compression_level",
      value: "8"
    },
    {
      section: "attachments",
      key: "compressible_types",
      value: "text/plain"
    }
  ];

  function testMultipartAttCompression() {
    var doc = { _id: "foobar" };
    var lorem =
      CouchDB.request("GET", "/_utils/script/test/lorem.txt").responseText;
    var helloData = "hello world";

    TEquals(true, db.save(doc).ok);

    var firstRev = doc._rev;
    var xhr = CouchDB.request(
      "PUT",
      "/" + db.name + "/" + doc._id + "/data.bin?rev=" + firstRev,
      {
        body: helloData,
        headers: {"Content-Type": "application/binary"}
      }
    );
    TEquals(201, xhr.status);

    var secondRev = db.open(doc._id)._rev;
    xhr = CouchDB.request(
      "PUT",
      "/" + db.name + "/" + doc._id + "/lorem.txt?rev=" + secondRev,
      {
        body: lorem,
        headers: {"Content-Type": "text/plain"}
      }
    );
    TEquals(201, xhr.status);

    var thirdRev = db.open(doc._id)._rev;

    xhr = CouchDB.request(
      "GET",
      '/' + db.name + '/' + doc._id + '?open_revs=["' + thirdRev + '"]',
      {
        headers: {
          "Accept": "multipart/mixed",
          "X-CouchDB-Send-Encoded-Atts": "true"
        }
      }
    );
    TEquals(200, xhr.status);

    var sections = parseMultipart(xhr);
    // 1 section, with a multipart/related Content-Type
    TEquals(1, sections.length);
    TEquals(0,
      sections[0].headers['Content-Type'].indexOf('multipart/related;'));

    var innerSections = parseMultipart(sections[0]);
    // 3 inner sections: a document body section plus 2 attachment data sections
    TEquals(3, innerSections.length);
    TEquals('application/json', innerSections[0].headers['content-type']);

    doc = JSON.parse(innerSections[0].body);

    TEquals(true, doc._attachments['lorem.txt'].follows);
    TEquals("gzip", doc._attachments['lorem.txt'].encoding);
    TEquals(true, doc._attachments['data.bin'].follows);
    T(doc._attachments['data.bin'] !== "gzip");

    if (innerSections[1].body === helloData) {
      T(innerSections[2].body !== lorem);
    } else if (innerSections[2].body === helloData) {
      T(innerSections[1].body !== lorem);
    } else {
      T(false, "Could not found data.bin attachment data");
    }

    // now test that it works together with the atts_since parameter

    xhr = CouchDB.request(
      "GET",
      '/' + db.name + '/' + doc._id + '?open_revs=["' + thirdRev + '"]' +
        '&atts_since=["' + secondRev + '"]',
      {
        headers: {
          "Accept": "multipart/mixed",
          "X-CouchDB-Send-Encoded-Atts": "true"
        }
      }
    );
    TEquals(200, xhr.status);

    sections = parseMultipart(xhr);
    // 1 section, with a multipart/related Content-Type
    TEquals(1, sections.length);
    TEquals(0,
      sections[0].headers['Content-Type'].indexOf('multipart/related;'));

    innerSections = parseMultipart(sections[0]);
    // 2 inner sections: a document body section plus 1 attachment data section
    TEquals(2, innerSections.length);
    TEquals('application/json', innerSections[0].headers['content-type']);

    doc = JSON.parse(innerSections[0].body);

    TEquals(true, doc._attachments['lorem.txt'].follows);
    TEquals("gzip", doc._attachments['lorem.txt'].encoding);
    TEquals("undefined", typeof doc._attachments['data.bin'].follows);
    TEquals(true, doc._attachments['data.bin'].stub);
    T(innerSections[1].body !== lorem);
  }

  run_on_modified_server(server_config, testMultipartAttCompression);

  // cleanup
  db.deleteDb();
};
<|endoftext|>