<commit_before>/*
    Post commands to the node-aws-deploy server
    Finds all running instances and issues the command.
    Currently the node-aws-deploy server supports the following commands '/pull', '/restart' and '/rebuild'
    commands. Specify the the type, route, and arguments.

    This code finds all the servers that have user data that matches the
    type passed in and issues the command to them.

    Additionally the /pull command allows you to specify additional arguments that are set on the command line.
    These are stored in the .app-config.json and are applied to the instances every time they restart. This is useful
    for passing up version info, app-cache dates, etc...

    // example shell script for pulling:

         cd your_application_directory/node-aws-deploy
         echo Enter type:
         read type
         echo Enter the version:
         read version
         dateTime=$(date +"%m-%d-%y%_T")
         route="/pull"
         args="{\"version\":\"$version\",\"appCacheDate\":\"$dateTime\"}"
         params="{\"type\":\"$type\",\"listensTo\":\"master\",\"secure\":true}"
         echo -e "\nSignaling Servers to Pull. Waiting for server response...\n"
         node postToAllInstances.js $route $params $args

    The args parameter can also be a string instead of a JSON. Example:
        args="--version=$version --appCacheDate=$dateTime"

    This is handy if you just want to apply the pull arguments in addition to the regular command line arguments. If this
    second method is used (args are a string and not a JSON) they will be applied as command line parameters automatically
    when the app starts up. If they are a JSON then it is up to the developer to do something with them in his preload.json
    script.
*/

// this function exits but allows time for things to get flushed.
function exit (code){
    code = code || 0;
    setTimeout (function (){
        process.exit (code);
    }, 1000);
}

var local_path =  (require.resolve ("./cloud.js")).replace ("cloud.js", "");
var config = require ("./config").data;
var https = require ("https");
var http = require ("http");
var cloud = require ("./cloud.js");
var async = require ("async");

// pass in the route, defaults to '/pull'. Can be '/pull', '/restart' or '/rebuild'
var route = process.argv[2];
if (!route){
    console.log ("No route is specified");
    exit (1);
}

// Pass in the instance type params (JSON)
var instance_data = process.argv[3];
if (instance_data){
    try {instance_data = JSON.parse (instance_data);}
    catch (e){console.log ("Error parsing instance type JSON: %j", e);}
}
if (!instance_data){
    console.log ("No instance type is specified");
    exit (1);
}

// optional command line params we want to set. Should be in the form of a JSON string. Useful for app-cache time stamps
// and versions, etc...
var args = process.argv[4];
if (args){
    try {args = JSON.parse (args);}
    catch (e){
        console.log ("Params are not a JSON. Assuming a a string");
        args = process.argv[4]
    }
}
args = args || {};

// post a command out
function post (url, body, port, secure, path, cb){
    var qs = require('querystring');
    body = qs.stringify (body);
    var options = {
        host: url,
        port: port,
        path: path,
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': body.length
        },
        rejectUnauthorized:false // add this so if someone uses a local certificate on the machine the post will still go through
    };

    var server = (secure) ? https : http;
    var req = server.request(options, function(res) {
        var result = "";
        res.setEncoding('utf8');
        res.on('data', function (chunk) {
            result += chunk;
        });
        res.on('end', function (){
            cb (null, result);
        });
    });
    req.on('error', function(e) {
        console.log('problem with request: ' + e.message);
        cb (e.message);
    });

    // write data to request body
    req.write(body);
    req.end();
    //console.log ("posted to " + url + " waiting for response....");
}

// go get all the instances and post to them
cloud.init (function (){
    var body = {};
    var key = config.pullField || "ref";
    var branch = instance_data.listensTo || config.pullBranch;
    var secure = (config.appURL.search ("https://") !== -1);
    var path = route + "?";
    if (config.pullSecret) {path += "secret=" + config.pullSecret + "&";}
    path += "master=false";
    if (instance_data && (instance_data.secure !== undefined || instance_data.secure !== null)){
        secure = instance_data.secure;
    }

    // grab any params passed as a JSON and set them in the body
    if (typeof args === "object"){
        body.args = JSON.stringify(args);
    }
    else{
        body.args = args;
    }
    body[key] = branch;

    if (instance_data){
        cloud.setInstanceData (instance_data);
        console.log ("Finding Instances");
        cloud.getInstances (function (err, instances){
            if (instances && instances.length){
                console.log ("Found " + instances.length + " instances, posting pull request.");
                async.map (instances, function (instance, cb){
                    if (instance.dns){
                        console.log ("Posting to: " + instance.dns + " waiting for result...");
                        post (instance.dns, body, config.pullPort, secure, path, function (err, result){
                            console.log ("\nPosted " + route + " to:" + instance.dns);
                            console.log ("\tport:" + config.pullPort);
                            console.log ("\tsecure:" + secure);
                            console.log ("\tbody:%j", body);
                            if (err){console.log ("\n\tError:" + err);}
                            else{console.log ("\n\tResult:" + result);}
                            cb ();
                        });
                    }
                    else {cb ();}
                }, function () {exit (0);});
            }
            else{
                console.log ("No instances found");
                exit (0);
            }
        });
    }
    else{
        console.log ("Error posting to instances, instance data json string no passed in or corrupt");
        exit (1);
    }
});
<commit_msg>fixed a spelling error<commit_after>/*
    Post commands to the node-aws-deploy server
    Finds all running instances and issues the command.
    Currently the node-aws-deploy server supports the following commands '/pull', '/restart' and '/rebuild'
    commands. Specify the the type, route, and arguments.

    This code finds all the servers that have user data that matches the
    type passed in and issues the command to them.

    Additionally the /pull command allows you to specify additional arguments that are set on the command line.
    These are stored in the .app-config.json and are applied to the instances every time they restart. This is useful
    for passing up version info, app-cache dates, etc...

    // example shell script for pulling:

         cd your_application_directory/node-aws-deploy
         echo Enter type:
         read type
         echo Enter the version:
         read version
         dateTime=$(date +"%m-%d-%y%_T")
         route="/pull"
         args="{\"version\":\"$version\",\"appCacheDate\":\"$dateTime\"}"
         params="{\"type\":\"$type\",\"listensTo\":\"master\",\"secure\":true}"
         echo -e "\nSignaling Servers to Pull. Waiting for server response...\n"
         node postToAllInstances.js $route $params $args

    The args parameter can also be a string instead of a JSON. Example:
        args="--version=$version --appCacheDate=$dateTime"

    This is handy if you just want to apply the pull arguments in addition to the regular command line arguments. If this
    second method is used (args are a string and not a JSON) they will be applied as command line parameters automatically
    when the app starts up. If they are a JSON then it is up to the developer to do something with them in his preload.json
    script.
*/

// this function exits but allows time for things to get flushed.
function exit (code){
    code = code || 0;
    setTimeout (function (){
        process.exit (code);
    }, 1000);
}

var local_path =  (require.resolve ("./cloud.js")).replace ("cloud.js", "");
var config = require ("./config").data;
var https = require ("https");
var http = require ("http");
var cloud = require ("./cloud.js");
var async = require ("async");

// pass in the route, defaults to '/pull'. Can be '/pull', '/restart' or '/rebuild'
var route = process.argv[2];
if (!route){
    console.log ("No route is specified");
    exit (1);
}

// Pass in the instance type params (JSON)
var instance_data = process.argv[3];
if (instance_data){
    try {instance_data = JSON.parse (instance_data);}
    catch (e){console.log ("Error parsing instance type JSON: %j", e);}
}
if (!instance_data){
    console.log ("No instance type is specified");
    exit (1);
}

// optional command line params we want to set. Should be in the form of a JSON string. Useful for app-cache time stamps
// and versions, etc...
var args = process.argv[4];
if (args){
    try {args = JSON.parse (args);}
    catch (e){
        console.log ("Params are not a JSON. Assuming a string");
        args = process.argv[4]
    }
}
args = args || {};

// post a command out
function post (url, body, port, secure, path, cb){
    var qs = require('querystring');
    body = qs.stringify (body);
    var options = {
        host: url,
        port: port,
        path: path,
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': body.length
        },
        rejectUnauthorized:false // add this so if someone uses a local certificate on the machine the post will still go through
    };

    var server = (secure) ? https : http;
    var req = server.request(options, function(res) {
        var result = "";
        res.setEncoding('utf8');
        res.on('data', function (chunk) {
            result += chunk;
        });
        res.on('end', function (){
            cb (null, result);
        });
    });
    req.on('error', function(e) {
        console.log('problem with request: ' + e.message);
        cb (e.message);
    });

    // write data to request body
    req.write(body);
    req.end();
    //console.log ("posted to " + url + " waiting for response....");
}

// go get all the instances and post to them
cloud.init (function (){
    var body = {};
    var key = config.pullField || "ref";
    var branch = instance_data.listensTo || config.pullBranch;
    var secure = (config.appURL.search ("https://") !== -1);
    var path = route + "?";
    if (config.pullSecret) {path += "secret=" + config.pullSecret + "&";}
    path += "master=false";
    if (instance_data && (instance_data.secure !== undefined || instance_data.secure !== null)){
        secure = instance_data.secure;
    }

    // grab any params passed as a JSON and set them in the body
    if (typeof args === "object"){
        body.args = JSON.stringify(args);
    }
    else{
        body.args = args;
    }
    body[key] = branch;

    if (instance_data){
        cloud.setInstanceData (instance_data);
        console.log ("Finding Instances");
        cloud.getInstances (function (err, instances){
            if (instances && instances.length){
                console.log ("Found " + instances.length + " instances, posting pull request.");
                async.map (instances, function (instance, cb){
                    if (instance.dns){
                        console.log ("Posting to: " + instance.dns + " waiting for result...");
                        post (instance.dns, body, config.pullPort, secure, path, function (err, result){
                            console.log ("\nPosted " + route + " to:" + instance.dns);
                            console.log ("\tport:" + config.pullPort);
                            console.log ("\tsecure:" + secure);
                            console.log ("\tbody:%j", body);
                            if (err){console.log ("\n\tError:" + err);}
                            else{console.log ("\n\tResult:" + result);}
                            cb ();
                        });
                    }
                    else {cb ();}
                }, function () {exit (0);});
            }
            else{
                console.log ("No instances found");
                exit (0);
            }
        });
    }
    else{
        console.log ("Error posting to instances, instance data json string no passed in or corrupt");
        exit (1);
    }
});
<|endoftext|>


<commit_before>'use strict';

function download(tabId, downloadRequest){
    new Download(downloadRequest, tabId).action();
}

function Download(downloadRequest, tabId){
    this.downloadRequest    = downloadRequest;
    this.tabId              = tabId;
    this.duplicateCheckTry  = 0;
}

Download.prototype = {
    action: async function(){
        let extractedFilename;

        if (!this.downloadRequest.template.filename) {
            this.downloadRequest.template.filename = '::both_filenames::'; // default behavior
        }

        if (this.filenameExtractionRequired()) {
            const extractedFromSrc = filenameTools.extractFilename(this.downloadRequest.src);
            if (extractedFromSrc.filename) {
                extractedFilename = extractedFromSrc.filename;
            } else {
                const extractedFromHeaders = await this.getFilenameFromHeaders();
                if (extractedFromHeaders.filename) {
                    extractedFilename = extractedFromHeaders.filename;
                } else {
                    extractedFilename = this.getFallbackFilename(extractedFromHeaders.extension)
                }
            }
        }

        this.download(
            filenameTools.replacePathPlaceholders(this.downloadRequest),
            filenameTools.replaceFilenamePlaceholders(this.downloadRequest, extractedFilename)
        );
    },

    getFilenameFromHeaders: async function(){
        const headers = await this.getHeaders();
        let tmpFilename;

        if (!headers) {
            return {};
        }

        tmpFilename = headers.contentDisposition && headers.contentDisposition.match(/^.+filename\*?=(.{0,20}')?([^;]*);?$/i);

        return tmpFilename ?
            {filename: decodeURI(tmpFilename[2]).replace(/"/g, '')} :
            {extension: headers.contentType && headers.contentType.match(/\w+\/(\w+)/)[1].replace('jpeg', 'jpg')};
    },

    getHeaders: async function(){
        let response = await fetch(this.downloadRequest.src, {method: 'HEAD'});

        if ([404, 405, 501].includes(response.status)) { // HEAD request method is not supported / allowed / implemented by server
            response = await fetch(this.downloadRequest.src, {method: 'GET'});
        }

        return response.ok ? {
            contentType: response.headers.get('content-type'),
            contentDisposition: response.headers.get('content-disposition'),
        } : null;
    },

    getFallbackFilename: function(possibleExtension){
        const filenameTry = (this.downloadRequest.pageInfo.title || '').match(/[^\s]+\.(jpg|jpeg|png|gif|bmp|webm|mp4|ogg|mp3)/i);
        return filenameTry ? filenameTry[0] : `${filenameTools.getTimestamp()}.${possibleExtension || 'jpg'}`;
    },

    download: function(path, filename){
        const finalPath = filenameTools.preparePath(path),
            finalFilename = filenameTools.prepareFilename(filename);

        chrome.downloads.download({
                url             : this.downloadRequest.src,
                filename        : finalPath + finalFilename,
                saveAs          : this.downloadRequest.showSaveDialog,
                conflictAction  : 'uniquify',
            },
            downloadId => {
                if (chrome.extension.lastError) {return;}
                this.checkForDuplicate(finalFilename, downloadId);
            }
        );
    },

    /*
    * If resulted filename is not the same as the filename passed to downloads.download function,
    * then it was modified by 'uniquify' conflict action of WebExt API,
    * and user should be warned that he saved already existing file.
    */
    checkForDuplicate: function(originalFilename, downloadId){
        chrome.downloads.search({
            id: downloadId
        }, downloadItems => {
            if (!downloadItems[0]) {
            	this.checkForDuplicateRetry(originalFilename, downloadId);
            	return;
            }
            if (downloadItems[0].filename && !downloadItems[0].filename.endsWith(originalFilename)) {
                chrome.tabs.sendMessage(this.tabId, 'duplicate_warning');
            }
        });
    },

    /*
    * Hack for once broken(?) downloads.search, now it can't find download if called instantly from downloads.download callback
    */
    checkForDuplicateRetry: function(originalFilename, downloadId){
        if (this.duplicateCheckTry > 5) {return;}
        this.duplicateCheckTry++;
        setTimeout(() => this.checkForDuplicate(originalFilename, downloadId), 50);
    },

    filenameExtractionRequired: function(){
        const filenameTemplate = this.downloadRequest.template.filename;
        return (
            filenameTemplate.includes('::filename::') ||
            (filenameTemplate.includes('::both_filenames::') && !this.downloadRequest.useOriginalName)
        );
    },
};

const filenameTools = {
    replacePathPlaceholders: function(dlRequest){
        // TODO: don't use *filename* replacements
        return this.replacePlaceholders(dlRequest.template.path, dlRequest)
    },

    replaceFilenamePlaceholders: function(dlRequest, extractedFilename){
        return this.replacePlaceholders(dlRequest.template.filename, dlRequest, extractedFilename)
    },

    replacePlaceholders: function(template, dlRequest, extractedFilename = null){
        let string = template;
        const placeholders = {
            '::domain::'            : () => this.trimForbiddenWinChars(dlRequest.pageInfo.domain),
            '::title::'             : () => this.trimForbiddenWinChars(dlRequest.pageInfo.title),
            '::thread_number::'     : () => this.trimForbiddenWinChars(dlRequest.pageInfo.threadNum),
            '::board_name::'        : () => this.trimForbiddenWinChars(dlRequest.pageInfo.boardName),
            '::datetime::'          : this.getDatetimeString,
            '::date::'              : this.getDateString,
            '::time::'              : this.getTimestamp,
            '::filename::'          : () => extractedFilename,
            '::original_filename::' : () => dlRequest.originalName,
            '::both_filenames::'    : () => dlRequest.useOriginalName ? dlRequest.originalName : extractedFilename,
        };

        string.includes(':') && Object.entries(placeholders).forEach(([placeholder, replacement]) => {
            string = string.replace(placeholder, replacement());
        });

        return string;
    },

    /*
    * Decodes url, cuts possible GET parameters, extracts filename from the url.
    * Usually filename is located at the end, after last "/" symbol, but sometimes
    * it might be somewhere in the middle between "/" symbols.
    * That's why it's important to extract filename manually by looking for the last sub-string
    * with pre-known extension located between "/" symbols.
    * WebExt API is incapable of extracting such filenames.
    * Cheers pineapple.
    */
    extractFilename: function(originalUrl){
        const url = decodeURI(originalUrl).replace(/^.*https?:\/\/([^/]+)\/+/, '').split(/[?#]/)[0].replace(/:\w+$/, '').replace(/\/{2,}/, '/'),
            filenameTry = url.match(/^([^/]+\/)*([^/]+\.(jpg|jpeg|png|gif|bmp|webm|mp4|ogg|mp3))([\/][^.]+)?$/i);

        return filenameTry ?
            {filename: filenameTry[2]} :
            {basename: url.replace(/\/480$/, '').split('/').pop()}; // '/480' removal is a hack for tumblr videos
    },

    trimForbiddenWinChars: function(string){
        return (string || '').replace(/[/\\:*?"<>|\x09\u0080-\u008f]/g, '').replace(/^\.+|\.+$/g, '');
    },

    /*
    * If the path is not empty then trims leading/trailing slashes/backslashes
    * and adds a single slash to the end for concating with filename.
    * Doesn't matter which slashes are used in save path, WebExt API recognizes both.
    */
    preparePath: function(path){
        return path && (path.replace(/^\\+|\\+$|^\/+|\/+$/, '') + '/');
    },

    prepareFilename: function(filename){
        let decodedFilename;
        try {
            decodedFilename = decodeURI(filename);
        } catch (e) { // malformed URI sequence
            decodedFilename = filename;
        }

        return this.trimForbiddenWinChars(decodedFilename);
    },

    getDatetimeString: function(){
        return new Date().toISOString().replace('T', '_').replace(/\..+/g, '').replace(/[^\d_]/g, '');
    },

    getDateString: function(){
        return filenameTools.getDatetimeString().split('_')[0];
    },

    getTimestamp: function(){
        return Date.now();
    },
};
<commit_msg>check that there's always extension in filename from template<commit_after>'use strict';

const supportedFormats = '(jpg|jpeg|png|gif|bmp|webm|mp4|ogg|mp3)'; // TODO: use it for all regexps
const regexps = {
    extensionCheck  : new RegExp(`\\.${supportedFormats}$`),
    filenameExtract : null, // TODO: regexp for extractFilename
    filenameInTitle : null, // TODO: regexp for getFallbackFilename
};

function download(tabId, downloadRequest){
    new Download(downloadRequest, tabId).action();
}

function Download(downloadRequest, tabId){
    this.downloadRequest    = downloadRequest;
    this.tabId              = tabId;
    this.duplicateCheckTry  = 0;
}

Download.prototype = {
    action: async function(){
        let extractedFilename;

        if (!this.downloadRequest.template.filename) {
            this.downloadRequest.template.filename = '::both_filenames::'; // default behavior
        }

        if (this.filenameExtractionRequired()) {
            const extractedFromSrc = filenameTools.extractFilename(this.downloadRequest.src);
            if (extractedFromSrc.filename) {
                extractedFilename = extractedFromSrc.filename;
            } else {
                const extractedFromHeaders = await this.getFilenameFromHeaders();
                extractedFilename = extractedFromHeaders.filename || this.getFallbackFilename(extractedFromHeaders.extension);
            }
        }

        this.download(
            filenameTools.replacePathPlaceholders(this.downloadRequest),
            filenameTools.replaceFilenamePlaceholders(this.downloadRequest, extractedFilename)
        );
    },

    getFilenameFromHeaders: async function(){
        const headers = await this.getHeaders();
        let tmpFilename;

        if (!headers) {
            return {};
        }

        tmpFilename = headers.contentDisposition && headers.contentDisposition.match(/^.+filename\*?=(.{0,20}')?([^;]*);?$/i);

        return tmpFilename ?
            {filename: decodeURI(tmpFilename[2]).replace(/"/g, '')} :
            {extension: headers.contentType && headers.contentType.match(/\w+\/(\w+)/)[1].replace('jpeg', 'jpg')};
    },

    getHeaders: async function(){
        let response = await fetch(this.downloadRequest.src, {method: 'HEAD'});

        if ([404, 405, 501].includes(response.status)) { // HEAD request method is not supported / allowed / implemented by server
            response = await fetch(this.downloadRequest.src, {method: 'GET'});
        }

        return response.ok ? {
            contentType: response.headers.get('content-type'),
            contentDisposition: response.headers.get('content-disposition'),
        } : null;
    },

    getFallbackFilename: function(possibleExtension){
        const filenameTry = (this.downloadRequest.pageInfo.title || '').match(/[^\s]+\.(jpg|jpeg|png|gif|bmp|webm|mp4|ogg|mp3)/i);
        return filenameTry ? filenameTry[0] : `${filenameTools.getTimestamp()}.${possibleExtension || 'jpg'}`;
    },

    download: function(path, filename){
        const finalPath = filenameTools.preparePath(path),
            finalFilename = filenameTools.prepareFilename(filename);

        chrome.downloads.download({
                url             : this.downloadRequest.src,
                filename        : finalPath + finalFilename,
                saveAs          : this.downloadRequest.showSaveDialog,
                conflictAction  : 'uniquify',
            },
            downloadId => {
                if (chrome.extension.lastError) {return;}
                this.checkForDuplicate(finalFilename, downloadId);
            }
        );
    },

    /*
    * If resulted filename is not the same as the filename passed to downloads.download function,
    * then it was modified by 'uniquify' conflict action of WebExt API,
    * and user should be warned that he saved already existing file.
    */
    checkForDuplicate: function(originalFilename, downloadId){
        chrome.downloads.search({
            id: downloadId
        }, downloadItems => {
            if (!downloadItems[0]) {
            	this.checkForDuplicateRetry(originalFilename, downloadId);
            	return;
            }
            if (downloadItems[0].filename && !downloadItems[0].filename.endsWith(originalFilename)) {
                chrome.tabs.sendMessage(this.tabId, 'duplicate_warning');
            }
        });
    },

    /*
    * Hack for once broken(?) downloads.search, now it can't find download if called instantly from downloads.download callback
    */
    checkForDuplicateRetry: function(originalFilename, downloadId){
        if (this.duplicateCheckTry > 5) {return;}
        this.duplicateCheckTry++;
        setTimeout(() => this.checkForDuplicate(originalFilename, downloadId), 50);
    },

    filenameExtractionRequired: function(){
        const filenameTemplate = this.downloadRequest.template.filename;

        if (filenameTemplate.includes('::filename::')) {
        	return true;
        }
        return !(
            filenameTemplate.endsWith('::original_filename::') ||
            (filenameTemplate.endsWith('::both_filenames::') && this.downloadRequest.useOriginalName)
        );
    },
};

const filenameTools = {
    replacePathPlaceholders: function(dlRequest){
        // TODO: don't use *filename* replacements
        return this.replacePlaceholders(dlRequest.template.path, dlRequest);
    },

    replaceFilenamePlaceholders: function(dlRequest, extractedFilename){
        const filename = this.replacePlaceholders(dlRequest.template.filename, dlRequest, extractedFilename);
        return this.addMissingExtension(filename, extractedFilename);
    },

    replacePlaceholders: function(template, dlRequest, extractedFilename = null){
        let string = template;
        const placeholders = {
            '::domain::'            : () => this.trimForbiddenWinChars(dlRequest.pageInfo.domain),
            '::title::'             : () => this.trimForbiddenWinChars(dlRequest.pageInfo.title),
            '::thread_number::'     : () => this.trimForbiddenWinChars(dlRequest.pageInfo.threadNum),
            '::board_name::'        : () => this.trimForbiddenWinChars(dlRequest.pageInfo.boardName),
            '::datetime::'          : this.getDatetimeString,
            '::date::'              : this.getDateString,
            '::time::'              : this.getTimestamp,
            '::filename::'          : () => extractedFilename,
            '::original_filename::' : () => dlRequest.originalName,
            '::both_filenames::'    : () => dlRequest.useOriginalName ? dlRequest.originalName : extractedFilename,
        };

        string.includes(':') && Object.entries(placeholders).forEach(([placeholder, replacement]) => {
            string = string.replace(placeholder, replacement());
        });

        return string;
    },

    /*
    * If there's no extension in the resulting filename, get it from extracted filename
    */
    addMissingExtension: function(resultingFilename, extractedFilename){
        return resultingFilename.match(regexps.extensionCheck) ?
            resultingFilename :
            (resultingFilename + extractedFilename.match(regexps.extensionCheck)[0]);
    },

    /*
    * Decodes url, cuts possible GET parameters, extracts filename from the url.
    * Usually filename is located at the end, after last "/" symbol, but sometimes
    * it might be somewhere in the middle between "/" symbols.
    * That's why it's important to extract filename manually by looking for the last sub-string
    * with pre-known extension located between "/" symbols.
    * WebExt API is incapable of extracting such filenames.
    * Cheers pineapple.
    */
    extractFilename: function(originalUrl){
        const url = decodeURI(originalUrl).replace(/^.*https?:\/\/([^/]+)\/+/, '').split(/[?#]/)[0].replace(/:\w+$/, '').replace(/\/{2,}/, '/'),
            filenameTry = url.match(/^([^/]+\/)*([^/]+\.(jpg|jpeg|png|gif|bmp|webm|mp4|ogg|mp3))([\/][^.]+)?$/i);

        return filenameTry ?
            {filename: filenameTry[2]} :
            {basename: url.replace(/\/480$/, '').split('/').pop()}; // '/480' removal is a hack for tumblr videos
    },

    trimForbiddenWinChars: function(string){
        return (string || '').replace(/[/\\:*?"<>|\x09\u0080-\u008f]/g, '').replace(/^\.+|\.+$/g, '');
    },

    /*
    * If the path is not empty then trims leading/trailing slashes/backslashes
    * and adds a single slash to the end for concating with filename.
    * Doesn't matter which slashes are used in save path, WebExt API recognizes both.
    */
    preparePath: function(path){
        return path && (path.replace(/^\\+|\\+$|^\/+|\/+$/, '') + '/');
    },

    prepareFilename: function(filename){
        let decodedFilename;
        try {
            decodedFilename = decodeURI(filename);
        } catch (e) { // malformed URI sequence
            decodedFilename = filename;
        }

        return this.trimForbiddenWinChars(decodedFilename);
    },

    getDatetimeString: function(){
        return new Date().toISOString().replace('T', '_').replace(/\..+/g, '').replace(/[^\d_]/g, '');
    },

    getDateString: function(){
        return filenameTools.getDatetimeString().split('_')[0];
    },

    getTimestamp: function(){
        return Date.now();
    },
};
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom/server';
import serialize from 'serialize-javascript';
import Helmet from 'react-helmet';

/**
 * Wrapper component containing HTML metadata and boilerplate tags.
 * Used in server-side code only to wrap the string output of the
 * rendered route component.
 *
 * The only thing this component doesn't (and can't) include is the
 * HTML doctype declaration, which is added to the rendered output
 * by the server.js file.
 */
export default class Html extends Component {
  static propTypes = {
    assets: PropTypes.object,
    component: PropTypes.node,
    store: PropTypes.object
  };

  render() {
    const {assets, component, store} = this.props;
    const content = component ? ReactDOM.renderToString(component) : '';
    const head = Helmet.rewind();

    return (

          {head.base.toComponent()}
          {head.title.toComponent()}
          {head.meta.toComponent()}
          {head.link.toComponent()}
          {head.script.toComponent()}

          <meta name="bankrate-site-verification" content="75387a3abc9bf8cf6cc8" />
          <meta name="google-site-verification" content="1DZOZwCcjvk35l-3ZclhWBnFEXzDLECREa5Buv2E37U" />
          <link rel="shortcut icon" href="/favicon.ico" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          {/* styles (will be present only in production with webpack extract text plugin) */}
          {Object.keys(assets.styles).map((style, key) =>
            <link href={assets.styles[style]} key={key} media="screen, projection"
                  rel="stylesheet" type="text/css" charSet="UTF-8"/>
          )}

          {/* (will be present only in development mode) */}
          {/* outputs a


<commit_before>
// Test URL: http://localhost:8000/invaders_enemies.html?dot_xs=1,2,3&dot_ys=1,2,6
// Use this or there won't be enemies

var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create, update: update, render: render });

function preload() {

    game.load.image('bullet', 'assets/bullet.png');
    game.load.image('enemyBullet', 'assets/enemy-bullet.png');
    //game.load.spritesheet('invader', 'assets/invader32x32x4.png', 32, 32);
    game.load.image('invader', 'img/bluedot.png', 32, 32);
    game.load.image('ship', 'assets/player.png');
    game.load.spritesheet('kaboom', 'assets/explode.png', 128, 128);
    game.load.image('starfield', 'assets/starfield.png');

    highscore = localStorage.getItem("highscore");
    if (highscore == null) {
	    highscore = 0;
	    localStorate.setItem("highscore", highscore);
	}
}

var player;
var aliens;
var bullets;
var bulletTime = 0;
var cursors;
var fireButton;
var explosions;
var starfield;
var score = 0;
var scoreString = '';
var scoreText;
var highscore = 0;
var highscoreString = '';
var highscoreText;
var lives;
var enemyBullet;
var firingTimer = 0;
var stateText;
var livingEnemies = [];
var tapTargetX;
var tapTargetY;

function create() {

    game.physics.startSystem(Phaser.Physics.ARCADE);

    //  The scrolling starfield background
    starfield = game.add.tileSprite(0, 0, 800, 600, 'starfield');

    //  Our bullet group
    bullets = game.add.group();
    bullets.enableBody = true;
    bullets.physicsBodyType = Phaser.Physics.ARCADE;
    bullets.createMultiple(30, 'bullet');
    bullets.setAll('anchor.x', 0.5);
    bullets.setAll('anchor.y', 1);
    bullets.setAll('outOfBoundsKill', true);
    bullets.setAll('checkWorldBounds', true);

    // The enemy's bullets
    enemyBullets = game.add.group();
    enemyBullets.enableBody = true;
    enemyBullets.physicsBodyType = Phaser.Physics.ARCADE;
    enemyBullets.createMultiple(30, 'enemyBullet');
    enemyBullets.setAll('anchor.x', 0.5);
    enemyBullets.setAll('anchor.y', 1);
    enemyBullets.setAll('outOfBoundsKill', true);
    enemyBullets.setAll('checkWorldBounds', true);

    //  The hero!
    player = game.add.sprite(400, 500, 'ship');
    player.anchor.setTo(0.5, 0.5);
    game.physics.enable(player, Phaser.Physics.ARCADE);

    //  The baddies!
    aliens = game.add.group();
    aliens.enableBody = true;
    aliens.physicsBodyType = Phaser.Physics.ARCADE;
    createAliens();

	// Highscore
	highscoreString = 'Highscore : ';
    highscoreText = game.add.text(10, 10, highscoreString + highscore, { font: '34px Arial', fill: '#fff' });

    //  The score
    scoreString = 'Score : ';
    scoreText = game.add.text(10, 44, scoreString + score, { font: '34px Arial', fill: '#fff' });

    //  Lives
    lives = game.add.group();
    game.add.text(game.world.width - 100, 10, 'Lives : ', { font: '34px Arial', fill: '#fff' });

    //  Text
    stateText = game.add.text(game.world.centerX,game.world.centerY,' ', { font: '84px Arial', fill: '#fff' });
    stateText.anchor.setTo(0.5, 0.5);
    stateText.visible = false;

    for (var i = 0; i < 3; i++)
    {
        var ship = lives.create(game.world.width - 100 + (30 * i), 60, 'ship');
        ship.anchor.setTo(0.5, 0.5);
        ship.angle = 90;
        ship.alpha = 0.4;
    }

    //  An explosion pool
    explosions = game.add.group();
    explosions.createMultiple(30, 'kaboom');
    explosions.forEach(setupInvader, this);

    //  And some controls to play the game with
    cursors = game.input.keyboard.createCursorKeys();
    fireButton = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);

}

function createAliens() {
    var coordinates = readCoordinatesFromUrl();

    // Use default space invaders pattern if we can't pull from URL
    if (!coordinates || coordinates.length <= 0) {
        for (var y = 0; y < 4; y++) {
            for (var x = 0; x < 10; x++) {
                coordinates.push([x, y]);
            }
        }
    }

    coordinates.map(function(coordinate) {
        var x = coordinate[0];
        var y = coordinate[1];

        var alien = aliens.create(x * 48, y * 50, 'invader');
        alien.anchor.setTo(0.5, 0.5);
        alien.animations.add('fly', [ 0, 1, 2, 3 ], 20, true);
        alien.play('fly');
        alien.body.moves = false;
    });

    aliens.x = 100;
    aliens.y = 50;

    //  All this does is basically start the invaders moving. Notice we're moving the Group they belong to, rather than the invaders directly.
    var tween = game.add.tween(aliens).to( { x: 200 }, 500, Phaser.Easing.Linear.None, true, 0, 1, true).loop();

    //  When the tween loops it calls descendAliens
    tween.onLoop.add(descendAliens, this);
}

// Loads dots from query params.
// Example:
// http://url?dot_xs=1,2,3&dot_ys=4,5,6
// represents (1, 4), (2, 5), and (3, 6)
function readCoordinatesFromUrl() {
    // http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
    var querystring = (function(a) {
        if (a === "") return {};
        var b = {};
        for (var i = 0; i < a.length; ++i) {
            var p=a[i].split('=', 2);
            if (p.length == 1)
                b[p[0]] = "";
            else
                b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
        }
        return b;
    })(window.location.search.substr(1).split('&'));

    if (!(querystring['dot_xs'] && querystring['dot_ys'])) {
        console.log("No dots present in URL parameters. Specify them using dot_xs and dot_ys");
        return [];
    }

    xs = querystring['dot_xs'].split(",");
    ys = querystring['dot_ys'].split(",");
    if (!(xs.length === ys.length)) {
        console.log("Invalid dots: number of x coords doesn't match number of y coords.");
        return [];
    }

    var allCoords = [];
    for (var i = 0; i < xs.length;i++) {
        allCoords.push([ xs[i], ys[i] ]);
    }

    var urlCoordsString = allCoords.map(function (coordinate) {
        return "(" + coordinate + ")";
    });
    console.log("All coords from URL: " + urlCoordsString);

    return allCoords;
}

function setupInvader(invader) {
    invader.anchor.x = 0.5;
    invader.anchor.y = 0.5;
    invader.animations.add('kaboom');
}

function descendAliens() {
    aliens.y += 20;
}

function update() {

    //  Scroll the background
    starfield.tilePosition.y += 2;

    if (player.alive) {
        // If mouse is down, register tap target and fire bullet
        if (game.input.mousePointer.isDown) {
            tapTargetX = game.input.x;
            tapTargetY = game.input.y;
            fireBullet();
        }

        // If the player has reached the tap target, stop, otherwise move there
        if (Phaser.Rectangle.contains(player.body, tapTargetX, tapTargetY)) {
            player.body.velocity.setTo(0, 0);
        } else {
            game.physics.arcade.moveToXY(player, tapTargetX, tapTargetY, 400);
        }

        if (game.time.now > firingTimer) {
            enemyFires();
        }

        //  Run collision
        game.physics.arcade.overlap(bullets, aliens, collisionHandler, null, this);
        game.physics.arcade.overlap(enemyBullets, player, enemyHitsPlayer, null, this);
    }

}

function render() {

    // for (var i = 0; i < aliens.length; i++)
    // {
    //     game.debug.body(aliens.children[i]);
    // }

}

function increaseScore (value) {
    score += value;
	scoreText.text = scoreString + score;
	if (score > highscore) {
	    highscore = score;
		highscoreText.text = highscoreString + highscore;
	}
}

function resetScore () {
    score = 0;
	scoreText.text = scoreString + score;
	localStorage.setItem("highscore", highscore );
}

function collisionHandler (bullet, alien) {

    //  When a bullet hits an alien we kill them both
    bullet.kill();
    alien.kill();

    //  Increase the score
    increaseScore(20);

    //  And create an explosion :)
    var explosion = explosions.getFirstExists(false);
    explosion.reset(alien.body.x, alien.body.y);
    explosion.play('kaboom', 30, false, true);

    if (aliens.countLiving() == 0)
    {
        increaseScore(1000);

        enemyBullets.callAll('kill',this);
        stateText.text = " You Won, \n Click to restart";
        stateText.visible = true;

        //the "click to restart" handler
        game.input.onTap.addOnce(restart,this);
    }

}

function enemyHitsPlayer(player,bullet) {

    bullet.kill();

    live = lives.getFirstAlive();

    if (live) {
        live.kill();
    }

    //  And create an explosion :)
    var explosion = explosions.getFirstExists(false);
    explosion.reset(player.body.x, player.body.y);
    explosion.play('kaboom', 30, false, true);

    // When the player dies
    if (lives.countLiving() < 1) {
        player.kill();
        enemyBullets.callAll('kill');

        stateText.text=" GAME OVER \n Click to restart";
        stateText.visible = true;

        //the "click to restart" handler
        game.input.onTap.addOnce(restart,this);
    }

}

function enemyFires () {

    //  Grab the first bullet we can from the pool
    enemyBullet = enemyBullets.getFirstExists(false);

    livingEnemies.length=0;

    aliens.forEachAlive(function(alien){
        // put every living enemy in an array
        livingEnemies.push(alien);
    });

    if (enemyBullet && livingEnemies.length > 0) {

        var random = game.rnd.integerInRange(0,livingEnemies.length-1);

        // randomly select one of them
        var shooter = livingEnemies[random];
        // And fire the bullet from this enemy
        enemyBullet.reset(shooter.body.x, shooter.body.y);

        game.physics.arcade.moveToObject(enemyBullet,player,120);
        firingTimer = game.time.now + 2000;
    }

}

function fireBullet() {

    //  To avoid them being allowed to fire too fast we set a time limit
    if (game.time.now > bulletTime) {
        //  Grab the first bullet we can from the pool
        bullet = bullets.getFirstExists(false);

        if (bullet) {
            //  And fire it
            bullet.reset(player.x, player.y + 8);
            bullet.body.velocity.y = -400;
            bulletTime = game.time.now + 200;
        }
    }

}

function restart() {

    //  A new level starts

    //resets the life count
    lives.callAll('revive');
    //  And brings the aliens back from the dead :)
    aliens.removeAll();
    createAliens();

    //revives the player
    player.revive();
    //hides the text
    stateText.visible = false;

	resetScore();

}
<commit_msg>Fixed a herp-a-derp typo.<commit_after>
// Test URL: http://localhost:8000/invaders_enemies.html?dot_xs=1,2,3&dot_ys=1,2,6
// Use this or there won't be enemies

var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create, update: update, render: render });

function preload() {

    game.load.image('bullet', 'assets/bullet.png');
    game.load.image('enemyBullet', 'assets/enemy-bullet.png');
    //game.load.spritesheet('invader', 'assets/invader32x32x4.png', 32, 32);
    game.load.image('invader', 'img/bluedot.png', 32, 32);
    game.load.image('ship', 'assets/player.png');
    game.load.spritesheet('kaboom', 'assets/explode.png', 128, 128);
    game.load.image('starfield', 'assets/starfield.png');

    highscore = localStorage.getItem("highscore");
    if (highscore == null) {
	    highscore = 0;
	    localStorage.setItem("highscore", highscore);
	}
}

var player;
var aliens;
var bullets;
var bulletTime = 0;
var cursors;
var fireButton;
var explosions;
var starfield;
var score = 0;
var scoreString = '';
var scoreText;
var highscore = 0;
var highscoreString = '';
var highscoreText;
var lives;
var enemyBullet;
var firingTimer = 0;
var stateText;
var livingEnemies = [];
var tapTargetX;
var tapTargetY;

function create() {

    game.physics.startSystem(Phaser.Physics.ARCADE);

    //  The scrolling starfield background
    starfield = game.add.tileSprite(0, 0, 800, 600, 'starfield');

    //  Our bullet group
    bullets = game.add.group();
    bullets.enableBody = true;
    bullets.physicsBodyType = Phaser.Physics.ARCADE;
    bullets.createMultiple(30, 'bullet');
    bullets.setAll('anchor.x', 0.5);
    bullets.setAll('anchor.y', 1);
    bullets.setAll('outOfBoundsKill', true);
    bullets.setAll('checkWorldBounds', true);

    // The enemy's bullets
    enemyBullets = game.add.group();
    enemyBullets.enableBody = true;
    enemyBullets.physicsBodyType = Phaser.Physics.ARCADE;
    enemyBullets.createMultiple(30, 'enemyBullet');
    enemyBullets.setAll('anchor.x', 0.5);
    enemyBullets.setAll('anchor.y', 1);
    enemyBullets.setAll('outOfBoundsKill', true);
    enemyBullets.setAll('checkWorldBounds', true);

    //  The hero!
    player = game.add.sprite(400, 500, 'ship');
    player.anchor.setTo(0.5, 0.5);
    game.physics.enable(player, Phaser.Physics.ARCADE);

    //  The baddies!
    aliens = game.add.group();
    aliens.enableBody = true;
    aliens.physicsBodyType = Phaser.Physics.ARCADE;
    createAliens();

	// Highscore
	highscoreString = 'Highscore : ';
    highscoreText = game.add.text(10, 10, highscoreString + highscore, { font: '34px Arial', fill: '#fff' });

    //  The score
    scoreString = 'Score : ';
    scoreText = game.add.text(10, 44, scoreString + score, { font: '34px Arial', fill: '#fff' });

    //  Lives
    lives = game.add.group();
    game.add.text(game.world.width - 100, 10, 'Lives : ', { font: '34px Arial', fill: '#fff' });

    //  Text
    stateText = game.add.text(game.world.centerX,game.world.centerY,' ', { font: '84px Arial', fill: '#fff' });
    stateText.anchor.setTo(0.5, 0.5);
    stateText.visible = false;

    for (var i = 0; i < 3; i++)
    {
        var ship = lives.create(game.world.width - 100 + (30 * i), 60, 'ship');
        ship.anchor.setTo(0.5, 0.5);
        ship.angle = 90;
        ship.alpha = 0.4;
    }

    //  An explosion pool
    explosions = game.add.group();
    explosions.createMultiple(30, 'kaboom');
    explosions.forEach(setupInvader, this);

    //  And some controls to play the game with
    cursors = game.input.keyboard.createCursorKeys();
    fireButton = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);

}

function createAliens() {
    var coordinates = readCoordinatesFromUrl();

    // Use default space invaders pattern if we can't pull from URL
    if (!coordinates || coordinates.length <= 0) {
        for (var y = 0; y < 4; y++) {
            for (var x = 0; x < 10; x++) {
                coordinates.push([x, y]);
            }
        }
    }

    coordinates.map(function(coordinate) {
        var x = coordinate[0];
        var y = coordinate[1];

        var alien = aliens.create(x * 48, y * 50, 'invader');
        alien.anchor.setTo(0.5, 0.5);
        alien.animations.add('fly', [ 0, 1, 2, 3 ], 20, true);
        alien.play('fly');
        alien.body.moves = false;
    });

    aliens.x = 100;
    aliens.y = 50;

    //  All this does is basically start the invaders moving. Notice we're moving the Group they belong to, rather than the invaders directly.
    var tween = game.add.tween(aliens).to( { x: 200 }, 500, Phaser.Easing.Linear.None, true, 0, 1, true).loop();

    //  When the tween loops it calls descendAliens
    tween.onLoop.add(descendAliens, this);
}

// Loads dots from query params.
// Example:
// http://url?dot_xs=1,2,3&dot_ys=4,5,6
// represents (1, 4), (2, 5), and (3, 6)
function readCoordinatesFromUrl() {
    // http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
    var querystring = (function(a) {
        if (a === "") return {};
        var b = {};
        for (var i = 0; i < a.length; ++i) {
            var p=a[i].split('=', 2);
            if (p.length == 1)
                b[p[0]] = "";
            else
                b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
        }
        return b;
    })(window.location.search.substr(1).split('&'));

    if (!(querystring['dot_xs'] && querystring['dot_ys'])) {
        console.log("No dots present in URL parameters. Specify them using dot_xs and dot_ys");
        return [];
    }

    xs = querystring['dot_xs'].split(",");
    ys = querystring['dot_ys'].split(",");
    if (!(xs.length === ys.length)) {
        console.log("Invalid dots: number of x coords doesn't match number of y coords.");
        return [];
    }

    var allCoords = [];
    for (var i = 0; i < xs.length;i++) {
        allCoords.push([ xs[i], ys[i] ]);
    }

    var urlCoordsString = allCoords.map(function (coordinate) {
        return "(" + coordinate + ")";
    });
    console.log("All coords from URL: " + urlCoordsString);

    return allCoords;
}

function setupInvader(invader) {
    invader.anchor.x = 0.5;
    invader.anchor.y = 0.5;
    invader.animations.add('kaboom');
}

function descendAliens() {
    aliens.y += 20;
}

function update() {

    //  Scroll the background
    starfield.tilePosition.y += 2;

    if (player.alive) {
        // If mouse is down, register tap target and fire bullet
        if (game.input.mousePointer.isDown) {
            tapTargetX = game.input.x;
            tapTargetY = game.input.y;
            fireBullet();
        }

        // If the player has reached the tap target, stop, otherwise move there
        if (Phaser.Rectangle.contains(player.body, tapTargetX, tapTargetY)) {
            player.body.velocity.setTo(0, 0);
        } else {
            game.physics.arcade.moveToXY(player, tapTargetX, tapTargetY, 400);
        }

        if (game.time.now > firingTimer) {
            enemyFires();
        }

        //  Run collision
        game.physics.arcade.overlap(bullets, aliens, collisionHandler, null, this);
        game.physics.arcade.overlap(enemyBullets, player, enemyHitsPlayer, null, this);
    }

}

function render() {

    // for (var i = 0; i < aliens.length; i++)
    // {
    //     game.debug.body(aliens.children[i]);
    // }

}

function increaseScore (value) {
    score += value;
	scoreText.text = scoreString + score;
	if (score > highscore) {
	    highscore = score;
		highscoreText.text = highscoreString + highscore;
	}
}

function resetScore () {
    score = 0;
	scoreText.text = scoreString + score;
	localStorage.setItem("highscore", highscore );
}

function collisionHandler (bullet, alien) {

    //  When a bullet hits an alien we kill them both
    bullet.kill();
    alien.kill();

    //  Increase the score
    increaseScore(20);

    //  And create an explosion :)
    var explosion = explosions.getFirstExists(false);
    explosion.reset(alien.body.x, alien.body.y);
    explosion.play('kaboom', 30, false, true);

    if (aliens.countLiving() == 0)
    {
        increaseScore(1000);

        enemyBullets.callAll('kill',this);
        stateText.text = " You Won, \n Click to restart";
        stateText.visible = true;

        //the "click to restart" handler
        game.input.onTap.addOnce(restart,this);
    }

}

function enemyHitsPlayer(player,bullet) {

    bullet.kill();

    live = lives.getFirstAlive();

    if (live) {
        live.kill();
    }

    //  And create an explosion :)
    var explosion = explosions.getFirstExists(false);
    explosion.reset(player.body.x, player.body.y);
    explosion.play('kaboom', 30, false, true);

    // When the player dies
    if (lives.countLiving() < 1) {
        player.kill();
        enemyBullets.callAll('kill');

        stateText.text=" GAME OVER \n Click to restart";
        stateText.visible = true;

        //the "click to restart" handler
        game.input.onTap.addOnce(restart,this);
    }

}

function enemyFires () {

    //  Grab the first bullet we can from the pool
    enemyBullet = enemyBullets.getFirstExists(false);

    livingEnemies.length=0;

    aliens.forEachAlive(function(alien){
        // put every living enemy in an array
        livingEnemies.push(alien);
    });

    if (enemyBullet && livingEnemies.length > 0) {

        var random = game.rnd.integerInRange(0,livingEnemies.length-1);

        // randomly select one of them
        var shooter = livingEnemies[random];
        // And fire the bullet from this enemy
        enemyBullet.reset(shooter.body.x, shooter.body.y);

        game.physics.arcade.moveToObject(enemyBullet,player,120);
        firingTimer = game.time.now + 2000;
    }

}

function fireBullet() {

    //  To avoid them being allowed to fire too fast we set a time limit
    if (game.time.now > bulletTime) {
        //  Grab the first bullet we can from the pool
        bullet = bullets.getFirstExists(false);

        if (bullet) {
            //  And fire it
            bullet.reset(player.x, player.y + 8);
            bullet.body.velocity.y = -400;
            bulletTime = game.time.now + 200;
        }
    }

}

function restart() {

    //  A new level starts

    //resets the life count
    lives.callAll('revive');
    //  And brings the aliens back from the dead :)
    aliens.removeAll();
    createAliens();

    //revives the player
    player.revive();
    //hides the text
    stateText.visible = false;

	resetScore();

}
<|endoftext|>


<commit_before>/**
 * Nextcloud - nextant
 *
 * This file is licensed under the Affero General Public License version 3 or
 * later. See the COPYING file.
 *
 * @author Maxence Lange <maxence@pontapreta.net>
 * @copyright Maxence Lange 2016
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

$(document)
		.ready(
				function() {

					(function(func) {
						$.fn.addClass = function() {
							func.apply(this, arguments);
							this.trigger('classChanged');
							return this;
						}
					})($.fn.addClass);

					(function(func) {
						$.fn.removeClass = function() {
							func.apply(this, arguments);
							this.trigger('classChanged');
							return this;
						}
					})($.fn.removeClass);

					$('#searchresults').on('classChanged', function() {
						if ($('#searchresults').attr('class') == 'hidden')
							$('#nextantList').hide();
						else
							$('#nextantList').show();
					});

					$('#searchbox').focusout(function() {
						nextantCurrentFocus = false;
						nextant.suggestShow();
					});

					$('#searchbox').focusin(function() {
						nextantCurrentFocus = true;
						nextant.suggestShow();
					});

					$(window).resize(function() {
						nextant.suggestShow();
					});

					$('html').keydown(function(e) {
						if (!nextantCurrentFocus)
							return;
						if (e.which == 13)
							return nextant.search();
						// if (e.which == 38)
						// return nextant.suggestSelect('prev');
						// if (e.which == 39)
						// return nextant.suggestSelect('select');
						// if (e.which == 40)
						// return nextant.suggestSelect('next');
					});

					var nextantCurrentSearch = '';
					var nextantCurrentFocus = false;
					var nextantSearchDelayTimer = null;
					var nextantSuggestDelayTimer = null;
					var nextantSuggestNoSpam = false;
					var nextantSuggestSelected = 0;
					var nextantSuggestResults = null;
					var nextant = {

						init : function() {
							$('#searchbox').on('input', function(e) {
								nextant.searchTimer();
								nextant.suggestTimer();
							});
						},

						searchTimer : function() {
							if (nextantSearchDelayTimer != null)
								clearTimeout(nextantSearchDelayTimer);

							nextantSearchDelayTimer = setTimeout(function() {
								nextant.search();
							}, 170);
						},

						suggestTimer : function() {
							if (nextantSuggestDelayTimer != null)
								clearTimeout(nextantSuggestDelayTimer);

							nextantSuggestDelayTimer = setTimeout(function() {
								nextant.suggest();
							}, 10);
						},

						search : function() {
							nextantSearchDelayTimer = null;

							var query = $('#searchbox').val();
							if (query == nextantCurrentSearch)
								return;
							nextantCurrentSearch = query;

							var data = {
								query : query,
								current_dir : nextant.get('dir')
							}

							nextant.searchRequest(data);
						},

						suggest : function() {
							nextantSuggestDelayTimer = null;
							var query = $('#searchbox').val();
							var data = {
								query : query
							}
							nextant.suggestRequest(data);
						},

						suggestRequest : function(data) {
							if (nextantSuggestNoSpam)
								return;
							$.post(
									OC.filePath('nextant', 'ajax',
											'suggest.php'), data,
									nextant.suggestResult);
						},

						suggestResult : function(response) {

							if (response == null || response.status > 0
									|| response.result.length == 0) {

								if (response.status > 0) {
									nextantSuggestNoSpam = true;
									setTimeout(function() {
										nextantSuggestNoSpam = false;
									}, 60000);
								}
								if ($('#nextantSugg_list').length)
									$('#nextantSugg_list').hide(200);
								return;
							}

							if (!$('#nextantSugg_list').length)
								$('#body-user').append(
										'

');

							nextant.suggestShow();

							$('#nextantSugg_list').empty();
							var result = response.result;
							nextantSuggestResult = result;
							for (var i = 0; i < result.length; i++) {
								var first = '';
								if (i == 0)
									first = 'nextantSugg_firstitem';

								$('#nextantSugg_list').append(
										'
'
												+ result[i].suggestion
												+ '
');
							}

							$('.nextantSugg_item').click(function() {
								nextant.suggestReplace($(this).text());
								nextant.search();
							});

						},

						suggestShow : function() {

							var offset = $('#searchbox').offset();
							var height = $('#searchbox').height();
							var top = offset.top + height + "px";
							var left = offset.left + "px";

							$('#nextantSugg_list').css({
								'position' : 'absolute',
								'left' : left,
								'top' : top
							});

							if (!$('#nextantSugg_list').length)
								return;
							if (nextantCurrentFocus)
								$('#nextantSugg_list').show(200);
							else
								$('#nextantSugg_list').hide(200);
						},

						suggestReplace : function(txt) {
							$('#searchbox').val(txt + ' ');
							$('#searchbox').focus();
						},

						// suggestSelect : function(pos) {
						// if (!nextantCurrentFocus)
						// return;
						// if (nextantSuggestResult == null)
						// return;
						//
						// switch (pos) {
						// case 'next':
						// if (nextantSuggestSelected <
						// nextantSuggestResult.length)
						// nextantSuggestSelected++;
						// break;
						// case 'prev':
						// if (nextantSuggestSelected > 1)
						// nextantSuggestSelected--;
						// break;
						// case 'select':
						// suggestReplace(nextantSuggestResult[nextantSuggestSelected]);
						// break;
						//
						// case 'reset':
						// nextantSuggestSelected = 0;
						// break;
						// }
						// },

						searchRequest : function(data) {
							$.post(
									OC
											.filePath('nextant', 'ajax',
													'search.php'), data,
									nextant.searchResult);
						},

						searchResult : function(response) {
							if (response == null)
								return;

							if (!$('#nextantList').length)
								$('#fileList')
										.append(
												'

');

							$('#nextantList').empty();

							response
									.forEach(function(entry) {
										var row = nextant
												.template_entry()
												.replace(/%ID%/gi, entry.id)
												.replace(/%TYPE%/gi, entry.type)
												.replace(/%TITLE%/gi,
														entry.title)
												.replace(/%LINKMAIN%/gi,
														entry.link_main)
												.replace(/%FILENAME%/gi,
														entry.filename)
												.replace(/%DIRPATH%/gi,
														entry.dirpath)
												.replace(/%ETAG%/gi, entry.etag)
												.replace(/%SIZE%/gi, entry.size)
												.replace(/%SIZEREAD%/gi,
														entry.size_readable)
												.replace(/%MIMETYPE%/gi,
														entry.mimetype)
												.replace(/%ICON%/gi, entry.icon)
												.replace(/%MTIME%/gi,
														entry.mtime).replace(
														/%HIGHLIGHT1%/gi,
														entry.highlight1)
												.replace(/%HIGHLIGHT2%/gi,
														entry.highlight2);

										row = row
												.replace(
														/%SHARED%/gi,
														(entry.shared != '') ? ' style="background-image:url('
																+ entry.shared
																+ ');"'
																: '');
										row = row
												.replace(
														/%DELETED%/gi,
														(entry.deleted != '') ? ' style="background-image:url('
																+ entry.deleted
																+ ');"'
																: '');

										$('#nextantList').append(row);
									});
						},

						template_entry : function() {

							$tmpl = '
';
							$tmpl += '';
							$tmpl += '';
							$tmpl += '';
							$tmpl += '
';
							$tmpl += '

';
							$tmpl += '
';
							$tmpl += 'Select';

							$tmpl += '';
							$tmpl += '
';
							$tmpl += '%TITLE%';
							$tmpl += '%HIGHLIGHT1%';
							$tmpl += '%HIGHLIGHT2%';
							$tmpl += '
';
							$tmpl += '	';
							$tmpl += '%SIZEREAD%	';
							$tmpl += '
';

							return $tmpl;
						},

						get : function(name, url) {
							if (!url)
								url = window.location.href;
							name = name.replace(/[\[\]]/g, "\\$&");
							var regex = new RegExp("[?&]" + name
									+ "(=([^&#]*)|&|#|$)"), results = regex
									.exec(url);
							if (!results)
								return null;
							if (!results[2])
								return '';
							return decodeURIComponent(results[2].replace(/\+/g,
									' '));
						}

					}

					nextant.init();

				});
<commit_msg>rewrite<commit_after>/**
 * Nextcloud - nextant
 *
 * This file is licensed under the Affero General Public License version 3 or
 * later. See the COPYING file.
 *
 * @author Maxence Lange <maxence@pontapreta.net>
 * @copyright Maxence Lange 2016
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */
(function() {

	/**
	 * @constructs Nextant
	 */
	var Nextant = function() {
		this.initialize();
	};

	Nextant.prototype = {

		fileList : null,

		/**
		 * Initialize the file search
		 */
		initialize : function() {

			var self = this;

			// detect if Files App is loaded
			this.fileAppLoaded = function() {
				return !!OCA.Files && !!OCA.Files.App;
			};

			// search request
			this.searchRequest = function(data) {
				$.post(OC.filePath('nextant', 'ajax', 'search2.php'), data,
						self.searchResult);
			};

			// display search result
			this.searchResult = function(result) {

				if (self.fileList == null)
					return;

				var data = [];
				_.each(result, function(item) {
					data.push(item.entry);
				});

				try {
					self.fileList.setFiles(data);
				} catch (e) {
				}

				// We edit each row
				_.each(result, function(item) {
					tr = self.getElem(item.entry.name);

					if (!$(tr).length)
						return;

					var elemhref = $(tr).find('td.filename').find('a.name');
					if ($(elemhref).length)
						$(elemhref).attr('href',
								$(elemhref).attr('href').replace(/%2F/g, '/'));

				});

			};

			// get TR elem from filelist
			this.getElem = function(file) {
				var list = $('#fileList').children('tr');
				for (var i = 0; i < list.length; i++) {
					if ($(list[i]).attr('data-file') == file)
						return $(list[i]);
				}

				return null;
			};

			// get info from url
			this.get = function(name, url) {

				if (!url)
					url = window.location.href;
				name = name.replace(/[\[\]]/g, "\\$&");
				var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), results = regex
						.exec(url);
				if (!results)
					return null;
				if (!results[2])
					return '';
				return decodeURIComponent(results[2].replace(/\+/g, ' '));
			};

			OC.Plugins.register('OCA.Search', this);
		},

		attach : function(search) {
			var self = this;

			// receiving search request in Files App
			search.setFilter('files', function(query) {
				if (self.fileAppLoaded()) {

					// init Search/FileList if needed
					if (self.fileList == null) {
						_.each(OC.Plugins.getPlugins('OCA.Search'), function(
								plugin) {
							if (plugin instanceof OCA.Search.Files)
								self.fileList = plugin.fileList;
						});
					}

					// sending the ajax request
					var data = {
						query : query,
						current_dir : self.get('dir')
					}

					self.searchRequest(data);
				}
			});

		}
	};
	OCA.Search.Nextant = Nextant;
	OCA.Search.nextant = new Nextant();
})();
<|endoftext|>


<commit_before><commit_msg>implemented minimum viable reducer codebase<commit_after>const DEFAULT_STATE = {
  searchTerm: ''
}

const rootReducer = (state, action) => {
  // if (!state) {
  //   state = DEFAULT_STATE
  // }
  state = state || DEFAULT_STATE
  switch (action.type) {
    default:
    return state
  }
}
<|endoftext|>


<commit_before>var serverws = {
	// IP + Port de la machine qui fait tourner le KaTZ-Link
	ip : "192.168.0.10",
	port : 9000,
	auto_connect : false,   //true,
	delay : 15000,
	socket : null
};

//var timer = null;

// Procedure de connection à la websocket
function serverws_connect(){

	console.log("Essai Connection " + serverws.ip + ":" + serverws.port);
	menu_connection_led(1) // voyant de connection orange

	var url = "ws://" + serverws.ip + ":" + serverws.port + "/";
	serverws.socket = new WebSocket(url);

	try {
		serverws.socket.onopen = function() {
			console.log("connection serveur démarrée")
			menu_connection_led(2) // voyant de connection vert
			serverws_Open();
		}

		serverws.socket.onmessage = function(event) {
	    	//console.log("data received");
	    	// fonction dans panel.js
			serverws_Message(event);
		}

		serverws.socket.onclose = function(){
			// fonction dans panel.js
			console.log("Deconnection du Serveur")
			menu_connection_led(0) // voyant de connection rouge
			serverws_Close();
			// Fonction de reconnection automatique
			//if(serverws.auto_connect){timer = setTimeout(serverws_connect,serverws.delay);}
		}

		serverws.socket.onerror = function(error){
			console.log("ERREUR connection serveur")
			menu_connection_led(0) // voyant de connection rouge
		}
	}

	catch(exception) {
		 //console.log("ERREUR connection serveur exception")
		 // fonction dans panel.js
		 serverws_Error(exception);
	}
}

function serverws_Open(){
	// Lancement de la boucle de rafraichissement du KaTZ-Pit
	mytimer = setInterval(pit_main, 100);
}

function serverws_Message(event){

	received_msg = event.data

	// stockage du message sous forme d'objet
	var new_data = JSON.parse(received_msg)
	//console.log("message received",new_data);

	var dataref;

	// Transfert des données recues dans le tableau KaTZ-Pit_Data
	for (dataref in new_data){
		KaTZPit_data[dataref]=new_data[dataref]
		}

	// Si reception d'un Ping sur le canal 8, on répond sur le canal 7
	if (KaTZPit_data["Ping"] != KaTZPit_data["Ping_old"]){
		CmdSiocSpe(7, KaTZPit_data["Ping"])
		KaTZPit_data["Ping_old"] = KaTZPit_data["Ping"]
	}

}

function serverws_send(command){

	if(serverws.socket.readyState==1){
		serverws.socket.send(command);
		//console.log("Envoi de la commande _ module serverws_send ..", command);
	}
}

function serverws_Close(){
	// Arrêt de la boucle de rafraichissement du KaTZ-Pit
	clearInterval(mytimer);
}

<commit_msg>Prévention des "multiconnections" d'un même pit<commit_after>var serverws = {
	// IP + Port de la machine qui fait tourner le KaTZ-Link
	ip : "192.168.0.10",
	port : 9000,
	auto_connect : false,   //true,
	delay : 15000,
	socket : null
};

//var timer = null;

// Procedure de connection à la websocket
function serverws_connect(){

	console.log("Essai Connection " + serverws.ip + ":" + serverws.port);

	try {
        serverws.socket.close()
     } catch(exception) {}

	menu_connection_led(1) // voyant de connection orange

	var url = "ws://" + serverws.ip + ":" + serverws.port + "/";
	serverws.socket = new WebSocket(url);

	try {
		serverws.socket.onopen = function() {
			console.log("connection serveur démarrée")
			menu_connection_led(2) // voyant de connection vert
			serverws_Open();
		}

		serverws.socket.onmessage = function(event) {
	    	//console.log("data received");
	    	// fonction dans panel.js
			serverws_Message(event);
		}

		serverws.socket.onclose = function(){
			// fonction dans panel.js
			console.log("Deconnection du Serveur")
			menu_connection_led(0) // voyant de connection rouge
			serverws_Close();
			// Fonction de reconnection automatique
			//if(serverws.auto_connect){timer = setTimeout(serverws_connect,serverws.delay);}
		}

		serverws.socket.onerror = function(error){
			console.log("ERREUR connection serveur")
			menu_connection_led(0) // voyant de connection rouge
		}
	}

	catch(exception) {
		 //console.log("ERREUR connection serveur exception")
		 // fonction dans panel.js
		 serverws_Error(exception);
	}
}

function serverws_Open(){
	// Lancement de la boucle de rafraichissement du KaTZ-Pit
	mytimer = setInterval(pit_main, 100);
}

function serverws_Message(event){

	received_msg = event.data

	// stockage du message sous forme d'objet
	var new_data = JSON.parse(received_msg)
	//console.log("message received",new_data);

	var dataref;

	// Transfert des données recues dans le tableau KaTZ-Pit_Data
	for (dataref in new_data){
		KaTZPit_data[dataref]=new_data[dataref]
		}

	// Si reception d'un Ping sur le canal 8, on répond sur le canal 7
	if (KaTZPit_data["Ping"] != KaTZPit_data["Ping_old"]){
		CmdSiocSpe(7, KaTZPit_data["Ping"])
		KaTZPit_data["Ping_old"] = KaTZPit_data["Ping"]
	}

}

function serverws_send(command){

	if(serverws.socket.readyState==1){
		serverws.socket.send(command);
		//console.log("Envoi de la commande _ module serverws_send ..", command);
	}
}

function serverws_Close(){
	// Arrêt de la boucle de rafraichissement du KaTZ-Pit
	clearInterval(mytimer);
}

<|endoftext|>


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

var fsevents        = require('fsevents'),
    fs              = require('fs'),
    extend          = require('util')._extend,
    http            = require('http'),
    Agent           = require('agentkeepalive'),
    getRelativePath = require('path').relative,
    RelPathList     = require('pathspec').RelPathList,
    async           = require('async'),
    colors          = require('colors/safe'),

    config          = {
        debug:      false,
        logIgnored: false,
        localDir:   '.',

        webdav: {
            ssl: false
        },

        ignore: {
            global: [],
            watch:  [],
            sync:   [],
            git:    [],
        },

        sound: {
            error:   'Basso',
            success: 'Submarine'
        }
    },

    ignoreList      = {},
    movedOut        = null,
    DEBUG           = false,
    drainSound = null;

// Merge default configuration options with config.json if present
if (fs.existsSync(config.localDir + '/config.json')) {
    config = extend(config, JSON.parse(fs.readFileSync(config.localDir + '/config.json')));
    DEBUG = !!config.debug;
}

// Build regular expressions from globs
for (var ignoreType in config.ignore) {
    ignoreList[ignoreType] = RelPathList.parse(config.ignore[ignoreType]);
}

function playSound(sound) {
    sound = sound || config.sound.success;
    require('child_process').spawn('afplay', ['/System/Library/Sounds/' + sound + '.aiff']);
}

function shouldIgnore(event, path) {
    var events = ['global'];

    if (typeof path !== 'string') {
        path = event;
    } else {
        events.push(event);
    }

    for (var x = 0, len = events.length; x < len; x++) {
        if (ignoreList[events[x]] && ignoreList[events[x]].matches(path)) {
            return true;
        }
    }

    if (event === 'sync') {
        // HACK: Do not try to sync files to the root directory
        return path.indexOf('/') === -1;
    }

    return false;
}

var watcher = fsevents(config.localDir);

var watcherLogStream;

if (config.watcherLog) {
    watcherLogStream = fs.createWriteStream(config.watcherLog, {flags: 'a'});
}

var keepaliveAgent = new Agent({
    maxSockets:       100,
    maxFreeSockets:   10,
    timeout:          120000,
    keepAliveTimeout: 60000
});

var q = async.queue(function (task, callback) {
    webDavRequest(task.verb, task.dst, task.src, function(err) {
        task.cb(err);
        callback();
    });
}, 1);

q.drain = function() {
    if (drainSound) {
        playSound(drainSound);
    }
};

function webDavRequest(verb, destination, file, callback) {
    var fileStream,
        request,
        headers = {};

    if (file) {
        if (/move/i.test(verb)) {
            headers['Destination'] = config.webdav.path + '/' + destination;
            destination = file;
        } else {
            fileStream = fs.createReadStream(file);
            headers['Content-Length'] = fs.statSync(file).size;
        }
    }

    request = http.request({
        host:    config.webdav.hostname,
        port:    config.webdav.ssl ? 443 : 80,
        method:  verb,
        path:    config.webdav.path + '/' + destination,
        headers: headers,
        agent:   keepaliveAgent,
        auth:    config.webdav.username + ':' + config.webdav.password
    });

    request.on("response", function (res) {
        // Ignore when a delete fails due to a 404
        if (res.statusCode >= 400 && !(verb === 'DELETE' && res.statusCode == 404) && // delete a file that doesn't exist
            !(verb === 'MKCOL' && res.statusCode == 405)  // make a directory that already exists
        ) {
            res.on('end', function () {
                callback(new Error("HTTP " + verb + " failed with code " + res.statusCode + " for " + request.path));
            });
        } else {
            res.on('end', callback);
        }

        res.resume();
    });

    request.on('error', callback);

    if (fileStream) {
        fileStream.pipe(request);
    } else {
        request.end();
    }
}

watcher.on('change', function (path, info) {
    var verb, src, dst, ignore,
        isDir = info.type === 'directory',
        relPath = getRelativePath(config.localDir, path);

    DEBUG && console.error(info);

    if (watcherLogStream) {
        watcherLogStream.write([
            Date.now(),
            info.id,
            info.event,
            info.type,
            Object.keys(info.changes).filter(function(key) { return info.changes[key]; }),
            path
        ].join('\t') + '\n');
    }

    // Add trailing slash to directories
    if (isDir) {
        path = path + '/';
        info.path = path;
        // HACK: fsevent is passing an unknown event for directory creation (deleted works)
        if (info.event === 'unknown') {
            // HACK: fsevent passes an unknown event for directory chowning
            if (info.changes.inode || info.changes.access || info.changes.xattrs || info.changes.finder) {
                console.warn('Ignoring chown/chmod to: ' + path);
                DEBUG && console.log(info);
                return;
            }
            info.event = 'created';
        }
    }

    dst = relPath;
    ignore = shouldIgnore('watch', relPath);

    switch (info.event) {
        case 'modified':
        case 'created':
            verb = isDir ? 'MKCOL' : 'PUT';
            src = path;
            break;
        case 'deleted':
            verb = 'DELETE';
            dst = null;
            break;
        case 'moved-in':
            if (movedOut) {
                src = getRelativePath(config.localDir, movedOut);
                dst = relPath;
                movedOut = null;
            }
            verb = 'MOVE';
            break;
        case 'moved-out':
            movedOut = path;
            break;
    }

    if (verb) {
        // Do not log ignored watch events if logIgnored is false
        if (ignore && config.logIgnored === false) {
            return;
        }

        if (verb === 'MKCOL' || verb === 'DELETE') {
            path = relPath;
            dst = path;
            src = null;
        }

        // File was ignored in either global or watch
        if (ignore) {
            console.log((ignore ? colors.yellow('[IGNORED] ') : '') + verb + ': ' + relPath + (dst ? ' -> ' + dst : ''));
            return;
        }

        // TODO: Memoize should ignore
        if (!shouldIgnore('sync', relPath)) {
            q.push({
                verb: verb, dst: dst, src: src, cb: function (err) {
                    if (err) {
                        playSound(config.sound.error);
                        console.log(colors.red('[FAILED ] ') + verb + ': ' + relPath + (dst ? ' -> ' + dst : '') + '(' + err + ')');
                        return;
                    }

                    drainSound = (drainSound === null) ? config.sound.success : drainSound;
                    console.log(colors.green('[SUCCESS] ') + verb + ': ' + relPath + (dst ? ' -> ' + dst : ''));
                }
            });
        }
    }

});

watcher.start();
<commit_msg>fix path for config.json to look in cwd<commit_after>#!/usr/bin/env node

var fsevents        = require('fsevents'),
    fs              = require('fs'),
    extend          = require('util')._extend,
    http            = require('http'),
    Agent           = require('agentkeepalive'),
    getRelativePath = require('path').relative,
    RelPathList     = require('pathspec').RelPathList,
    async           = require('async'),
    colors          = require('colors/safe'),

    config          = {
        debug:      false,
        logIgnored: false,
        localDir:   '.',

        webdav: {
            ssl: false
        },

        ignore: {
            global: [],
            watch:  [],
            sync:   [],
            git:    [],
        },

        sound: {
            error:   'Basso',
            success: 'Submarine'
        }
    },

    ignoreList      = {},
    movedOut        = null,
    DEBUG           = false,
    drainSound = null;

// Merge default configuration options with config.json if present
if (fs.existsSync(process.cwd() + '/config.json')) {
    config = extend(config, require(process.cwd() + '/config.json'));
}

// Build regular expressions from globs
for (var ignoreType in config.ignore) {
    ignoreList[ignoreType] = RelPathList.parse(config.ignore[ignoreType]);
}

function playSound(sound) {
    sound = sound || config.sound.success;
    require('child_process').spawn('afplay', ['/System/Library/Sounds/' + sound + '.aiff']);
}

function shouldIgnore(event, path) {
    var events = ['global'];

    if (typeof path !== 'string') {
        path = event;
    } else {
        events.push(event);
    }

    for (var x = 0, len = events.length; x < len; x++) {
        if (ignoreList[events[x]] && ignoreList[events[x]].matches(path)) {
            return true;
        }
    }

    if (event === 'sync') {
        // HACK: Do not try to sync files to the root directory
        return path.indexOf('/') === -1;
    }

    return false;
}

var watcher = fsevents(config.localDir),
    watcherLogStream;

if (config.watcherLog) {
    watcherLogStream = fs.createWriteStream(config.watcherLog, {flags: 'a'});
}

var keepaliveAgent = new Agent({
    maxSockets:       100,
    maxFreeSockets:   10,
    timeout:          120000,
    keepAliveTimeout: 60000
});

var q = async.queue(function (task, callback) {
    webDavRequest(task.verb, task.dst, task.src, function(err) {
        task.cb(err);
        callback();
    });
}, 1);

q.drain = function() {
    if (drainSound) {
        playSound(drainSound);
    }
};

function webDavRequest(verb, destination, file, callback) {
    var fileStream,
        request,
        headers = {};

    if (file) {
        if (/move/i.test(verb)) {
            headers['Destination'] = config.webdav.path + '/' + destination;
            destination = file;
        } else {
            fileStream = fs.createReadStream(file);
            headers['Content-Length'] = fs.statSync(file).size;
        }
    }

    request = http.request({
        host:    config.webdav.hostname,
        port:    config.webdav.ssl ? 443 : 80,
        method:  verb,
        path:    config.webdav.path + '/' + destination,
        headers: headers,
        agent:   keepaliveAgent,
        auth:    config.webdav.username + ':' + config.webdav.password
    });

    request.on("response", function (res) {
        // Ignore when a delete fails due to a 404
        if (res.statusCode >= 400 && !(verb === 'DELETE' && res.statusCode == 404) && // delete a file that doesn't exist
            !(verb === 'MKCOL' && res.statusCode == 405)  // make a directory that already exists
        ) {
            res.on('end', function () {
                callback(new Error("HTTP " + verb + " failed with code " + res.statusCode + " for " + request.path));
            });
        } else {
            res.on('end', callback);
        }

        res.resume();
    });

    request.on('error', callback);

    if (fileStream) {
        fileStream.pipe(request);
    } else {
        request.end();
    }
}

watcher.on('change', function (path, info) {
    var verb, src, dst, ignore,
        isDir = info.type === 'directory',
        relPath = getRelativePath(config.localDir, path);

    DEBUG && console.error(info);

    if (watcherLogStream) {
        watcherLogStream.write([
            Date.now(),
            info.id,
            info.event,
            info.type,
            Object.keys(info.changes).filter(function(key) { return info.changes[key]; }),
            path
        ].join('\t') + '\n');
    }

    // Add trailing slash to directories
    if (isDir) {
        path = path + '/';
        info.path = path;
        // HACK: fsevent is passing an unknown event for directory creation (deleted works)
        if (info.event === 'unknown') {
            // HACK: fsevent passes an unknown event for directory chowning
            if (info.changes.inode || info.changes.access || info.changes.xattrs || info.changes.finder) {
                console.warn('Ignoring chown/chmod to: ' + path);
                DEBUG && console.log(info);
                return;
            }
            info.event = 'created';
        }
    }

    dst = relPath;
    ignore = shouldIgnore('watch', relPath);

    switch (info.event) {
        case 'modified':
        case 'created':
            verb = isDir ? 'MKCOL' : 'PUT';
            src = path;
            break;
        case 'deleted':
            verb = 'DELETE';
            dst = null;
            break;
        case 'moved-in':
            if (movedOut) {
                src = getRelativePath(config.localDir, movedOut);
                dst = relPath;
                movedOut = null;
            }
            verb = 'MOVE';
            break;
        case 'moved-out':
            movedOut = path;
            break;
    }

    if (verb) {
        // Do not log ignored watch events if logIgnored is false
        if (ignore && config.logIgnored === false) {
            return;
        }

        if (verb === 'MKCOL' || verb === 'DELETE') {
            path = relPath;
            dst = path;
            src = null;
        }

        // File was ignored in either global or watch
        if (ignore) {
            console.log((ignore ? colors.yellow('[IGNORED] ') : '') + verb + ': ' + relPath + (dst ? ' -> ' + dst : ''));
            return;
        }

        // TODO: Memoize should ignore
        if (!shouldIgnore('sync', relPath)) {
            q.push({
                verb: verb, dst: dst, src: src, cb: function (err) {
                    if (err) {
                        playSound(config.sound.error);
                        console.log(colors.red('[FAILED ] ') + verb + ': ' + relPath + (dst ? ' -> ' + dst : '') + '(' + err + ')');
                        return;
                    }

                    drainSound = (drainSound === null) ? config.sound.success : drainSound;
                    console.log(colors.green('[SUCCESS] ') + verb + ': ' + relPath + (dst ? ' -> ' + dst : ''));
                }
            });
        }
    }

});

watcher.start();
<|endoftext|>


<commit_before>'use strict';

var path = require('path');
var url = require('url');
var crypto = require('crypto');
var cssFontParser = require('css-font-parser');

/**
 * FontFace 描述类
 * @param   {object}
 */
function FontFace(options) {
    this.id = options.id;
    this.family = options.family;
    this.files = options.files;
    this.stretch = options.stretch;
    this.style = options.style;
    this.weight = options.weight;
}

/**
 * 解析 @font-face
 * @param   {CSSFontFaceRule}
 * @return  {FontFace}
 */
FontFace.parse = function parseFontFace(cssFontFaceRule) {
    var baseURI = cssFontFaceRule.parentStyleSheet.href;
    var s = cssFontFaceRule.style;

    var family = s['font-family'];
    var stretch = s['font-stretch'];
    var style = s['font-style'];
    var weight = s['font-weight'];

    var src = s.src;
    var files = parseFontFaceSrc(src, baseURI);

    if (!family) {
        return null;
    }

    family = parseFontfamily(family)[0];

    var id = crypto
        .createHash('md5')
        .update(files.join(','))
        .digest('hex');

    return new FontFace({
        id: id,
        family: family,
        files: files,
        stretch: stretch,
        style: style,
        weight: weight
    });
};

/**
 * 匹配 CSS 规则
 * @see https://www.w3.org/html/ig/zh/wiki/CSS3字体模块#.E5.AD.97.E4.BD.93.E5.8C.B9.E9.85.8D.E7.AE.97.E6.B3.95
 * @param   {CSSStyleDeclaration}
 * @return  {Boolean}
 */
FontFace.prototype.match = function(style) {

    var fontFamilys = [];

    if (!style['font-family'] && !style.font) {
        return false;
    }

    for (var i = 0, key; i < style.length; i++) {
        key = style[i];

        if (key === 'font-family') {
            fontFamilys = parseFontfamily(style[key]);
        } else if (key === 'font') {
            var s = cssFontParser(style[key]);
            if (s) {
                var f = s['font-family'];
                if (Array.isArray(f)) {
                    fontFamilys = f;
                }
            }
        }
    }

    // 暂时只能做到名称匹配，会产生冗余
    // TODO 完善匹配算法 fontFamily | fontStretch | fontStyle | fontWeight
    if (fontFamilys.indexOf(this.family) !== -1) {
        return true;
    } else {
        return false;
    }
};

// 解析 @font-face src 值
function parseFontFaceSrc(value, baseURI) {
    var list = [];
    var src;

    var RE_FONT_URL = /url\(["']?(.*?)["']?\)(?:\s*format\(["']?(.*?)["']?\))?/ig;

    RE_FONT_URL.lastIndex = 0;

    while ((src = RE_FONT_URL.exec(value)) !== null) {
        list.push(new FontFile(baseURI, src[1], src[2]));
    }

    return list;
}

// font-face 路径与字体类型描述信息类
function FontFile(baseURI, source, format) {

    if (baseURI) {
        source = url.resolve(baseURI, source);
    }

    source = source.replace(/[\?#].*$/, '');

    if (!format) {

        switch (path.dirname(source).toLowerCase()) {
            case '.eot':
                format = 'embedded-opentype';
                break;
            case '.woff':
                format = 'woff';
                break;
            case '.ttf':
                format = 'truetype';
                break;
            case '.svg':
                format = 'svg';
                break;
        }
    } else {
        format = format.toLowerCase();
    }

    this.source = source;
    this.format = format;
}

FontFile.prototype.toString = function() {
    return this.source;
};

// 解析 font-family 属性值为数组
function parseFontfamily(fontFamily) {
    // TODO test
    var list = fontFamily
        .replace(/^\s*["']?|["']?\s*$|["']?\s*(,)\s*["']?/g, '$1')
        .split(',');
    return list;
}

/**
 * WebFont 描述类 - 继承自 FontFace
 * @param   {object}
 */
function WebFont(options) {
    FontFace.call(this, options);
    this.chars = '';
    this.selectors = [];
}

WebFont.parse = function(cssFontFaceRule) {
    return new WebFont(FontFace.parse(cssFontFaceRule));
};
WebFont.prototype = Object.create(FontFace.prototype);
WebFont.prototype.constructor = WebFont;

module.exports = WebFont;<commit_msg>修复特殊情况下获取字体路径的BUG<commit_after>'use strict';

var path = require('path');
var url = require('url');
var crypto = require('crypto');
var cssFontParser = require('css-font-parser');
var utils = require('./utils');

/**
 * WebFont 描述类
 * @param   {object}
 */
function WebFont(options) {
    this.id = options.id;
    this.family = options.family;
    this.files = options.files;
    this.stretch = options.stretch;
    this.style = options.style;
    this.weight = options.weight;

    this.chars = options.chars || '';
    this.selectors = options.selectors || [];
}

/**
 * 解析 @font-face
 * @param   {CSSFontFaceRule}
 * @return  {WebFont}
 */
WebFont.parse = function parseFontFace(cssFontFaceRule) {
    var baseURI = cssFontFaceRule.parentStyleSheet.href;
    var s = cssFontFaceRule.style;

    var family = s['font-family'];
    var stretch = s['font-stretch'];
    var style = s['font-style'];
    var weight = s['font-weight'];

    var src = s.src;
    var files = parseFontFaceSrc(src, baseURI);

    if (!family) {
        return null;
    }

    family = parseFontfamily(family)[0];

    var id = crypto
        .createHash('md5')
        .update(files.join(','))
        .digest('hex');

    return new WebFont({
        id: id,
        family: family,
        files: files,
        stretch: stretch,
        style: style,
        weight: weight
    });
};

WebFont.FontFile = FontFile;
WebFont.parseFontFaceSrc = parseFontFaceSrc;
WebFont.parseFontfamily = parseFontfamily;

/**
 * 匹配 CSS 规则
 * @see https://www.w3.org/html/ig/zh/wiki/CSS3字体模块#.E5.AD.97.E4.BD.93.E5.8C.B9.E9.85.8D.E7.AE.97.E6.B3.95
 * @param   {CSSStyleDeclaration}
 * @return  {Boolean}
 */
WebFont.prototype.match = function(style) {

    var fontFamilys = [];

    if (!style['font-family'] && !style.font) {
        return false;
    }

    for (var i = 0, key; i < style.length; i++) {
        key = style[i];

        if (key === 'font-family') {
            fontFamilys = parseFontfamily(style[key]);
        } else if (key === 'font') {
            var s = cssFontParser(style[key]);
            if (s) {
                var f = s['font-family'];
                if (Array.isArray(f)) {
                    fontFamilys = f;
                }
            }
        }
    }

    // 暂时只能做到名称匹配，会产生冗余
    // TODO 完善匹配算法 fontFamily | fontStretch | fontStyle | fontWeight
    if (fontFamilys.indexOf(this.family) !== -1) {
        return true;
    } else {
        return false;
    }
};

/**
 * 解析 @font-face src 值
 * @param   {String}    src
 * @param   {String}    baseURI
 * @param   {Array<Object>}
 */
function parseFontFaceSrc(value, baseURI) {
    var list = [];
    var src;

    var RE_FONT_URL = /url\(["']?(.*?)["']?\)(?:\s*format\(["']?(.*?)["']?\))?/ig;

    RE_FONT_URL.lastIndex = 0;

    while ((src = RE_FONT_URL.exec(value)) !== null) {
        list.push(new FontFile(baseURI, src[1], src[2]));
    }

    return list;
}

/**
 * 解析 font-family 值
 * @param   {String}
 * @return  {Array<String>}
 */
function parseFontfamily(fontFamily) {
    return utils.split(fontFamily).map(function(value) {
        return value.replace(/^["']|["']$/g, '');
    });
}

// font-face 路径与字体类型描述信息类
function FontFile(baseURI, source, format) {

    if (!/^https?\:/.test(source)) {
        source = url.resolve(baseURI, source);
    }

    if (/^https?\:/.test(source)) {
        source = source.replace(/[#].*$/, '');
    } else {
        source = source.replace(/[?#].*$/, '');
    }

    if (!format) {
        switch (path.extname(source.replace(/\?.*$/, '')).toLowerCase()) {
            case '.eot':
                format = 'embedded-opentype';
                break;
            case '.woff2':
                format = 'woff2';
                break;
            case '.woff':
                format = 'woff';
                break;
            case '.ttf':
                format = 'truetype';
                break;
            case '.svg':
                format = 'svg';
                break;
        }
    } else {
        format = format.toLowerCase();
    }

    this.source = source;
    this.format = format;
}

FontFile.prototype.toString = function() {
    return this.source;
};

module.exports = WebFont;<|endoftext|>


<commit_before>'use strict';

const tokens = require('./token.js');
const rp = require('request-promise');

const get = (link) => {
  const wuURL = 'http://api.wunderground.com/api/' + tokens.WU_TOKEN + '/';
  const API = 'conditions';
  const fullURL = wuURL + API + link;
  console.log('To WU:', fullURL);
  return rp({
    uri: fullURL,
    method: 'GET'
  }).then((response) => {
    console.log('WU:', response);
    return JSON.parse(response);
  });
};

const location = (query) => {
  return rp({
    uri: 'http://autocomplete.wunderground.com/aq',
    method: 'GET',
    qs: {
      query: query
    }
  }).then((response) => {
    return JSON.parse(response);
  });
};

module.exports = {
  loc: location,
  get: get
};
<commit_msg>.json<commit_after>'use strict';

const tokens = require('./token.js');
const rp = require('request-promise');

const get = (link) => {
  const wuURL = 'http://api.wunderground.com/api/' + tokens.WU_TOKEN + '/';
  const API = 'conditions';
  const fullURL = wuURL + API + link + '.json';
  console.log('To WU:', fullURL);
  return rp({
    uri: fullURL,
    method: 'GET'
  }).then((response) => {
    console.log('WU:', response);
    return JSON.parse(response);
  });
};

const location = (query) => {
  return rp({
    uri: 'http://autocomplete.wunderground.com/aq',
    method: 'GET',
    qs: {
      query: query
    }
  }).then((response) => {
    return JSON.parse(response);
  });
};

module.exports = {
  loc: location,
  get: get
};
<|endoftext|>