<commit_before>'use strict';

const fs = require('fs');
const randomString = require('randomstring');
const electron = require('electron');
const childProcess = require('child_process');
const app = electron.app;
const path = require('path');
const isDevEnv = require('../utils/misc.js').isDevEnv;
const isMac = require('../utils/misc.js').isMac;

// Search library
const libSymphonySearch = require('./searchLibrary');

// Crypto Library
const Crypto = require('../cryptoLib');

// Path for the exec file and the user data folder
const userData = path.join(app.getPath('userData'));
const execPath = path.dirname(app.getPath('exe'));

// Constants paths for temp indexing folders
const TEMP_BATCH_INDEX_FOLDER = isDevEnv ? './data/temp_batch_indexes' : path.join(userData, 'data/temp_batch_indexes');
const TEMP_REAL_TIME_INDEX = isDevEnv ? './data/temp_realtime_index' : path.join(userData, 'data/temp_realtime_index');
// Main User Index path
const INDEX_PREFIX = isDevEnv ? './data/search_index' : path.join(userData, 'data/search_index');
// Folder contains real time, batch and user index
const INDEX_DATA_FOLDER = isDevEnv ? './data' : path.join(userData, 'data');
//3 Months
const SEARCH_PERIOD_SUBTRACTOR = 3 * 31 * 24 * 60 * 60 * 1000;
const MINIMUM_DATE = '0000000000000';
const MAXIMUM_DATE = '9999999999999';
const INDEX_VERSION = 'v1';

const SORT_BY_SCORE = 0;
const BATCH_RANDOM_INDEX_PATH_LENGTH = 20;

// library path contractor
const winArchPath = process.arch === 'ia32' ? 'library/indexvalidator-x86.exe' : 'library/indexvalidator-x64.exe';
const rootPath = isMac ? 'library/indexvalidator.exec' : winArchPath;
const productionPath = path.join(execPath, isMac ? '..' : '', rootPath);
const devPath = path.join(__dirname, '..', '..', rootPath);
const INDEX_VALIDATOR = isDevEnv ? devPath : productionPath;

/**
 * This search class communicates with the SymphonySearchEngine C library via node-ffi.
 * There should be only 1 instance of this class in the Electron
 */
class Search {

    /**
     * Constructor for the SymphonySearchEngine library
     * @param userId (for the index folder name)
     * @param key
     */
    constructor(userId, key) {
        console.time('Decrypting');
        this.isInitialized = false;
        this.userId = userId;
        this.key = key;
        this.indexFolderName = INDEX_PREFIX + '_' + this.userId + '_' + INDEX_VERSION;
        this.dataFolder = INDEX_DATA_FOLDER;
        this.realTimeIndex = TEMP_REAL_TIME_INDEX;
        this.batchIndex = TEMP_BATCH_INDEX_FOLDER;
        this.messageData = [];
        this.crypto = new Crypto(userId, key);
        this.decryptAndInit();
    }

    decryptAndInit() {
        this.crypto.decryption().then(() => {
            console.timeEnd('Decrypting');
            this.init();
        }).catch(() => {
            this.init();
        });
    }

    /**
     * returns isInitialized boolean
     * @returns {boolean}
     */
    isLibInit() {
        return this.isInitialized;
    }

    /**
     * This init function
     * initialise the SymphonySearchEngine library
     * and creates a folder in the userData
     */
    init() {
        libSymphonySearch.symSEInit();
        libSymphonySearch.symSEEnsureFolderExists(this.dataFolder);
        libSymphonySearch.symSERemoveFolder(this.realTimeIndex);
        libSymphonySearch.symSERemoveFolder(this.batchIndex);
        Search.indexValidator(this.indexFolderName);
        Search.indexValidator(this.realTimeIndex);
        let indexDateStartFrom = new Date().getTime() - SEARCH_PERIOD_SUBTRACTOR;
        // Deleting all the messages except 3 Months from now
        libSymphonySearch.symSEDeleteMessages(this.indexFolderName, null,
            MINIMUM_DATE, indexDateStartFrom.toString());
        this.isInitialized = true;
    }

    /**
     * An array of messages is passed for indexing
     * it will be indexed in a temporary index folder
     * @param {Array} messages
     * @returns {Promise}
     */
    indexBatch(messages) {
        return new Promise((resolve, reject) => {
            if (!messages) {
                reject(new Error('Messages is required'));
                return;
            }

            if (!(JSON.parse(messages) instanceof Array)) {
                reject(new Error('Messages must be an array'));
                return;
            }

            if (!this.isInitialized) {
                reject(new Error('Library not initialized'));
                return;
            }

            const indexId = randomString.generate(BATCH_RANDOM_INDEX_PATH_LENGTH);
            libSymphonySearch.symSECreatePartialIndexAsync(this.batchIndex, indexId, messages, (err, res) => {
                if (err) {
                    reject(new Error(err));
                }
                resolve(res);
            });
        });
    }

    /**
     * Merging the temporary
     * created from indexBatch()
     */
    mergeIndexBatches() {
        return new Promise((resolve, reject) => {
            libSymphonySearch.symSEMergePartialIndexAsync(this.indexFolderName, this.batchIndex, (err, res) => {
                if (err) {
                    reject(new Error(err));
                }
                libSymphonySearch.symSERemoveFolder(this.batchIndex);
                resolve(res);
            });
        });
    }

    /**
     * An array of messages to be indexed
     * in real time
     * @param message
     */
    realTimeIndexing(message) {
        if (!message) {
            return new Error('Message is required');
        }

        if (!(JSON.parse(message) instanceof Array)){
            return new Error('Message must be an array');
        }

        if (!this.isInitialized) {
            return new Error('Library not initialized');
        }

        let result = libSymphonySearch.symSEIndexRealTime(this.realTimeIndex, message);
        return result === 0 ? "Successful" : result
    }

    /**
     * Reading a json file
     * for the demo search app only
     * @param {String} batch
     * @returns {Promise}
     */
    readJson(batch) {
        return new Promise((resolve, reject) => {
            let dirPath = path.join(execPath, isMac ? '..' : '', 'msgsjson', batch);
            let messageFolderPath = isDevEnv ? path.join('./msgsjson', batch) : dirPath;
            let files = fs.readdirSync(messageFolderPath);
            this.messageData = [];
            files.forEach((file) => {
                let tempPath = path.join(messageFolderPath, file);
                let data = fs.readFileSync(tempPath, "utf8");
                if (data) {
                    try {
                        this.messageData.push(JSON.parse(data));
                    } catch (err) {
                        reject(new Error(err))
                    }
                } else {
                    reject(new Error('Error reading batch'))
                }
            });
            resolve(this.messageData);
        });
    }

    /**
     * Encrypting the index after the merging the index
     * to the main user index
     */
    encryptIndex() {
        return this.crypto.encryption().then(() => {
            return 'Success'
        }).catch((e) => {
            return (new Error(e));
        });
    }

    /**
     * This returns the search results
     * which returns a char *
     * @param {String} query
     * @param {Array} senderIds
     * @param {Array} threadIds
     * @param {String} fileType
     * @param {String} startDate
     * @param {String} endDate
     * @param {Number} limit
     * @param {Number} offset
     * @param {Number} sortOrder
     * @returns {Promise}
     */
    searchQuery(query, senderIds, threadIds, fileType, startDate,
                endDate, limit, offset, sortOrder) {

        let _limit = limit;
        let _offset = offset;
        let _sortOrder = sortOrder;

        return new Promise((resolve, reject) => {
            if (!this.isInitialized) {
                reject(new Error('Library not initialized'));
                return;
            }

            if (!fs.existsSync(this.indexFolderName) || !fs.existsSync(this.realTimeIndex)) {
                reject('Index folder does not exist.');
                return;
            }

            let q = Search.constructQuery(query, senderIds, threadIds, fileType);

            if (q === undefined) {
                reject(new Error('Search query error'));
                return;
            }

            let searchPeriod = new Date().getTime() - SEARCH_PERIOD_SUBTRACTOR;
            let sd_time = searchPeriod;
            if (startDate) {
                sd_time = new Date(parseInt(startDate, 10)).getTime();
                if (sd_time < searchPeriod) {
                    sd_time = searchPeriod;
                }
            }

            let ed_time = MAXIMUM_DATE;
            if (endDate) {
                let etime = new Date(parseInt(endDate, 10)).getTime();
                if (etime) {
                    ed_time = etime;
                }
            }

            if (!_limit && _limit === "" && typeof _limit !== 'number' && Math.round(_limit) !== _limit) {
                _limit = 25;
            }

            if (!_offset && _offset === "" && typeof _offset !== 'number' && Math.round(_offset) !== _offset) {
                _offset = 0
            }

            if (!_sortOrder && _sortOrder === "" && typeof _sortOrder !== 'number' && Math.round(_sortOrder) !== _sortOrder) {
                _sortOrder = SORT_BY_SCORE;
            }

            const returnedResult = libSymphonySearch.symSESearch(this.indexFolderName, this.realTimeIndex, q, sd_time.toString(), ed_time.toString(), _offset, _limit, _sortOrder);
            try {
                let ret = returnedResult.readCString();
                resolve(JSON.parse(ret));
            } finally {
                libSymphonySearch.symSEFreeResult(returnedResult);
            }
        });
    }

    /**
     * returns the latest message timestamp
     * from the indexed data
     * @returns {Promise}
     */
    getLatestMessageTimestamp() {
        return new Promise((resolve, reject) => {
            if (!this.isInitialized) {
                reject('Not initialized');
                return;
            }

            if (!fs.existsSync(this.indexFolderName)) {
                reject('Index folder does not exist.');
                return;
            }

            libSymphonySearch.symSEGetLastMessageTimestampAsync(this.indexFolderName, (err, res) => {
                if (err) {
                    reject(new Error(err));
                }
                const returnedResult = res;
                try {
                    let ret = returnedResult.readCString();
                    resolve(ret);
                } finally {
                    libSymphonySearch.symSEFreeResult(returnedResult);
                }
            });
        });
    }

    /**
     * This the query constructor
     * for the search function
     * @param {String} searchQuery
     * @param {Array} senderId
     * @param {Array} threadId
     * @param {String} fileType
     * @returns {string}
     */
    static constructQuery(searchQuery, senderId, threadId, fileType) {

        let searchText = "";
        if(searchQuery !== undefined) {
            searchText = searchQuery.trim().toLowerCase(); //to prevent injection of AND and ORs
        }
        let q = "";
        let hashTags = Search.getHashTags(searchText);
        let hashCashTagQuery = "";

        if(hashTags.length > 0) {
            hashCashTagQuery = " OR tags:(";
            hashTags.forEach((item) => {
                hashCashTagQuery = hashCashTagQuery + "\"" + item + "\" "
            });
            hashCashTagQuery += ")";
        }

        let hasAttachments = false;
        let additionalAttachmentQuery = "";
        if(fileType) {
            hasAttachments = true;
            if(fileType.toLowerCase() === "attachment") {
                additionalAttachmentQuery = "(hasfiles:true)";
            } else {
                additionalAttachmentQuery = "(filetype:(" + fileType +"))";
            }
        }

        if (searchText.length > 0 ) {
            q = "((text:(" + searchText + "))" + hashCashTagQuery ;
            if(hasAttachments) {
                q += " OR (filename:(" + searchText + "))" ;
            }
            q = q + ")";
        }

        q = Search.appendFilterQuery(q, "senderId", senderId);
        q = Search.appendFilterQuery(q, "threadId", threadId);

        if(q === "") {
            if(hasAttachments) {
                q = additionalAttachmentQuery;
            } else {
                q = undefined; //will be handled in the search function
            }
        } else {
            if(hasAttachments){
                q = q + " AND " + additionalAttachmentQuery
            }
        }
        return q;
    }

    /**
     * appending the senderId and threadId for the query
     * @param {String} searchText
     * @param {String} fieldName
     * @param {Array} valueArray
     * @returns {string}
     */
    static appendFilterQuery(searchText, fieldName, valueArray) {
        let q = "";
        if (valueArray && valueArray.length > 0 ) {

            q += "(" + fieldName +":(";
            valueArray.forEach((item)=>{
                q+= "\"" + item + "\" ";
            });
            q += "))";
            if(searchText.length > 0 ) {
                q = searchText + " AND " + q;
            }

        } else {
            q = searchText;
        }

        return q;
    }

    // hashtags can have any characters(before the latest release it was
    // not like this). So the only regex is splitting the search query based on
    // whitespaces
    /**
     * return the hash cash
     * tags from the query
     * @param {String} searchText
     * @returns {Array}
     */
    static getHashTags(searchText) {
        let hashTags = [];
        let tokens = searchText.toLowerCase()
            .trim()
            .replace(/\s\s+/g, ' ')
            .split(' ').filter((el) => {return el.length !== 0});
        tokens.forEach((item) => {
            if (item.startsWith('#') || item.startsWith('$')) {
                hashTags.push(item);
            }
        });
        return hashTags;
    }

    /**
     * Validate the index folder exist or not
     * @param {String} file
     * @returns {*}
     */
    static indexValidator(file) {
        let data;
        let result = childProcess.execFileSync(INDEX_VALIDATOR, [file]).toString();
        try {
            data = JSON.parse(result);
            if (data.status === 'OK') {
                return data;
            }
            return new Error('Unable validate index folder')
        } catch (err) {
            throw new Error(err);
        }
    }
}

/**
 * Exporting the search library
 * @type {{Search: Search}}
 */
module.exports = {
    Search: Search
};
<commit_msg>SEARCH-480<commit_after>'use strict';

const fs = require('fs');
const randomString = require('randomstring');
const electron = require('electron');
const childProcess = require('child_process');
const app = electron.app;
const path = require('path');
const isDevEnv = require('../utils/misc.js').isDevEnv;
const isMac = require('../utils/misc.js').isMac;

// Search library
const libSymphonySearch = require('./searchLibrary');

// Crypto Library
const Crypto = require('../cryptoLib');

// Path for the exec file and the user data folder
const userData = path.join(app.getPath('userData'));
const execPath = path.dirname(app.getPath('exe'));

// Constants paths for temp indexing folders
const TEMP_BATCH_INDEX_FOLDER = isDevEnv ? './data/temp_batch_indexes' : path.join(userData, 'data/temp_batch_indexes');
const TEMP_REAL_TIME_INDEX = isDevEnv ? './data/temp_realtime_index' : path.join(userData, 'data/temp_realtime_index');
// Main User Index path
const INDEX_PREFIX = isDevEnv ? './data/search_index' : path.join(userData, 'data/search_index');
// Folder contains real time, batch and user index
const INDEX_DATA_FOLDER = isDevEnv ? './data' : path.join(userData, 'data');
//3 Months
const SEARCH_PERIOD_SUBTRACTOR = 3 * 31 * 24 * 60 * 60 * 1000;
const MINIMUM_DATE = '0000000000000';
const MAXIMUM_DATE = '9999999999999';
const INDEX_VERSION = 'v1';

const SORT_BY_SCORE = 0;
const BATCH_RANDOM_INDEX_PATH_LENGTH = 20;

// library path contractor
const winArchPath = process.arch === 'ia32' ? 'library/indexvalidator-x86.exe' : 'library/indexvalidator-x64.exe';
const rootPath = isMac ? 'library/indexvalidator.exec' : winArchPath;
const productionPath = path.join(execPath, isMac ? '..' : '', rootPath);
const devPath = path.join(__dirname, '..', '..', rootPath);
const INDEX_VALIDATOR = isDevEnv ? devPath : productionPath;

/**
 * This search class communicates with the SymphonySearchEngine C library via node-ffi.
 * There should be only 1 instance of this class in the Electron
 */
class Search {

    /**
     * Constructor for the SymphonySearchEngine library
     * @param userId (for the index folder name)
     * @param key
     */
    constructor(userId, key) {
        console.time('Decrypting');
        this.isInitialized = false;
        this.userId = userId;
        this.key = key;
        this.indexFolderName = INDEX_PREFIX + '_' + this.userId + '_' + INDEX_VERSION;
        this.dataFolder = INDEX_DATA_FOLDER;
        this.realTimeIndex = TEMP_REAL_TIME_INDEX;
        this.batchIndex = TEMP_BATCH_INDEX_FOLDER;
        this.messageData = [];
        this.crypto = new Crypto(userId, key);
        this.decryptAndInit();
    }

    decryptAndInit() {
        this.crypto.decryption().then(() => {
            console.timeEnd('Decrypting');
            this.init();
        }).catch(() => {
            this.init();
        });
    }

    /**
     * returns isInitialized boolean
     * @returns {boolean}
     */
    isLibInit() {
        return this.isInitialized;
    }

    /**
     * This init function
     * initialise the SymphonySearchEngine library
     * and creates a folder in the userData
     */
    init() {
        libSymphonySearch.symSEInit();
        libSymphonySearch.symSEEnsureFolderExists(this.dataFolder);
        libSymphonySearch.symSERemoveFolder(this.realTimeIndex);
        libSymphonySearch.symSERemoveFolder(this.batchIndex);
        Search.indexValidator(this.indexFolderName);
        Search.indexValidator(this.realTimeIndex);
        let indexDateStartFrom = new Date().getTime() - SEARCH_PERIOD_SUBTRACTOR;
        // Deleting all the messages except 3 Months from now
        libSymphonySearch.symSEDeleteMessages(this.indexFolderName, null,
            MINIMUM_DATE, indexDateStartFrom.toString());
        this.isInitialized = true;
    }

    /**
     * An array of messages is passed for indexing
     * it will be indexed in a temporary index folder
     * @param {Array} messages
     * @returns {Promise}
     */
    indexBatch(messages) {
        return new Promise((resolve, reject) => {
            if (!messages) {
                reject(new Error('Messages is required'));
                return;
            }

            if (!(JSON.parse(messages) instanceof Array)) {
                reject(new Error('Messages must be an array'));
                return;
            }

            if (!this.isInitialized) {
                reject(new Error('Library not initialized'));
                return;
            }

            const indexId = randomString.generate(BATCH_RANDOM_INDEX_PATH_LENGTH);
            libSymphonySearch.symSECreatePartialIndexAsync(this.batchIndex, indexId, messages, (err, res) => {
                if (err) {
                    reject(new Error(err));
                }
                resolve(res);
            });
        });
    }

    /**
     * Merging the temporary
     * created from indexBatch()
     */
    mergeIndexBatches() {
        return new Promise((resolve, reject) => {
            libSymphonySearch.symSEMergePartialIndexAsync(this.indexFolderName, this.batchIndex, (err, res) => {
                if (err) {
                    reject(new Error(err));
                }
                libSymphonySearch.symSERemoveFolder(this.batchIndex);
                resolve(res);
            });
        });
    }

    /**
     * An array of messages to be indexed
     * in real time
     * @param message
     */
    realTimeIndexing(message) {
        if (!message) {
            return new Error('Message is required');
        }

        if (!(JSON.parse(message) instanceof Array)){
            return new Error('Message must be an array');
        }

        if (!this.isInitialized) {
            return new Error('Library not initialized');
        }

        let result = libSymphonySearch.symSEIndexRealTime(this.realTimeIndex, message);
        return result === 0 ? "Successful" : result
    }

    /**
     * Reading a json file
     * for the demo search app only
     * @param {String} batch
     * @returns {Promise}
     */
    readJson(batch) {
        return new Promise((resolve, reject) => {
            let dirPath = path.join(execPath, isMac ? '..' : '', 'msgsjson', batch);
            let messageFolderPath = isDevEnv ? path.join('./msgsjson', batch) : dirPath;
            let files = fs.readdirSync(messageFolderPath);
            this.messageData = [];
            files.forEach((file) => {
                let tempPath = path.join(messageFolderPath, file);
                let data = fs.readFileSync(tempPath, "utf8");
                if (data) {
                    try {
                        this.messageData.push(JSON.parse(data));
                    } catch (err) {
                        reject(new Error(err))
                    }
                } else {
                    reject(new Error('Error reading batch'))
                }
            });
            resolve(this.messageData);
        });
    }

    /**
     * Encrypting the index after the merging the index
     * to the main user index
     */
    encryptIndex() {
        return this.crypto.encryption().then(() => {
            return 'Success'
        }).catch((e) => {
            return (new Error(e));
        });
    }

    /**
     * This returns the search results
     * which returns a char *
     * @param {String} query
     * @param {Array} senderIds
     * @param {Array} threadIds
     * @param {String} fileType
     * @param {String} startDate
     * @param {String} endDate
     * @param {Number} limit
     * @param {Number} offset
     * @param {Number} sortOrder
     * @returns {Promise}
     */
    searchQuery(query, senderIds, threadIds, fileType, startDate,
                endDate, limit, offset, sortOrder) {

        let _limit = limit;
        let _offset = offset;
        let _sortOrder = sortOrder;

        return new Promise((resolve, reject) => {
            if (!this.isInitialized) {
                reject(new Error('Library not initialized'));
                return;
            }

            if (!fs.existsSync(this.indexFolderName) || !fs.existsSync(this.realTimeIndex)) {
                reject('Index folder does not exist.');
                return;
            }

            let q = Search.constructQuery(query, senderIds, threadIds, fileType);

            if (q === undefined) {
                reject(new Error('Search query error'));
                return;
            }

            let searchPeriod = new Date().getTime() - SEARCH_PERIOD_SUBTRACTOR;
            let sd_time = searchPeriod;
            if (startDate) {
                sd_time = new Date(parseInt(startDate, 10)).getTime();
                if (!sd_time) {
                    sd_time = searchPeriod;
                } else if (sd_time < searchPeriod) {
                    sd_time = searchPeriod;
                }
            }

            let ed_time = MAXIMUM_DATE;
            if (endDate) {
                let etime = new Date(parseInt(endDate, 10)).getTime();
                if (etime) {
                    ed_time = etime;
                }
            }

            if (!_limit && _limit === "" && typeof _limit !== 'number' && Math.round(_limit) !== _limit) {
                _limit = 25;
            }

            if (!_offset && _offset === "" && typeof _offset !== 'number' && Math.round(_offset) !== _offset) {
                _offset = 0
            }

            if (!_sortOrder && _sortOrder === "" && typeof _sortOrder !== 'number' && Math.round(_sortOrder) !== _sortOrder) {
                _sortOrder = SORT_BY_SCORE;
            }

            const returnedResult = libSymphonySearch.symSESearch(this.indexFolderName, this.realTimeIndex, q, sd_time.toString(), ed_time.toString(), _offset, _limit, _sortOrder);
            try {
                let ret = returnedResult.readCString();
                resolve(JSON.parse(ret));
            } finally {
                libSymphonySearch.symSEFreeResult(returnedResult);
            }
        });
    }

    /**
     * returns the latest message timestamp
     * from the indexed data
     * @returns {Promise}
     */
    getLatestMessageTimestamp() {
        return new Promise((resolve, reject) => {
            if (!this.isInitialized) {
                reject('Not initialized');
                return;
            }

            if (!fs.existsSync(this.indexFolderName)) {
                reject('Index folder does not exist.');
                return;
            }

            libSymphonySearch.symSEGetLastMessageTimestampAsync(this.indexFolderName, (err, res) => {
                if (err) {
                    reject(new Error(err));
                }
                const returnedResult = res;
                try {
                    let ret = returnedResult.readCString();
                    resolve(ret);
                } finally {
                    libSymphonySearch.symSEFreeResult(returnedResult);
                }
            });
        });
    }

    /**
     * This the query constructor
     * for the search function
     * @param {String} searchQuery
     * @param {Array} senderId
     * @param {Array} threadId
     * @param {String} fileType
     * @returns {string}
     */
    static constructQuery(searchQuery, senderId, threadId, fileType) {

        let searchText = "";
        if(searchQuery !== undefined) {
            searchText = searchQuery.trim().toLowerCase(); //to prevent injection of AND and ORs
        }
        let q = "";
        let hashTags = Search.getHashTags(searchText);
        let hashCashTagQuery = "";

        if(hashTags.length > 0) {
            hashCashTagQuery = " OR tags:(";
            hashTags.forEach((item) => {
                hashCashTagQuery = hashCashTagQuery + "\"" + item + "\" "
            });
            hashCashTagQuery += ")";
        }

        let hasAttachments = false;
        let additionalAttachmentQuery = "";
        if(fileType) {
            hasAttachments = true;
            if(fileType.toLowerCase() === "attachment") {
                additionalAttachmentQuery = "(hasfiles:true)";
            } else {
                additionalAttachmentQuery = "(filetype:(" + fileType +"))";
            }
        }

        if (searchText.length > 0 ) {
            q = "((text:(" + searchText + "))" + hashCashTagQuery ;
            if(hasAttachments) {
                q += " OR (filename:(" + searchText + "))" ;
            }
            q = q + ")";
        }

        q = Search.appendFilterQuery(q, "senderId", senderId);
        q = Search.appendFilterQuery(q, "threadId", threadId);

        if(q === "") {
            if(hasAttachments) {
                q = additionalAttachmentQuery;
            } else {
                q = undefined; //will be handled in the search function
            }
        } else {
            if(hasAttachments){
                q = q + " AND " + additionalAttachmentQuery
            }
        }
        return q;
    }

    /**
     * appending the senderId and threadId for the query
     * @param {String} searchText
     * @param {String} fieldName
     * @param {Array} valueArray
     * @returns {string}
     */
    static appendFilterQuery(searchText, fieldName, valueArray) {
        let q = "";
        if (valueArray && valueArray.length > 0 ) {

            q += "(" + fieldName +":(";
            valueArray.forEach((item)=>{
                q+= "\"" + item + "\" ";
            });
            q += "))";
            if(searchText.length > 0 ) {
                q = searchText + " AND " + q;
            }

        } else {
            q = searchText;
        }

        return q;
    }

    // hashtags can have any characters(before the latest release it was
    // not like this). So the only regex is splitting the search query based on
    // whitespaces
    /**
     * return the hash cash
     * tags from the query
     * @param {String} searchText
     * @returns {Array}
     */
    static getHashTags(searchText) {
        let hashTags = [];
        let tokens = searchText.toLowerCase()
            .trim()
            .replace(/\s\s+/g, ' ')
            .split(' ').filter((el) => {return el.length !== 0});
        tokens.forEach((item) => {
            if (item.startsWith('#') || item.startsWith('$')) {
                hashTags.push(item);
            }
        });
        return hashTags;
    }

    /**
     * Validate the index folder exist or not
     * @param {String} file
     * @returns {*}
     */
    static indexValidator(file) {
        let data;
        let result = childProcess.execFileSync(INDEX_VALIDATOR, [file]).toString();
        try {
            data = JSON.parse(result);
            if (data.status === 'OK') {
                return data;
            }
            return new Error('Unable validate index folder')
        } catch (err) {
            throw new Error(err);
        }
    }
}

/**
 * Exporting the search library
 * @type {{Search: Search}}
 */
module.exports = {
    Search: Search
};
<|endoftext|>


<commit_before>var Pentagine = (function() {
  function init() {
    lastUpdate = Date.now();

    if (desiredFPS) {
      var myInterval = setInterval(tick, 1 / desiredFPS * 1000);
    } else {
      setInterval(tick, 0);
    }
  }

  function tick() {
    var currentTime = Date.now();
    var dt = currentTime - lastUpdate;
    lastUpdate = currentTime;
    currentState.dt = dt * 0.001;

    currentState.update();
    currentState.draw();
  }

  window.addEventListener("keydown", handleKeyDown);
  window.addEventListener("keyup", handleKeyUp);

  var keyCodeToString = [];
  keyCodeToString[8] = "backspace";
  keyCodeToString[9] = "tab";
  keyCodeToString[13] = "return";
  keyCodeToString[16] = "shift";
  keyCodeToString[17] = "ctrl";
  keyCodeToString[18] = "alt";
  keyCodeToString[19] = "pause";
  keyCodeToString[20] = "capslock";
  keyCodeToString[27] = "escape";
  keyCodeToString[32] = "space";
  keyCodeToString[33] = "pageup";
  keyCodeToString[34] = "pagedown";
  keyCodeToString[35] = "end";
  keyCodeToString[36] = "home";
  keyCodeToString[37] = "left";
  keyCodeToString[38] = "up";
  keyCodeToString[39] = "right";
  keyCodeToString[40] = "down";
  keyCodeToString[45] = "insert";
  keyCodeToString[46] = "delete";

  keyCodeToString[91] = "leftwindowkey";
  keyCodeToString[92] = "rightwindowkey";
  keyCodeToString[93] = "selectkey";
  keyCodeToString[106] = "multiply";
  keyCodeToString[107] = "add";
  keyCodeToString[109] = "subtract";
  keyCodeToString[110] = "decimalpoint";
  keyCodeToString[111] = "divide";

  keyCodeToString[144] = "numlock";
  keyCodeToString[145] = "scrollock";
  keyCodeToString[186] = "semicolon";
  keyCodeToString[187] = "equalsign";
  keyCodeToString[188] = "comma";
  keyCodeToString[189] = "dash";
  keyCodeToString[190] = "period";
  keyCodeToString[191] = "forwardslash";
  keyCodeToString[192] = "graveaccent";
  keyCodeToString[219] = "openbracket";
  keyCodeToString[220] = "backslash";
  keyCodeToString[221] = "closebracket";
  keyCodeToString[222] = "singlequote";

  var numpadKeys = ["numpad1", "numpad2", "numpad3", "numpad4", "numpad5",
                    "numpad6", "numpad7", "numpad8", "numpad9"];

  var numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
  var letters = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
                 "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
                 "u", "v", "w", "x", "y", "z"];

  for (var i = 0; numbers[i]; i++) { keyCodeToString[48 + i] = numbers[i]; }
  for (var i = 0; letters[i]; i++) { keyCodeToString[65 + i] = letters[i]; }
  for (var i = 0; numpadKeys[i]; i++) { keyCodeToString[96 + i] = numpadKeys[i]; }

  var pressedKeys = [];
  var preventedKeys = [];

  function handleKeyDown(e) {
    var event = (e) ? e : window.event;
    var name = keyCodeToString[event.keyCode];
    pressedKeys[name] = true;

    if (preventedKeys[name])
      e.preventDefault();
  }

  function handleKeyUp(e) {
    var event = (e) ? e : window.event;
    var name = keyCodeToString[event.keyCode];
    pressedKeys[name] = false;

    if (preventedKeys[name]) {
      e.preventDefault();
    }
  }

  window.addEventListener("mousedown", handleMouseDown, false);
  window.addEventListener("mouseup", handleMouseUp, false);
  window.addEventListener("mousemove", handleMouseMove, false);

  window.addEventListener("touchstart", handleMouseDown, false);
  window.addEventListener("touchmove", handleMouseUp, false);

  var pressedButtons = [];
  var mouseX = null;
  var mouseY = null;

  var convertMouseButtonToString = [];
  convertMouseButtonToString[0] = "left";
  convertMouseButtonToString[1] = "center";
  convertMouseButtonToString[2] = "right";

  function handleMouseDown(e) {
    var event = (e) ? e : window.event;
    var humanName = convertMouseButtonToString[event.button];

    pressedButtons[humanName] = true;
  }

  function handleMouseUp(e) {
    var event = (e) ? e : window.event;
    var humanName = convertMouseButtonToString[event.button];

    pressedButtons[humanName] = false;
  }

  function handleMouseMove(e) {
    if (e.offsetX) {
      mouseX = e.offsetX;
      mouseY = e.offsetY;
    } else if (e.layerX) {
      mouseX = e.layerX;
      mouseY = e.layerY;
    }

    if (currentState != null) {
      mouseX += currentState.camera.x;
      mouseY += currentState.camera.y;
    }
  }

  window.addEventListener("touchend", handleMouseMove, false);

  function handleTouchMove(e) {
    var event = (e) ? e : window.event;
    e.stopPropagation();
    e.preventDefault();

    /* Tanslate to mouse event */
    var clickEvent = document.createEvent("MouseEvent");
    clickEvent.initMouseEvent("mousemove", true, true, window, e.detail,
                              e.touches[0].screenX, e.touches[0].screenY,
                              e.touches[0].clientX, e.touches[0].clientY,
                              false, false, false, false, 0, null);

    window.dispatchEvent(clickEvent);
  }

  var sharedCanvases = {};

  var currentState = null;
  var lastUpdate = null;
  var desiredFPS = null;

  var currentFont = "10px serif";

  var pauseKey = "p";
  var canPauseOrResume = true;
  var gamePaused = false;

  var context;

  function constructor() {

  }

  constructor.prototype = {
    setup: function() {
      /* Load the canvas */
      this.canvas = document.getElementById("canvas");
      this.context = null;

      if (typeof this.canvas !== "undefined") {
        this.context = this.canvas.getContext("2d");
        this.context.width = this.canvas.width;
        this.context.height = this.canvas.height;
      }

      return this;
    },

    drawCircle: function(x, y, radius, color) {
      this.context.fillStyle = color;
      this.context.beginPath();
      this.context.arc(x, y, radius, 0, Math.PI * 2, false);
      this.context.fill();
    },

    drawRectangle: function(x, y, width, height, color) {
      if (color) {
        this.context.fillStyle = color;
      }

      this.context.fillRect(x, y, width, height);
    },

    drawLine: function(x1, y1, x2, y2, color) {
      if (color) {
        this.context.fillStyle = color;
      }

      this.context.beginPath();
      this.context.moveTo(x1, y1);
      this.context.lineTo(x2, y2);
      this.context.stroke();
    },

    drawString: function(text, x, y, color, alignment) {
      if (!alignment) {
        this.context.textAlign = "left";
      } else {
        this.context.textAlign = alignment;
      }

      this.context.font = currentFont;
      this.context.fillStyle = color;
      this.context.fillText(text, x, y);
    },

    clearCanvas: function(backgroundColor) {
      this.context.clearRect(0, 0, this.context.width, this.context.height);

      if (backgroundColor != null) {
        this.drawRectangle(0, 0, this.context.width, this.context.height, backgroundColor);
      }
    },

    isOutsideOfScreen: function(x, y) {
      return (x < 0 || x > this.context.width || y < 0 || y > this.context.height);
    },

    SpriteList: (function() {
      function constructor() {
        this.sprites = [];
      }

      constructor.prototype = {
        draw: function() {
          for (var i = 0; i < this.sprites.length; i++) {
            if (this.sprites[i]) {
              this.sprites[i].draw();
            }
          }
        },

        push: function(newSprite) {
          this.sprites.push(newSprite);
        },

        remove: function(sprite) {
          var index = this.sprites.indexOf(sprite);
          this.sprites.splice(index, 1);
        },

        getLength: function() {
          return this.sprites.length;
        }
      };

      return constructor;
    }()),

    Camera: (function() {
      function constructor(x, y, width, height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
      }

      constructor.prototype = {
        follow: function() {
        }
      };

      return constructor;
    }()),

    preventKeys: function(keys) {
      preventedKeys = keys;
    },

    isDown: function(name) {
      if (pressedKeys[name]) {
        return true;
      }

      return false;
    },

    isMouseDown: function(name) {
      if (pressedButtons[name])
        return true;

      return false;
    },

    Sprite: (function() {
      function constructor(image, x, y) {
        this.x = x;
        this.y = y;
        this.alpha = 1;
        this.angle = 0;
        this.path = image;
        this.offset = { x: 0, y: 0 };
        this.context = penta.context;

        if (!image) {
          this.shared = true;
          this.loaded = true;
          console.log("Attempted to load null image.");
          return;
        }

        /* Try to retrieve a shared canvas instead of generating a new one */
        this.shared = true;
        if (this.path in sharedCanvases) {
          var shared = sharedCanvases[this.path];
          this.internal = shared[0];
          this.internalctx = shared[1];
          this.loaded = shared[2].loaded;

          if (!this.loaded) {
            this.pending = [];
            shared[3].push(this);
          }
        } else {
          this.image = new Image();
          this.image.src = image;
          this.image.owner = this;
          this.loaded = false;
          this.pending = [];

          /* Cache image modifications to an internal canvas for performance and flexibility */
          this.internal = document.createElement("canvas");
          this.internalctx = this.internal.getContext("2d");

          /* Save the canvas to the global shared canvas map */
          sharedCanvases[this.path] = [this.internal, this.internalctx, this, []];

          /* Asynchronous image loading and caching */
          this.image.onload = function() {
            this.owner.internal.width = this.width.toString();
            this.owner.internal.height = this.height.toString();
            this.owner.internalctx.drawImage(this, 0, 0);

            /* Dump image reference, we no longer need it */
            delete this.owner.image;
            this.owner.loaded = true;
            this.owner.dispatchPending();

            /* Set all dependencies to loaded */
            var deps = sharedCanvases[this.owner.path][3];
            for (var i = 0; i < deps.length; i++) {
              deps[i].loaded = true;
              deps[i].dispatchPending();
            }
          };
        }
      }

      constructor.prototype = {
        draw: function() {
          // TODO: how about caching rotated sprites on their internal canvas?
          if (this.angle) {
            this.context.save();
            // this.context.translate(this.x + this.offset.x - currentState.camera.x,
            // this.y + this.offset.y - currentState.camera.y);
            this.context.rotate(this.angle * degToRad);
            // this.context.translate(-(this.x + this.offset.x - currentState.camera.x),
            // -(this.y + this.offset.y - currentState.camera.y));
          }

          if (this.alpha != 1) {
            this.context.globalAlpha = this.alpha;
          }

          this.context.drawImage(this.internal, this.x - currentState.camera.x,
                                 this.y - currentState.camera.y);

          if (this.alpha != 1) {
            this.context.globalAlpha = 1;
          }

          if (this.angle) {
            this.context.restore();
          }
        },

        makeGraphic: function(width, height, color) {
          if (this.shared) {
            this.releaseShared();
          }

          var ws = width.toString();
          var hs = height.toString();

          if (ws != this.internal.width || hs != this.internal.height) {
            this.internal.width = ws;
            this.internal.height = hs;
          }

          this.stampRect(0, 0, width, height, color);
        },

        makeLabel: function(text, size, font, color) {
          if (this.shared) {
            this.releaseShared();
          }

          this.internalctx.font = size + "px " + font;
          var metrics = this.internalctx.measureText(text);
          // TODO: figure out actual height instead of 2 * size (see stampText TODO)
          this.makeGraphic(metrics.width, size * 2, "rgba(0, 0, 0, 0)");
          this.stampText(0, 0, text, size, font, color);
        },

        stampText: function(x, y, text, size, font, color) {
          // TODO: write small function to extract and cache ACTUAL font height
          if (!this.loaded) {
            this.pending.push([this.stampText, x, y, text, font, size, color]);
          } else {
            if (this.shared) {
              this.releaseShared();
            }

            this.internalctx.font = size + "px " + font;
            this.internalctx.textAlign = "left";
            this.internalctx.fillStyle = color;
            this.internalctx.fillText(text, x, y + size);
          }
        },

        stampRect: function(x, y, width, height, color) {
          if (!this.loaded) {
            this.pending.push([this.stampRect, x, y, width, height, color]);
          } else {
            if (this.shared) {
              this.releaseShared();
            }

            this.internalctx.fillStyle = color;
            this.internalctx.fillRect(x, y, width, height);
          }
        },

        stampImage: function(x, y, path) {
          if (!this.loaded) {
            this.pending.push([this.stampImage, x, y, path]);
          } else {
            if (this.shared) {
              this.releaseShared();
            }

            var image = new Image();
            image.src = path;
            image.sprite = this;
            image.target = { x: x, y: y };

            image.onload = function() {
              this.sprite.internalctx.drawImage(this, this.target.x, this.target.y);
            };
          }
        },

        dispatchPending: function() {
          /* Dispatch all pending sprite modifications */
          var pending = this.pending;
          for (var i = 0; i < pending.length; i++) {
            pending[i][0].apply(this, Array.prototype.slice.call(pending[i], 1));
          }

          delete this.pending;
        },

        releaseShared: function() {
          /* Stop using the shared version of the internal canvas, we probably
           * need a dinamically modified sprite */
          var newInternal = document.createElement("canvas");
          this.internalctx = newInternal.getContext("2d");

          if (this.path) {
            newInternal.width = this.internal.width.toString();
            newInternal.height = this.internal.height.toString();
            this.internalctx.drawImage(sharedCanvases[this.path][0], 0, 0);
          }

          this.internal = newInternal;
          this.shared = false;
        }
      };

      return constructor;
    })(),

    switchState: function(newState) {
      if (!currentState) {
        currentState = newState;
        init();
      }

      if (!newState.camera) {
        newState.camera = new this.Camera(0, 0, this.context.width, this.context.height);
      }

      newState.setup();
      return currentState = newState;
    }
  };

  return constructor;
}());

var penta = new Pentagine().setup();
<commit_msg>Cleans up framework pattern structure.<commit_after>var penta = (function() {
  function init() {
    lastUpdate = Date.now();

    if (desiredFPS) {
      var myInterval = setInterval(tick, 1 / desiredFPS * 1000);
    } else {
      setInterval(tick, 0);
    }
  }

  function tick() {
    var currentTime = Date.now();
    var dt = currentTime - lastUpdate;
    lastUpdate = currentTime;
    currentState.dt = dt * 0.001;

    currentState.update();
    currentState.draw();
  }

  window.addEventListener("keydown", handleKeyDown);
  window.addEventListener("keyup", handleKeyUp);

  var keyCodeToString = [];
  keyCodeToString[8] = "backspace";
  keyCodeToString[9] = "tab";
  keyCodeToString[13] = "return";
  keyCodeToString[16] = "shift";
  keyCodeToString[17] = "ctrl";
  keyCodeToString[18] = "alt";
  keyCodeToString[19] = "pause";
  keyCodeToString[20] = "capslock";
  keyCodeToString[27] = "escape";
  keyCodeToString[32] = "space";
  keyCodeToString[33] = "pageup";
  keyCodeToString[34] = "pagedown";
  keyCodeToString[35] = "end";
  keyCodeToString[36] = "home";
  keyCodeToString[37] = "left";
  keyCodeToString[38] = "up";
  keyCodeToString[39] = "right";
  keyCodeToString[40] = "down";
  keyCodeToString[45] = "insert";
  keyCodeToString[46] = "delete";

  keyCodeToString[91] = "leftwindowkey";
  keyCodeToString[92] = "rightwindowkey";
  keyCodeToString[93] = "selectkey";
  keyCodeToString[106] = "multiply";
  keyCodeToString[107] = "add";
  keyCodeToString[109] = "subtract";
  keyCodeToString[110] = "decimalpoint";
  keyCodeToString[111] = "divide";

  keyCodeToString[144] = "numlock";
  keyCodeToString[145] = "scrollock";
  keyCodeToString[186] = "semicolon";
  keyCodeToString[187] = "equalsign";
  keyCodeToString[188] = "comma";
  keyCodeToString[189] = "dash";
  keyCodeToString[190] = "period";
  keyCodeToString[191] = "forwardslash";
  keyCodeToString[192] = "graveaccent";
  keyCodeToString[219] = "openbracket";
  keyCodeToString[220] = "backslash";
  keyCodeToString[221] = "closebracket";
  keyCodeToString[222] = "singlequote";

  var numpadKeys = ["numpad1", "numpad2", "numpad3", "numpad4", "numpad5",
                    "numpad6", "numpad7", "numpad8", "numpad9"];

  var numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
  var letters = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
                 "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
                 "u", "v", "w", "x", "y", "z"];

  for (var i = 0; numbers[i]; i++) { keyCodeToString[48 + i] = numbers[i]; }
  for (var i = 0; letters[i]; i++) { keyCodeToString[65 + i] = letters[i]; }
  for (var i = 0; numpadKeys[i]; i++) { keyCodeToString[96 + i] = numpadKeys[i]; }

  var pressedKeys = [];
  var preventedKeys = [];

  function handleKeyDown(e) {
    var event = (e) ? e : window.event;
    var name = keyCodeToString[event.keyCode];
    pressedKeys[name] = true;

    if (preventedKeys[name])
      e.preventDefault();
  }

  function handleKeyUp(e) {
    var event = (e) ? e : window.event;
    var name = keyCodeToString[event.keyCode];
    pressedKeys[name] = false;

    if (preventedKeys[name]) {
      e.preventDefault();
    }
  }

  window.addEventListener("mousedown", handleMouseDown, false);
  window.addEventListener("mouseup", handleMouseUp, false);
  window.addEventListener("mousemove", handleMouseMove, false);

  window.addEventListener("touchstart", handleMouseDown, false);
  window.addEventListener("touchmove", handleMouseUp, false);

  var pressedButtons = [];
  var mouseX = null;
  var mouseY = null;

  var convertMouseButtonToString = [];
  convertMouseButtonToString[0] = "left";
  convertMouseButtonToString[1] = "center";
  convertMouseButtonToString[2] = "right";

  function handleMouseDown(e) {
    var event = (e) ? e : window.event;
    var humanName = convertMouseButtonToString[event.button];

    pressedButtons[humanName] = true;
  }

  function handleMouseUp(e) {
    var event = (e) ? e : window.event;
    var humanName = convertMouseButtonToString[event.button];

    pressedButtons[humanName] = false;
  }

  function handleMouseMove(e) {
    if (e.offsetX) {
      mouseX = e.offsetX;
      mouseY = e.offsetY;
    } else if (e.layerX) {
      mouseX = e.layerX;
      mouseY = e.layerY;
    }

    if (currentState != null) {
      mouseX += currentState.camera.x;
      mouseY += currentState.camera.y;
    }
  }

  window.addEventListener("touchend", handleMouseMove, false);

  function handleTouchMove(e) {
    var event = (e) ? e : window.event;
    e.stopPropagation();
    e.preventDefault();

    /* Tanslate to mouse event */
    var clickEvent = document.createEvent("MouseEvent");
    clickEvent.initMouseEvent("mousemove", true, true, window, e.detail,
                              e.touches[0].screenX, e.touches[0].screenY,
                              e.touches[0].clientX, e.touches[0].clientY,
                              false, false, false, false, 0, null);

    window.dispatchEvent(clickEvent);
  }

  var sharedCanvases = {};

  var currentState = null;
  var lastUpdate = null;
  var desiredFPS = null;

  var currentFont = "10px serif";

  var pauseKey = "p";
  var canPauseOrResume = true;
  var gamePaused = false;

  var context;

  return {
    setup: function() {
      /* Load the canvas */
      this.canvas = document.getElementById("canvas");
      this.context = null;

      if (typeof this.canvas !== "undefined") {
        this.context = this.canvas.getContext("2d");
        this.context.width = this.canvas.width;
        this.context.height = this.canvas.height;
      }

      return this;
    },

    drawCircle: function(x, y, radius, color) {
      this.context.fillStyle = color;
      this.context.beginPath();
      this.context.arc(x, y, radius, 0, Math.PI * 2, false);
      this.context.fill();
    },

    drawRectangle: function(x, y, width, height, color) {
      if (color) {
        this.context.fillStyle = color;
      }

      this.context.fillRect(x, y, width, height);
    },

    drawLine: function(x1, y1, x2, y2, color) {
      if (color) {
        this.context.fillStyle = color;
      }

      this.context.beginPath();
      this.context.moveTo(x1, y1);
      this.context.lineTo(x2, y2);
      this.context.stroke();
    },

    drawString: function(text, x, y, color, alignment) {
      if (!alignment) {
        this.context.textAlign = "left";
      } else {
        this.context.textAlign = alignment;
      }

      this.context.font = currentFont;
      this.context.fillStyle = color;
      this.context.fillText(text, x, y);
    },

    clearCanvas: function(backgroundColor) {
      this.context.clearRect(0, 0, this.context.width, this.context.height);

      if (backgroundColor != null) {
        this.drawRectangle(0, 0, this.context.width, this.context.height, backgroundColor);
      }
    },

    isOutsideOfScreen: function(x, y) {
      return (x < 0 || x > this.context.width || y < 0 || y > this.context.height);
    },

    SpriteList: (function() {
      function constructor() {
        this.sprites = [];
      }

      constructor.prototype = {
        draw: function() {
          for (var i = 0; i < this.sprites.length; i++) {
            if (this.sprites[i]) {
              this.sprites[i].draw();
            }
          }
        },

        push: function(newSprite) {
          this.sprites.push(newSprite);
        },

        remove: function(sprite) {
          var index = this.sprites.indexOf(sprite);
          this.sprites.splice(index, 1);
        },

        getLength: function() {
          return this.sprites.length;
        }
      };

      return constructor;
    }()),

    Camera: (function() {
      function constructor(x, y, width, height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
      }

      constructor.prototype = {
        follow: function() {
        }
      };

      return constructor;
    }()),

    preventKeys: function(keys) {
      preventedKeys = keys;
    },

    isDown: function(name) {
      if (pressedKeys[name]) {
        return true;
      }

      return false;
    },

    isMouseDown: function(name) {
      if (pressedButtons[name])
        return true;

      return false;
    },

    Sprite: (function() {
      function constructor(image, x, y) {
        this.x = x;
        this.y = y;
        this.alpha = 1;
        this.angle = 0;
        this.path = image;
        this.offset = { x: 0, y: 0 };
        this.context = penta.context;

        if (!image) {
          this.shared = true;
          this.loaded = true;
          console.log("Attempted to load null image.");
          return;
        }

        /* Try to retrieve a shared canvas instead of generating a new one */
        this.shared = true;
        if (this.path in sharedCanvases) {
          var shared = sharedCanvases[this.path];
          this.internal = shared[0];
          this.internalctx = shared[1];
          this.loaded = shared[2].loaded;

          if (!this.loaded) {
            this.pending = [];
            shared[3].push(this);
          }
        } else {
          this.image = new Image();
          this.image.src = image;
          this.image.owner = this;
          this.loaded = false;
          this.pending = [];

          /* Cache image modifications to an internal canvas for performance and flexibility */
          this.internal = document.createElement("canvas");
          this.internalctx = this.internal.getContext("2d");

          /* Save the canvas to the global shared canvas map */
          sharedCanvases[this.path] = [this.internal, this.internalctx, this, []];

          /* Asynchronous image loading and caching */
          this.image.onload = function() {
            this.owner.internal.width = this.width.toString();
            this.owner.internal.height = this.height.toString();
            this.owner.internalctx.drawImage(this, 0, 0);

            /* Dump image reference, we no longer need it */
            delete this.owner.image;
            this.owner.loaded = true;
            this.owner.dispatchPending();

            /* Set all dependencies to loaded */
            var deps = sharedCanvases[this.owner.path][3];
            for (var i = 0; i < deps.length; i++) {
              deps[i].loaded = true;
              deps[i].dispatchPending();
            }
          };
        }
      }

      constructor.prototype = {
        draw: function() {
          // TODO: how about caching rotated sprites on their internal canvas?
          if (this.angle) {
            this.context.save();
            // this.context.translate(this.x + this.offset.x - currentState.camera.x,
            // this.y + this.offset.y - currentState.camera.y);
            this.context.rotate(this.angle * degToRad);
            // this.context.translate(-(this.x + this.offset.x - currentState.camera.x),
            // -(this.y + this.offset.y - currentState.camera.y));
          }

          if (this.alpha != 1) {
            this.context.globalAlpha = this.alpha;
          }

          this.context.drawImage(this.internal, this.x - currentState.camera.x,
                                 this.y - currentState.camera.y);

          if (this.alpha != 1) {
            this.context.globalAlpha = 1;
          }

          if (this.angle) {
            this.context.restore();
          }
        },

        makeGraphic: function(width, height, color) {
          if (this.shared) {
            this.releaseShared();
          }

          var ws = width.toString();
          var hs = height.toString();

          if (ws != this.internal.width || hs != this.internal.height) {
            this.internal.width = ws;
            this.internal.height = hs;
          }

          this.stampRect(0, 0, width, height, color);
        },

        makeLabel: function(text, size, font, color) {
          if (this.shared) {
            this.releaseShared();
          }

          this.internalctx.font = size + "px " + font;
          var metrics = this.internalctx.measureText(text);
          // TODO: figure out actual height instead of 2 * size (see stampText TODO)
          this.makeGraphic(metrics.width, size * 2, "rgba(0, 0, 0, 0)");
          this.stampText(0, 0, text, size, font, color);
        },

        stampText: function(x, y, text, size, font, color) {
          // TODO: write small function to extract and cache ACTUAL font height
          if (!this.loaded) {
            this.pending.push([this.stampText, x, y, text, font, size, color]);
          } else {
            if (this.shared) {
              this.releaseShared();
            }

            this.internalctx.font = size + "px " + font;
            this.internalctx.textAlign = "left";
            this.internalctx.fillStyle = color;
            this.internalctx.fillText(text, x, y + size);
          }
        },

        stampRect: function(x, y, width, height, color) {
          if (!this.loaded) {
            this.pending.push([this.stampRect, x, y, width, height, color]);
          } else {
            if (this.shared) {
              this.releaseShared();
            }

            this.internalctx.fillStyle = color;
            this.internalctx.fillRect(x, y, width, height);
          }
        },

        stampImage: function(x, y, path) {
          if (!this.loaded) {
            this.pending.push([this.stampImage, x, y, path]);
          } else {
            if (this.shared) {
              this.releaseShared();
            }

            var image = new Image();
            image.src = path;
            image.sprite = this;
            image.target = { x: x, y: y };

            image.onload = function() {
              this.sprite.internalctx.drawImage(this, this.target.x, this.target.y);
            };
          }
        },

        dispatchPending: function() {
          /* Dispatch all pending sprite modifications */
          var pending = this.pending;
          for (var i = 0; i < pending.length; i++) {
            pending[i][0].apply(this, Array.prototype.slice.call(pending[i], 1));
          }

          delete this.pending;
        },

        releaseShared: function() {
          /* Stop using the shared version of the internal canvas, we probably
           * need a dinamically modified sprite */
          var newInternal = document.createElement("canvas");
          this.internalctx = newInternal.getContext("2d");

          if (this.path) {
            newInternal.width = this.internal.width.toString();
            newInternal.height = this.internal.height.toString();
            this.internalctx.drawImage(sharedCanvases[this.path][0], 0, 0);
          }

          this.internal = newInternal;
          this.shared = false;
        }
      };

      return constructor;
    })(),

    switchState: function(newState) {
      if (!currentState) {
        currentState = newState;
        init();
      }

      if (!newState.camera) {
        newState.camera = new this.Camera(0, 0, this.context.width, this.context.height);
      }

      newState.setup();
      return currentState = newState;
    }
  };
}()).setup();
<|endoftext|>


<commit_before>function countdown(){
  var myTime = new Date();
  var end = new Date(2015, 7, 3);
  var count = end - myTime;
  var timeleft = new Date(count);
  var days = timeleft.getDate();
  var hours = timeleft.getUTCHours();
  var min = timeleft.getMinutes();
  var sec = timeleft.getSeconds();
  var myText = [days, hours, min, sec].join(":");
  document.getElementById('timer').innerHTML = myText;
};

function count(){
  setInterval(function() {
    var myTime = new Date();
    var end = new Date(2015, 7, 3);
    var count = end - myTime;
    var timeleft = new Date(count);
    var days = paddnum(timeleft.getDate());
    var hours = paddnum(timeleft.getUTCHours());
    var min = paddnum(timeleft.getMinutes());
    var sec = paddnum(timeleft.getSeconds());
    var myText = [days, hours, min, sec].join(":");
    var my_sentence = ["Countdown to Onsite ", myText].join("- ")
    document.getElementById('timer').innerHTML = my_sentence;
  }, 1000);
};
function paddnum(num){

  if (num < 9){
    num = ["0", num].join('');
  }
  return num;
};
<commit_msg>fix Timer<commit_after>function countdown(){
  var myTime = new Date();
  var end = new Date(2015, 6, 27);
  var count = end - myTime;
  var timeleft = new Date(count);
  var days = timeleft.getDate();
  var hours = timeleft.getUTCHours();
  var min = timeleft.getMinutes();
  var sec = timeleft.getSeconds();
  var myText = [days, hours, min, sec].join(":");
  document.getElementById('timer').innerHTML = myText;
};

function count(){
  setInterval(function() {
    var myTime = new Date();
    var end = new Date(2015, 6, 27);
    var count = end - myTime;
    var timeleft = new Date(count);
    var days = paddnum(timeleft.getDate());
    var hours = paddnum(timeleft.getUTCHours());
    var min = paddnum(timeleft.getMinutes());
    var sec = paddnum(timeleft.getSeconds());
    var myText = [days, hours, min, sec].join(":");
    var my_sentence = ["Countdown to Onsite ", myText].join("- ")
    document.getElementById('timer').innerHTML = my_sentence;
  }, 1000);
};
function paddnum(num){

  if (num < 9){
    num = ["0", num].join('');
  }
  return num;
};
<|endoftext|>


<commit_before>// Following function takes a numebrs and returns a rounded off number
function toInteger(number){
  return Math.round(  // round to nearest integer
    Number(number)    // type cast your input
  );
};

// Following function calculates slope between two points a certain distance
function slopeCalc (){
  // Taking input
  var a = document.getElementById('pointA').valueAsNumber;
  var b = document.getElementById('pointB').valueAsNumber;
  var distance = document.getElementById('distance').valueAsNumber;
  // Calculating slope
  var result = toInteger(distance / (b - a));

  document.getElementById('slopeResult').innerHTML = "The slope is 1: " + result;
}

// following function calculates the level of the second point based on the level of the first point, slope to be maintained, and the distance of the second point from the first point
function levelOfSecond(){
  // Taking inputs
  var a = document.getElementById('firstPoint').valueAsNumber;
  var distance = document.getElementById('distanceToSecond').valueAsNumber;
  var slope = document.getElementById('slope').valueAsNumber;

// If "up" is selected, provide point higher than the first point
  if (document.getElementById('up').checked == true) {
    var result = toInteger(a + (distance / slope));
    output = "The level of second point is " + result;
  }

// If "down" is selected, provide point lower than the first point
  if (document.getElementById('down').checked == true) {
    var result = toInteger(a - (distance / slope));
    output = "The level of second point is " + result;
  }

// If neither "up" or "down" is selected, give a message
  if (document.getElementById('up').checked != true && document.getElementById('down').checked != true) {
  output = "Please select either \"up\" or \"Down\""
  }
// Send output back to HTML
  document.getElementById('levelOfSecond').innerHTML = output;
}
<commit_msg>Round to Float<commit_after>// Following function takes a numebrs and returns a rounded off number
function toInteger(number){
  // Making float to show 2 decimal places
  return parseFloat(number).toFixed(2);
};

// Following function calculates slope between two points a certain distance
function slopeCalc (){
  // Taking input
  var a = document.getElementById('pointA').valueAsNumber;
  var b = document.getElementById('pointB').valueAsNumber;
  var distance = document.getElementById('distance').valueAsNumber;
  // Calculating slope
  var result = toInteger(distance / (b - a));

  document.getElementById('slopeResult').innerHTML = "The slope is 1: " + result;
}

// following function calculates the level of the second point based on the level of the first point, slope to be maintained, and the distance of the second point from the first point
function levelOfSecond(){
  // Taking inputs
  var a = document.getElementById('firstPoint').valueAsNumber;
  var distance = document.getElementById('distanceToSecond').valueAsNumber;
  var slope = document.getElementById('slope').valueAsNumber;

// If "up" is selected, provide point higher than the first point
  if (document.getElementById('up').checked == true) {
    var result = toInteger(a + (distance / slope));
    output = "The level of second point is " + result;
  }

// If "down" is selected, provide point lower than the first point
  if (document.getElementById('down').checked == true) {
    var result = toInteger(a - (distance / slope));
    output = "The level of second point is " + result;
  }

// If neither "up" or "down" is selected, give a message
  if (document.getElementById('up').checked != true && document.getElementById('down').checked != true) {
  output = "Please select either \"up\" or \"Down\""
  }
// Send output back to HTML
  document.getElementById('levelOfSecond').innerHTML = output;
}
<|endoftext|>


<commit_before>var semver = require('semver');

module.exports = {
    win32: {
        needsZip: true,
        getRunnable: function() { return 'nw.exe'; },
        files: { // First file must be the executable
            '<=0.9.2': ['nw.exe', 'ffmpegsumo.dll', 'icudt.dll', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak'],
            '>0.9.2': ['nw.exe', 'ffmpegsumo.dll', 'icudtl.dat', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak', 'locales'],
            '>=0.12.0': ['nw.exe', 'ffmpegsumo.dll', 'icudtl.dat', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak', 'locales', 'd3dcompiler_47.dll', 'pdf.dll']
        },
        versionNameTemplate: 'v${ version }/${ name }-v${ version }-win-ia32.zip'
    },
    win64: {
        needsZip: true,
        getRunnable: function() { return 'nw.exe'; },
        files: { // First file must be the executable
            '<=0.9.2': ['nw.exe', 'ffmpegsumo.dll', 'icudt.dll', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak', 'locales'],
            '>0.9.2': ['nw.exe', 'ffmpegsumo.dll', 'icudtl.dat', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak', 'locales'],
            '>=0.12.0': ['nw.exe', 'ffmpegsumo.dll', 'icudtl.dat', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak', 'locales', 'd3dcompiler_47.dll', 'pdf.dll']
        },
        versionNameTemplate: 'v${ version }/${ name }-v${ version }-win-x64.zip'
    },
    osx32: {
        getRunnable: function(version) {
            if(semver.satisfies(version, '>=0.12.0 || ~0.12.0-alpha')) {
                return 'nwjs.app/Contents/MacOS/nwjs';
            } else {
                return 'node-webkit.app/Contents/MacOS/node-webkit';
            }
        },
        files: {
            '<0.12.0-alpha': ['node-webkit.app'],
            '>=0.12.0 || ~0.12.0-alpha': ['nwjs.app']
        },
        versionNameTemplate: 'v${ version }/${ name }-v${ version }-osx-ia32.zip'
    },
    osx64: {
        getRunnable: function(version) {
            if(semver.satisfies(version, '>=0.12.0 || ~0.12.0-alpha')) {
                return 'nwjs.app/Contents/MacOS/nwjs';
            } else {
                return 'node-webkit.app/Contents/MacOS/node-webkit';
            }
        },
        files: {
            '<0.12.0-alpha': ['node-webkit.app'],
            '>=0.12.0 || ~0.12.0-alpha': ['nwjs.app']
        },
        versionNameTemplate: 'v${ version }/${ name }-v${ version }-osx-x64.zip'
    },
    linux32: {
        needsZip: true,
        chmod: '0755',
        getRunnable: function() { return 'nw'; },
        files: { // First file must be the executable
            '<=0.9.2': ['nw', 'nw.pak', 'libffmpegsumo.so'],
            '>0.9.2 <=0.10.1': ['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat'],
            '>0.10.1':         ['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat', 'locales']
        },
        versionNameTemplate: 'v${ version }/${ name }-v${ version }-linux-ia32.tar.gz'
    },
    linux64: {
        needsZip: true,
        chmod: '0755', // chmod file file to be executable
        getRunnable: function() { return 'nw'; },
        files: { // First file must be the executable
            '<=0.9.2': ['nw', 'nw.pak', 'libffmpegsumo.so'],
            '>0.9.2 <=0.10.1': ['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat'],
            '>0.10.1':         ['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat', 'locales']
        },
        versionNameTemplate: 'v${ version }/${ name }-v${ version }-linux-x64.tar.gz'
    }
};
<commit_msg>put upper bound to semver check for windows<commit_after>var semver = require('semver');

module.exports = {
    win32: {
        needsZip: true,
        getRunnable: function() { return 'nw.exe'; },
        files: { // First file must be the executable
            '<=0.9.2': ['nw.exe', 'ffmpegsumo.dll', 'icudt.dll', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak'],
            '>0.9.2 <0.12.0': ['nw.exe', 'ffmpegsumo.dll', 'icudtl.dat', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak', 'locales'],
            '>=0.12.0': ['nw.exe', 'ffmpegsumo.dll', 'icudtl.dat', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak', 'locales', 'd3dcompiler_47.dll', 'pdf.dll']
        },
        versionNameTemplate: 'v${ version }/${ name }-v${ version }-win-ia32.zip'
    },
    win64: {
        needsZip: true,
        getRunnable: function() { return 'nw.exe'; },
        files: { // First file must be the executable
            '<=0.9.2': ['nw.exe', 'ffmpegsumo.dll', 'icudt.dll', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak', 'locales'],
            '>0.9.2 <0.12.0': ['nw.exe', 'ffmpegsumo.dll', 'icudtl.dat', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak', 'locales'],
            '>=0.12.0': ['nw.exe', 'ffmpegsumo.dll', 'icudtl.dat', 'libEGL.dll', 'libGLESv2.dll', 'nw.pak', 'locales', 'd3dcompiler_47.dll', 'pdf.dll']
        },
        versionNameTemplate: 'v${ version }/${ name }-v${ version }-win-x64.zip'
    },
    osx32: {
        getRunnable: function(version) {
            if(semver.satisfies(version, '>=0.12.0 || ~0.12.0-alpha')) {
                return 'nwjs.app/Contents/MacOS/nwjs';
            } else {
                return 'node-webkit.app/Contents/MacOS/node-webkit';
            }
        },
        files: {
            '<0.12.0-alpha': ['node-webkit.app'],
            '>=0.12.0 || ~0.12.0-alpha': ['nwjs.app']
        },
        versionNameTemplate: 'v${ version }/${ name }-v${ version }-osx-ia32.zip'
    },
    osx64: {
        getRunnable: function(version) {
            if(semver.satisfies(version, '>=0.12.0 || ~0.12.0-alpha')) {
                return 'nwjs.app/Contents/MacOS/nwjs';
            } else {
                return 'node-webkit.app/Contents/MacOS/node-webkit';
            }
        },
        files: {
            '<0.12.0-alpha': ['node-webkit.app'],
            '>=0.12.0 || ~0.12.0-alpha': ['nwjs.app']
        },
        versionNameTemplate: 'v${ version }/${ name }-v${ version }-osx-x64.zip'
    },
    linux32: {
        needsZip: true,
        chmod: '0755',
        getRunnable: function() { return 'nw'; },
        files: { // First file must be the executable
            '<=0.9.2': ['nw', 'nw.pak', 'libffmpegsumo.so'],
            '>0.9.2 <=0.10.1': ['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat'],
            '>0.10.1':         ['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat', 'locales']
        },
        versionNameTemplate: 'v${ version }/${ name }-v${ version }-linux-ia32.tar.gz'
    },
    linux64: {
        needsZip: true,
        chmod: '0755', // chmod file file to be executable
        getRunnable: function() { return 'nw'; },
        files: { // First file must be the executable
            '<=0.9.2': ['nw', 'nw.pak', 'libffmpegsumo.so'],
            '>0.9.2 <=0.10.1': ['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat'],
            '>0.10.1':         ['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat', 'locales']
        },
        versionNameTemplate: 'v${ version }/${ name }-v${ version }-linux-x64.tar.gz'
    }
};
<|endoftext|>