LCOV - code coverage report
Current view: top level - src - mods.cpp (source / functions) Hit Total Coverage
Test: report Lines: 0 191 0.0 %
Date: 2015-07-11 18:23:49 Functions: 0 10 0.0 %

          Line data    Source code
       1             : /*
       2             : Minetest
       3             : Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
       4             : 
       5             : This program is free software; you can redistribute it and/or modify
       6             : it under the terms of the GNU Lesser General Public License as published by
       7             : the Free Software Foundation; either version 2.1 of the License, or
       8             : (at your option) any later version.
       9             : 
      10             : This program is distributed in the hope that it will be useful,
      11             : but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             : GNU Lesser General Public License for more details.
      14             : 
      15             : You should have received a copy of the GNU Lesser General Public License along
      16             : with this program; if not, write to the Free Software Foundation, Inc.,
      17             : 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      18             : */
      19             : 
      20             : #include <cctype>
      21             : #include <fstream>
      22             : #include "mods.h"
      23             : #include "filesys.h"
      24             : #include "strfnd.h"
      25             : #include "log.h"
      26             : #include "subgame.h"
      27             : #include "settings.h"
      28             : #include "strfnd.h"
      29             : #include "convert_json.h"
      30             : 
      31           0 : static bool parseDependsLine(std::istream &is,
      32             :                 std::string &dep, std::set<char> &symbols)
      33             : {
      34           0 :         std::getline(is, dep);
      35           0 :         dep = trim(dep);
      36           0 :         symbols.clear();
      37           0 :         size_t pos = dep.size();
      38           0 :         while(pos > 0 && !string_allowed(dep.substr(pos-1, 1), MODNAME_ALLOWED_CHARS)){
      39             :                 // last character is a symbol, not part of the modname
      40           0 :                 symbols.insert(dep[pos-1]);
      41           0 :                 --pos;
      42             :         }
      43           0 :         dep = trim(dep.substr(0, pos));
      44           0 :         return dep != "";
      45             : }
      46             : 
      47           0 : void parseModContents(ModSpec &spec)
      48             : {
      49             :         // NOTE: this function works in mutual recursion with getModsInPath
      50           0 :         Settings info;
      51           0 :         info.readConfigFile((spec.path+DIR_DELIM+"mod.conf").c_str());
      52             : 
      53           0 :         if (info.exists("name"))
      54           0 :                 spec.name = info.get("name");
      55             : 
      56           0 :         spec.depends.clear();
      57           0 :         spec.optdepends.clear();
      58           0 :         spec.is_modpack = false;
      59           0 :         spec.modpack_content.clear();
      60             : 
      61             :         // Handle modpacks (defined by containing modpack.txt)
      62           0 :         std::ifstream modpack_is((spec.path+DIR_DELIM+"modpack.txt").c_str());
      63           0 :         if(modpack_is.good()){ //a modpack, recursively get the mods in it
      64           0 :                 modpack_is.close(); // We don't actually need the file
      65           0 :                 spec.is_modpack = true;
      66           0 :                 spec.modpack_content = getModsInPath(spec.path, true);
      67             : 
      68             :                 // modpacks have no dependencies; they are defined and
      69             :                 // tracked separately for each mod in the modpack
      70             :         }
      71             :         else{ // not a modpack, parse the dependencies
      72           0 :                 std::ifstream is((spec.path+DIR_DELIM+"depends.txt").c_str());
      73           0 :                 while(is.good()){
      74           0 :                         std::string dep;
      75           0 :                         std::set<char> symbols;
      76           0 :                         if(parseDependsLine(is, dep, symbols)){
      77           0 :                                 if(symbols.count('?') != 0){
      78           0 :                                         spec.optdepends.insert(dep);
      79             :                                 }
      80             :                                 else{
      81           0 :                                         spec.depends.insert(dep);
      82             :                                 }
      83             :                         }
      84             :                 }
      85             :         }
      86           0 : }
      87             : 
      88           0 : std::map<std::string, ModSpec> getModsInPath(std::string path, bool part_of_modpack, const std::string &worldpath)
      89             : {
      90             :         // NOTE: this function works in mutual recursion with parseModContents
      91             : 
      92           0 :         std::map<std::string, ModSpec> result;
      93           0 :         std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path);
      94           0 :         std::string worldmt = worldpath+DIR_DELIM+"world.mt";
      95           0 :         Settings worldmt_settings;
      96           0 :         worldmt_settings.readConfigFile(worldmt.c_str());
      97           0 :         for(u32 j=0; j<dirlist.size(); j++){
      98           0 :                 if(!dirlist[j].dir)
      99           0 :                         continue;
     100           0 :                 std::string modname = dirlist[j].name;
     101             :                 // Ignore all directories beginning with a ".", especially
     102             :                 // VCS directories like ".git" or ".svn"
     103           0 :                 if(modname[0] == '.')
     104           0 :                         continue;
     105             : 
     106           0 :                 std::string disable = std::string("load_mod_") + modname;
     107             :                 // for backwards compatibility: exclude only mods which are
     108             :                 // explicitly excluded. if mod is not mentioned at all, it is
     109             :                 // enabled. So by default, all installed mods are enabled.
     110           0 :                 if(worldmt_settings.exists(disable) && !worldmt_settings.getBool(disable)){
     111           0 :                         actionstream<<"Mod "<<modname<<" is explicitly disabled in world.mt"<<std::endl;
     112           0 :                         continue;
     113             :                 }
     114             : 
     115           0 :                 std::string modpath = path + DIR_DELIM + modname;
     116             : 
     117           0 :                 ModSpec spec(modname, modpath);
     118           0 :                 spec.part_of_modpack = part_of_modpack;
     119           0 :                 parseModContents(spec);
     120           0 :                 result.insert(std::make_pair(modname, spec));
     121             :         }
     122           0 :         return result;
     123             : }
     124             : 
     125           0 : std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mods)
     126             : {
     127           0 :         std::map<std::string, ModSpec> result;
     128           0 :         for(std::map<std::string,ModSpec>::iterator it = mods.begin();
     129           0 :                 it != mods.end(); ++it)
     130             :         {
     131           0 :                 ModSpec mod = (*it).second;
     132           0 :                 if(mod.is_modpack)
     133             :                 {
     134             :                         std::map<std::string, ModSpec> content =
     135           0 :                                 flattenModTree(mod.modpack_content);
     136           0 :                         result.insert(content.begin(),content.end());
     137           0 :                         result.insert(std::make_pair(mod.name,mod));
     138             :                 }
     139             :                 else //not a modpack
     140             :                 {
     141           0 :                         result.insert(std::make_pair(mod.name,mod));
     142             :                 }
     143             :         }
     144           0 :         return result;
     145             : }
     146             : 
     147           0 : std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods)
     148             : {
     149           0 :         std::vector<ModSpec> result;
     150           0 :         for(std::map<std::string,ModSpec>::iterator it = mods.begin();
     151           0 :                 it != mods.end(); ++it)
     152             :         {
     153           0 :                 ModSpec mod = (*it).second;
     154           0 :                 if(mod.is_modpack)
     155             :                 {
     156           0 :                         std::vector<ModSpec> content = flattenMods(mod.modpack_content);
     157           0 :                         result.reserve(result.size() + content.size());
     158           0 :                         result.insert(result.end(),content.begin(),content.end());
     159             : 
     160             :                 }
     161             :                 else //not a modpack
     162             :                 {
     163           0 :                         result.push_back(mod);
     164             :                 }
     165             :         }
     166           0 :         return result;
     167             : }
     168             : 
     169           0 : ModConfiguration::ModConfiguration(std::string worldpath)
     170             : {
     171           0 :         SubgameSpec gamespec = findWorldSubgame(worldpath);
     172             : 
     173             :         // Add all game mods and all world mods
     174           0 :         addModsInPath(gamespec.gamemods_path, worldpath);
     175           0 :         addModsInPath(worldpath + DIR_DELIM + "worldmods", worldpath);
     176             : 
     177             :         // check world.mt file for mods explicitely declared to be
     178             :         // loaded or not by a load_mod_<modname> = ... line.
     179           0 :         std::string worldmt = worldpath+DIR_DELIM+"world.mt";
     180           0 :         Settings worldmt_settings;
     181           0 :         worldmt_settings.readConfigFile(worldmt.c_str());
     182           0 :         std::vector<std::string> names = worldmt_settings.getNames();
     183           0 :         std::set<std::string> include_mod_names;
     184           0 :         for(std::vector<std::string>::iterator it = names.begin();
     185           0 :                 it != names.end(); ++it)
     186             :         {
     187           0 :                 std::string name = *it;
     188             :                 // for backwards compatibility: exclude only mods which are
     189             :                 // explicitely excluded. if mod is not mentioned at all, it is
     190             :                 // enabled. So by default, all installed mods are enabled.
     191           0 :                 if (name.compare(0,9,"load_mod_") == 0 &&
     192           0 :                         worldmt_settings.getBool(name))
     193             :                 {
     194           0 :                         include_mod_names.insert(name.substr(9));
     195             :                 }
     196             :         }
     197             : 
     198             :         // Collect all mods that are also in include_mod_names
     199           0 :         std::vector<ModSpec> addon_mods;
     200           0 :         for(std::set<std::string>::const_iterator it_path = gamespec.addon_mods_paths.begin();
     201           0 :                         it_path != gamespec.addon_mods_paths.end(); ++it_path)
     202             :         {
     203           0 :                 std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(*it_path));
     204           0 :                 for(std::vector<ModSpec>::iterator it = addon_mods_in_path.begin();
     205           0 :                         it != addon_mods_in_path.end(); ++it)
     206             :                 {
     207           0 :                         ModSpec& mod = *it;
     208           0 :                         if(include_mod_names.count(mod.name) != 0)
     209           0 :                                 addon_mods.push_back(mod);
     210             :                         else
     211           0 :                                 worldmt_settings.setBool("load_mod_" + mod.name, false);
     212             :                 }
     213             :         }
     214           0 :         worldmt_settings.updateConfigFile(worldmt.c_str());
     215             : 
     216           0 :         addMods(addon_mods);
     217             : 
     218             :         // report on name conflicts
     219           0 :         if(!m_name_conflicts.empty()){
     220           0 :                 std::string s = "Unresolved name conflicts for mods ";
     221           0 :                 for(std::set<std::string>::const_iterator it = m_name_conflicts.begin();
     222           0 :                                 it != m_name_conflicts.end(); ++it)
     223             :                 {
     224           0 :                         if(it != m_name_conflicts.begin()) s += ", ";
     225           0 :                         s += std::string("\"") + (*it) + "\"";
     226             :                 }
     227           0 :                 s += ".";
     228           0 :                 throw ModError(s);
     229             :         }
     230             : 
     231             :         // get the mods in order
     232           0 :         resolveDependencies();
     233           0 : }
     234             : 
     235           0 : void ModConfiguration::addModsInPath(std::string path, const std::string &worldpath)
     236             : {
     237           0 :         addMods(flattenMods(getModsInPath(path, false, worldpath)));
     238           0 : }
     239             : 
     240           0 : void ModConfiguration::addMods(std::vector<ModSpec> new_mods)
     241             : {
     242             :         // Maintain a map of all existing m_unsatisfied_mods.
     243             :         // Keys are mod names and values are indices into m_unsatisfied_mods.
     244           0 :         std::map<std::string, u32> existing_mods;
     245           0 :         for(u32 i = 0; i < m_unsatisfied_mods.size(); ++i){
     246           0 :                 existing_mods[m_unsatisfied_mods[i].name] = i;
     247             :         }
     248             : 
     249             :         // Add new mods
     250           0 :         for(int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack){
     251             :                 // First iteration:
     252             :                 // Add all the mods that come from modpacks
     253             :                 // Second iteration:
     254             :                 // Add all the mods that didn't come from modpacks
     255             : 
     256           0 :                 std::set<std::string> seen_this_iteration;
     257             : 
     258           0 :                 for(std::vector<ModSpec>::const_iterator it = new_mods.begin();
     259           0 :                                 it != new_mods.end(); ++it){
     260           0 :                         const ModSpec &mod = *it;
     261           0 :                         if(mod.part_of_modpack != (bool)want_from_modpack)
     262           0 :                                 continue;
     263           0 :                         if(existing_mods.count(mod.name) == 0){
     264             :                                 // GOOD CASE: completely new mod.
     265           0 :                                 m_unsatisfied_mods.push_back(mod);
     266           0 :                                 existing_mods[mod.name] = m_unsatisfied_mods.size() - 1;
     267             :                         }
     268           0 :                         else if(seen_this_iteration.count(mod.name) == 0){
     269             :                                 // BAD CASE: name conflict in different levels.
     270           0 :                                 u32 oldindex = existing_mods[mod.name];
     271           0 :                                 const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
     272           0 :                                 actionstream<<"WARNING: Mod name conflict detected: \""
     273           0 :                                         <<mod.name<<"\""<<std::endl
     274           0 :                                         <<"Will not load: "<<oldmod.path<<std::endl
     275           0 :                                         <<"Overridden by: "<<mod.path<<std::endl;
     276           0 :                                 m_unsatisfied_mods[oldindex] = mod;
     277             : 
     278             :                                 // If there was a "VERY BAD CASE" name conflict
     279             :                                 // in an earlier level, ignore it.
     280           0 :                                 m_name_conflicts.erase(mod.name);
     281             :                         }
     282             :                         else{
     283             :                                 // VERY BAD CASE: name conflict in the same level.
     284           0 :                                 u32 oldindex = existing_mods[mod.name];
     285           0 :                                 const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
     286           0 :                                 errorstream<<"WARNING: Mod name conflict detected: \""
     287           0 :                                         <<mod.name<<"\""<<std::endl
     288           0 :                                         <<"Will not load: "<<oldmod.path<<std::endl
     289           0 :                                         <<"Will not load: "<<mod.path<<std::endl;
     290           0 :                                 m_unsatisfied_mods[oldindex] = mod;
     291           0 :                                 m_name_conflicts.insert(mod.name);
     292             :                         }
     293           0 :                         seen_this_iteration.insert(mod.name);
     294             :                 }
     295             :         }
     296           0 : }
     297             : 
     298           0 : void ModConfiguration::resolveDependencies()
     299             : {
     300             :         // Step 1: Compile a list of the mod names we're working with
     301           0 :         std::set<std::string> modnames;
     302           0 :         for(std::vector<ModSpec>::iterator it = m_unsatisfied_mods.begin();
     303           0 :                 it != m_unsatisfied_mods.end(); ++it){
     304           0 :                 modnames.insert((*it).name);
     305             :         }
     306             : 
     307             :         // Step 2: get dependencies (including optional dependencies)
     308             :         // of each mod, split mods into satisfied and unsatisfied
     309           0 :         std::list<ModSpec> satisfied;
     310           0 :         std::list<ModSpec> unsatisfied;
     311           0 :         for(std::vector<ModSpec>::iterator it = m_unsatisfied_mods.begin();
     312           0 :                         it != m_unsatisfied_mods.end(); ++it){
     313           0 :                 ModSpec mod = *it;
     314           0 :                 mod.unsatisfied_depends = mod.depends;
     315             :                 // check which optional dependencies actually exist
     316           0 :                 for(std::set<std::string>::iterator it_optdep = mod.optdepends.begin();
     317           0 :                                 it_optdep != mod.optdepends.end(); ++it_optdep){
     318           0 :                         std::string optdep = *it_optdep;
     319           0 :                         if(modnames.count(optdep) != 0)
     320           0 :                                 mod.unsatisfied_depends.insert(optdep);
     321             :                 }
     322             :                 // if a mod has no depends it is initially satisfied
     323           0 :                 if(mod.unsatisfied_depends.empty())
     324           0 :                         satisfied.push_back(mod);
     325             :                 else
     326           0 :                         unsatisfied.push_back(mod);
     327             :         }
     328             : 
     329             :         // Step 3: mods without unmet dependencies can be appended to
     330             :         // the sorted list.
     331           0 :         while(!satisfied.empty()){
     332           0 :                 ModSpec mod = satisfied.back();
     333           0 :                 m_sorted_mods.push_back(mod);
     334           0 :                 satisfied.pop_back();
     335           0 :                 for(std::list<ModSpec>::iterator it = unsatisfied.begin();
     336           0 :                                 it != unsatisfied.end(); ){
     337           0 :                         ModSpec& mod2 = *it;
     338           0 :                         mod2.unsatisfied_depends.erase(mod.name);
     339           0 :                         if(mod2.unsatisfied_depends.empty()){
     340           0 :                                 satisfied.push_back(mod2);
     341           0 :                                 it = unsatisfied.erase(it);
     342             :                         }
     343             :                         else{
     344           0 :                                 ++it;
     345             :                         }
     346             :                 }
     347             :         }
     348             : 
     349             :         // Step 4: write back list of unsatisfied mods
     350           0 :         m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end());
     351           0 : }
     352             : 
     353             : #if USE_CURL
     354           0 : Json::Value getModstoreUrl(std::string url)
     355             : {
     356           0 :         std::vector<std::string> extra_headers;
     357             : 
     358           0 :         bool special_http_header = true;
     359             : 
     360             :         try {
     361           0 :                 special_http_header = g_settings->getBool("modstore_disable_special_http_header");
     362           0 :         } catch (SettingNotFoundException) {}
     363             : 
     364           0 :         if (special_http_header) {
     365           0 :                 extra_headers.push_back("Accept: application/vnd.minetest.mmdb-v1+json");
     366             :         }
     367           0 :         return fetchJsonValue(url, special_http_header ? &extra_headers : NULL);
     368             : }
     369             : 
     370             : #endif

Generated by: LCOV version 1.11