<commit_before>var path = require('path'),
    fs   = require('fs'),
    et   = require('elementtree'),
    n    = require('ncallbacks'),
    action_stack = require('./util/action-stack'),
    shell = require('shelljs'),
    semver = require('semver'),
    config_changes = require('./util/config-changes'),
    xml_helpers = require('./util/xml-helpers'),
    platform_modules = require('./platforms');

/* INSTALL FLOW
   ------------
   There are four functions install "flows" through. Here is an attempt at
   providing a high-level logic flow overview.
   1. module.exports (installPlugin)
     a) checks that the platform is supported
     b) invokes possiblyFetch
   2. possiblyFetch
     a) checks that the plugin is fetched. if so, calls runInstall
     b) if not, invokes plugman.fetch, and when done, calls runInstall
   3. runInstall
     a) checks if the plugin is already installed. if so, calls back (done).
     b) if possible, will check the version of the project and make sure it is compatible with the plugin (checks <engine> tags)
     c) makes sure that any variables required by the plugin are specified. if they are not specified, plugman will throw or callback with an error.
     d) if dependencies are listed in the plugin, it will recurse for each dependent plugin and call possiblyFetch (2) on each one. When each dependent plugin is successfully installed, it will then proceed to call handleInstall (4)
   4. handleInstall
     a) queues up actions into a queue (asset, source-file, headers, etc)
     b) processes the queue
     c) calls back (done)
*/

// possible options: subdir, cli_variables, www_dir
module.exports = function installPlugin(platform, project_dir, id, plugins_dir, options, callback) {
    if (!platform_modules[platform]) {
        var err = new Error(platform + " not supported.");
        if (callback) return callback(err);
        else throw err;
    }

    var current_stack = new action_stack();

    options.is_top_level = true;
    possiblyFetch(current_stack, platform, project_dir, id, plugins_dir, options, callback);
};

// possible options: subdir, cli_variables, www_dir, git_ref, is_top_level
function possiblyFetch(actions, platform, project_dir, id, plugins_dir, options, callback) {
    var plugin_dir = path.join(plugins_dir, id);

    // Check that the plugin has already been fetched.
    if (!fs.existsSync(plugin_dir)) {
        // if plugin doesnt exist, use fetch to get it.
        require('../plugman').fetch(id, plugins_dir, { link: false, subdir: options.subdir, git_ref: options.git_ref }, function(err, plugin_dir) {
            if (err) {
                if (callback) callback(err);
                else throw err;
            } else {
                // update ref to plugin_dir after successful fetch, via fetch callback
                runInstall(actions, platform, project_dir, plugin_dir, plugins_dir, options, callback);
            }
        });
    } else {
        runInstall(actions, platform, project_dir, plugin_dir, plugins_dir, options, callback);
    }
}

function checkMinimumReq(currentProjectInfo, minRequirements, platform, callback) {
    if(currentProjectInfo.cordovaVersion) {
        if(currentProjectInfo.cordovaVersion == 'dev' || semver.satisfies(currentProjectInfo.cordovaVersion, minRequirements.cordovaMinVersion)){
            // engine ok!
        } else {
            var err = new Error('Plugin doesn\'t support this project\'s Cordova version. Project version: ' + currentProjectInfo.cordovaVersion + ', failed version requirement: ' + minRequirements.cordovaMinVersion);
            if (callback) return callback(err);
            else throw err;
        }   
    }
    
    if(currentProjectInfo.min_os_version) {
        if(semver.satisfies(currentProjectInfo.min_os_version, minRequirements.platformMinOS)) {
            // min-os version ok
        } else {
            var err = new Error('Plugin doesn\'t support ' + platform + '  minimum os version.  ' + platform + '  minimum os version: ' + currentProjectInfo.min_os_version + ', failed version requirement: ' + minRequirements.platformMinOS);
            if (callback) return callback(err);
            else throw err;
        }            
    }

    if(currentProjectInfo.min_sdk_version) {
        if(semver.satisfies(currentProjectInfo.min_sdk_version, minRequirements.platformMinSDK)) {
            // min-sdk version ok
        } else {
            var err = new Error('Plugin doesn\'t support ' + platform + '  minimum sdk version.  ' + platform + '  minimum sdk version: ' + currentProjectInfo.min_sdk_version + ', failed version requirement: ' + minRequirements.platformMinSDK);
            if (callback) return callback(err);
            else throw err;
        }                        
    }     
}

// use the project level cordova scripts to get the current cordova version and platform information
function getCurrentProjectVersion(versionPath, platformPath) {
    // need to think about a more graceful way of handling when these scripts break or when they are not detected
    // setting version/platform to null if they do fail or do not exist and then just continuing
    
    var cordovaVersion, platformInfo;
    
    fs.chmodSync(versionPath, '755');
    fs.chmodSync(platformPath, '755');

    if (fs.existsSync(versionPath)) {   
        var versionScript = shell.exec(versionPath, {silent: true});
        if (versionScript.code === 0) {
            cordovaVersion = versionScript.output.trim();
            var rc_index = cordovaVersion.indexOf('rc');
            if (rc_index > -1) {
                cordovaVersion = cordovaVersion.substr(0, rc_index) + '-' + cordovaVersion.substr(rc_index);
            }
        }else{
            cordovaVersion = null;
            require('../plugman').emit('log', 'Cordova project version script failed (has a ./cordova/version script, but something went wrong executing it), continuing anyways.');
        }
    }else{
        cordovaVersion = null;
        require('../plugman').emit('log', 'Cordova project version not detected (lacks a ./cordova/version script), continuing.');
    }  
    
    if (fs.existsSync(platformPath)) {   
        var platformScript = shell.exec(platformPath, {silent: true});
        if (platformScript.code === 0) {
            // thinking platformScript.output would be a JSON string like:
            // { min_os_version: "5.0.0" , min_sdk_version: "1.0.0" }
            platformInfo = platformScript.output;
        }else{
            platformInfo =  null;
            require('../plugman').emit('log', 'Cordova project platformReq script failed (has a ./cordova/platformReq script, but something went wrong executing it), continuing anyways.');
        }
    }else{  
        platformInfo =  null;
        require('../plugman').emit('log', 'Cordova project version not detected (lacks a ./cordova/version script), continuing.');
    }     
    
    return { 'cordovaVersion' : cordovaVersion, 'platformInfo' : platformInfo };
}

function getMinReq(pluginElement, platform){

    var cordovaMinVersion, platformMinOS, platformMinSDK;
    
    var engines = pluginElement.findall('engines/engine');
    engines.forEach(function(engine){
                if(engine.attrib["name"].toLowerCase() === "cordova"){
                    cordovaMinVersion = engine.attrib["version"];
                }else{
                    // check for other engines - if found, shove the info into return statement 
                }
            });
    
    
    platformMinOS = pluginElement.findall('./platform[@name="'+platform+'"][@min-os-version]');
    platformMinOS = platformMinOS[0] ? platformMinOS[0].attrib["min-os-version"] : null

    platformMinSDK = pluginElement.findall('./platform[@name="'+platform+'"][@min-sdk-version]');
    platformMinSDK = platformMinSDK[0] ? platformMinSDK[0].attrib["min-sdk-version"] : null;
    
    return { 'cordovaMinVersion': cordovaMinVersion, 'platformMinOS' : platformMinOS, 'platformMinSDK' : platformMinSDK };
}


// possible options: cli_variables, www_dir, is_top_level
function runInstall(actions, platform, project_dir, plugin_dir, plugins_dir, options, callback) {
    var xml_path     = path.join(plugin_dir, 'plugin.xml')
      , plugin_et    = xml_helpers.parseElementtreeSync(xml_path)
      , filtered_variables = {};
    var name         = plugin_et.findall('name').text;
    var plugin_id    = plugin_et.getroot().attrib['id'];
    require('../plugman').emit('log', 'Starting installation of "' + plugin_id + '"...');

    // check if platform has plugin installed already.
    var platform_config = config_changes.get_platform_json(plugins_dir, platform);
    var plugin_basename = path.basename(plugin_dir);
    var is_installed = false;
    Object.keys(platform_config.installed_plugins).forEach(function(installed_plugin_id) {
        if (installed_plugin_id == plugin_id) {
            is_installed = true;
        }
    });
    Object.keys(platform_config.dependent_plugins).forEach(function(installed_plugin_id) {
        if (installed_plugin_id == plugin_id) {<commit_msg>[CB-4036] - synching up with master and fixed failing tests<commit_after>var path = require('path'),
    fs   = require('fs'),
    et   = require('elementtree'),
    n    = require('ncallbacks'),
    action_stack = require('./util/action-stack'),
    shell = require('shelljs'),
    semver = require('semver'),
    config_changes = require('./util/config-changes'),
    xml_helpers = require('./util/xml-helpers'),
    platform_modules = require('./platforms');

/* INSTALL FLOW
   ------------
   There are four functions install "flows" through. Here is an attempt at
   providing a high-level logic flow overview.
   1. module.exports (installPlugin)
     a) checks that the platform is supported
     b) invokes possiblyFetch
   2. possiblyFetch
     a) checks that the plugin is fetched. if so, calls runInstall
     b) if not, invokes plugman.fetch, and when done, calls runInstall
   3. runInstall
     a) checks if the plugin is already installed. if so, calls back (done).
     b) if possible, will check the version of the project and make sure it is compatible with the plugin (checks <engine> tags)
     c) makes sure that any variables required by the plugin are specified. if they are not specified, plugman will throw or callback with an error.
     d) if dependencies are listed in the plugin, it will recurse for each dependent plugin and call possiblyFetch (2) on each one. When each dependent plugin is successfully installed, it will then proceed to call handleInstall (4)
   4. handleInstall
     a) queues up actions into a queue (asset, source-file, headers, etc)
     b) processes the queue
     c) calls back (done)
*/

// possible options: subdir, cli_variables, www_dir
module.exports = function installPlugin(platform, project_dir, id, plugins_dir, options, callback) {
    if (!platform_modules[platform]) {
        var err = new Error(platform + " not supported.");
        if (callback) return callback(err);
        else throw err;
    }

    var current_stack = new action_stack();

    options.is_top_level = true;
    possiblyFetch(current_stack, platform, project_dir, id, plugins_dir, options, callback);
};

// possible options: subdir, cli_variables, www_dir, git_ref, is_top_level
function possiblyFetch(actions, platform, project_dir, id, plugins_dir, options, callback) {
    var plugin_dir = path.join(plugins_dir, id);

    // Check that the plugin has already been fetched.
    if (!fs.existsSync(plugin_dir)) {
        // if plugin doesnt exist, use fetch to get it.
        require('../plugman').fetch(id, plugins_dir, { link: false, subdir: options.subdir, git_ref: options.git_ref }, function(err, plugin_dir) {
            if (err) {
                if (callback) callback(err);
                else throw err;
            } else {
                // update ref to plugin_dir after successful fetch, via fetch callback
                runInstall(actions, platform, project_dir, plugin_dir, plugins_dir, options, callback);
            }
        });
    } else {
        runInstall(actions, platform, project_dir, plugin_dir, plugins_dir, options, callback);
    }
}

function checkMinimumReq(currentProjectInfo, minRequirements, platform, callback) {
    if(currentProjectInfo.cordovaVersion) {
        if(currentProjectInfo.cordovaVersion == 'dev' || semver.satisfies(currentProjectInfo.cordovaVersion, minRequirements.cordovaMinVersion)){
            // engine ok!
        } else {
            var err = new Error('Plugin doesn\'t support this project\'s Cordova version. Project version: ' + currentProjectInfo.cordovaVersion + ', failed version requirement: ' + minRequirements.cordovaMinVersion);
            if (callback) return callback(err);
            else throw err;
        }   
    }
    
    if(currentProjectInfo.min_os_version) {
        if(semver.satisfies(currentProjectInfo.min_os_version, minRequirements.platformMinOS)) {
            // min-os version ok
        } else {
            var err = new Error('Plugin doesn\'t support ' + platform + '  minimum os version.  ' + platform + '  minimum os version: ' + currentProjectInfo.min_os_version + ', failed version requirement: ' + minRequirements.platformMinOS);
            if (callback) return callback(err);
            else throw err;
        }            
    }

    if(currentProjectInfo.min_sdk_version) {
        if(semver.satisfies(currentProjectInfo.min_sdk_version, minRequirements.platformMinSDK)) {
            // min-sdk version ok
        } else {
            var err = new Error('Plugin doesn\'t support ' + platform + '  minimum sdk version.  ' + platform + '  minimum sdk version: ' + currentProjectInfo.min_sdk_version + ', failed version requirement: ' + minRequirements.platformMinSDK);
            if (callback) return callback(err);
            else throw err;
        }                        
    }     
}

// use the project level cordova scripts to get the current cordova version and platform information
function getCurrentProjectVersion(versionPath, platformPath) {
    // need to think about a more graceful way of handling when these scripts break or when they are not detected
    // setting version/platform to null if they do fail or do not exist and then just continuing
    
    var cordovaVersion, platformInfo;

    if (fs.existsSync(versionPath)) {   
        fs.chmodSync(versionPath, '755');
        var versionScript = shell.exec(versionPath, {silent: true});
        if (versionScript.code === 0) {
            cordovaVersion = versionScript.output.trim();
            var rc_index = cordovaVersion.indexOf('rc');
            if (rc_index > -1) {
                cordovaVersion = cordovaVersion.substr(0, rc_index) + '-' + cordovaVersion.substr(rc_index);
            }
        }else{
            cordovaVersion = null;
            require('../plugman').emit('log', 'Cordova project version script failed (has a ./cordova/version script, but something went wrong executing it), continuing anyways.');
        }
    }else{
        cordovaVersion = null;
        require('../plugman').emit('log', 'Cordova project version not detected (lacks a ./cordova/version script), continuing.');
    }  
    
    if (fs.existsSync(platformPath)) {   
        fs.chmodSync(platformPath, '755');
        var platformScript = shell.exec(platformPath, {silent: true});
        if (platformScript.code === 0) {
            // thinking platformScript.output would be a JSON string like:
            // { min_os_version: "5.0.0" , min_sdk_version: "1.0.0" }
            platformInfo = platformScript.output;
        }else{
            platformInfo =  null;
            require('../plugman').emit('log', 'Cordova project platformReq script failed (has a ./cordova/platformReq script, but something went wrong executing it), continuing anyways.');
        }
    }else{  
        platformInfo =  null;
        require('../plugman').emit('log', 'Cordova project version not detected (lacks a ./cordova/version script), continuing.');
    }     
    
    return { 'cordovaVersion' : cordovaVersion, 'platformInfo' : platformInfo };
}

function getMinReq(pluginElement, platform){

    var cordovaMinVersion, platformMinOS, platformMinSDK;
    
    var engines = pluginElement.findall('engines/engine');
    engines.forEach(function(engine){
                if(engine.attrib["name"].toLowerCase() === "cordova"){
                    cordovaMinVersion = engine.attrib["version"];
                }else{
                    // check for other engines - if found, shove the info into return statement 
                }
            });
    
    
    // these elements are still in flux - most likely to change when the platforms start implementing these scripts
    platformMinOS = pluginElement.findall('./platform[@name="'+platform+'"][@min-os-version]');
    platformMinOS = platformMinOS[0] ? platformMinOS[0].attrib["min-os-version"] : null

    platformMinSDK = pluginElement.findall('./platform[@name="'+platform+'"][@min-sdk-version]');
    platformMinSDK = platformMinSDK[0] ? platformMinSDK[0].attrib["min-sdk-version"] : null;
    
    return { 'cordovaMinVersion': cordovaMinVersion, 'platformMinOS' : platformMinOS, 'platformMinSDK' : platformMinSDK };
}


// possible options: cli_variables, www_dir, is_top_level
function runInstall(actions, platform, project_dir, plugin_dir, plugins_dir, options, callback) {
    var xml_path     = path.join(plugin_dir, 'plugin.xml')
      , plugin_et    = xml_helpers.parseElementtreeSync(xml_path)
      , filtered_variables = {};
    var name         = plugin_et.findall('name').text;
    var plugin_id    = plugin_et.getroot().attrib['id'];
    require('../plugman').emit('log', 'Starting installation of "' + plugin_id + '"...');

    // check if platform has plugin installed already.
    var platform_config = config_changes.get_platform_json(plugins_dir, platform);
    var plugin_basename = path.basename(plugin_dir);
    var is_installed = false;
    Object.keys(platform_config.installed_plugins).forEach(function(installed_plugin_id) {
        if (installed_plugin_id == plugin_id) {
            is_installed = true;
        }
    });
    Object.keys(platform_config.dependent_plugins).forEach(function(installed_plugin_id) {
        if (installed_plugin_id == plugin_id) {
{'fraction_non_alphanumeric': 0.1084271802404657, 'fraction_numerical': 0.0030466242315434415, 'mean_word_length': 2.901931649331352, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 6, 'xml': 12}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6293160', 'n_tokens_mistral': 5035, 'n_tokens_neox': 4951, 'n_words': 1445}