X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fmods.cpp;h=dae49233978c29fa01fef5378486a92af6b6f59e;hb=2153965cf92ab61b0d7e095cf3c2755924fdc221;hp=c2bb907c2f76d4d776ee168e6a121288e9797bed;hpb=037b2591971d752e67fa7d47095b996b3f56da5a;p=minetest.git diff --git a/src/mods.cpp b/src/mods.cpp index c2bb907c2..dae492339 100644 --- a/src/mods.cpp +++ b/src/mods.cpp @@ -1,6 +1,6 @@ /* -Minetest-c55 -Copyright (C) 2011 celeron55, Perttu Ahola +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -17,141 +17,405 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "mods.h" -#include +#include #include -#include -#include +#include +#include "mods.h" #include "filesys.h" -#include "strfnd.h" #include "log.h" +#include "subgame.h" +#include "settings.h" +#include "porting.h" +#include "convert_json.h" -static void collectMods(const std::string &modspath, - std::queue &mods_satisfied, - core::list &mods_unsorted, - std::map &mod_names, - std::string indentation) -{ - TRACESTREAM(< dirlist = fs::GetDirListing(modspath); - for(u32 j=0; j &symbols) +{ + std::getline(is, dep); + dep = trim(dep); + symbols.clear(); + size_t pos = dep.size(); + while(pos > 0 && !string_allowed(dep.substr(pos-1, 1), MODNAME_ALLOWED_CHARS)){ + // last character is a symbol, not part of the modname + symbols.insert(dep[pos-1]); + --pos; + } + dep = trim(dep.substr(0, pos)); + return !dep.empty(); +} + +void parseModContents(ModSpec &spec) +{ + // NOTE: this function works in mutual recursion with getModsInPath + Settings info; + info.readConfigFile((spec.path+DIR_DELIM+"mod.conf").c_str()); + + if (info.exists("name")) + spec.name = info.get("name"); + + spec.depends.clear(); + spec.optdepends.clear(); + spec.is_modpack = false; + spec.modpack_content.clear(); + + // Handle modpacks (defined by containing modpack.txt) + std::ifstream modpack_is((spec.path+DIR_DELIM+"modpack.txt").c_str()); + if(modpack_is.good()){ //a modpack, recursively get the mods in it + modpack_is.close(); // We don't actually need the file + spec.is_modpack = true; + spec.modpack_content = getModsInPath(spec.path, true); + + // modpacks have no dependencies; they are defined and + // tracked separately for each mod in the modpack + } + else{ // not a modpack, parse the dependencies + std::ifstream is((spec.path+DIR_DELIM+"depends.txt").c_str()); + while(is.good()){ + std::string dep; + std::set symbols; + if(parseDependsLine(is, dep, symbols)){ + if(symbols.count('?') != 0){ + spec.optdepends.insert(dep); + } + else{ + spec.depends.insert(dep); + } } } - // Detect mod name conflicts + } +} + +std::map getModsInPath(std::string path, bool part_of_modpack) +{ + // NOTE: this function works in mutual recursion with parseModContents + + std::map result; + std::vector dirlist = fs::GetDirListing(path); + for (const fs::DirListNode &dln : dirlist) { + if(!dln.dir) + continue; + const std::string &modname = dln.name; + // Ignore all directories beginning with a ".", especially + // VCS directories like ".git" or ".svn" + if (modname[0] == '.') + continue; + std::string modpath = path + DIR_DELIM + modname; + + ModSpec spec(modname, modpath); + spec.part_of_modpack = part_of_modpack; + parseModContents(spec); + result.insert(std::make_pair(modname, spec)); + } + return result; +} + +std::vector flattenMods(std::map mods) +{ + std::vector result; + for (const auto &it : mods) { + const ModSpec &mod = it.second; + if (mod.is_modpack) { + std::vector content = flattenMods(mod.modpack_content); + result.reserve(result.size() + content.size()); + result.insert(result.end(),content.begin(),content.end()); + + } + else //not a modpack { - std::map::const_iterator i; - i = mod_names.find(modname); - if(i != mod_names.end()){ - std::string s; - infostream<second< &new_mods) +{ + // Maintain a map of all existing m_unsatisfied_mods. + // Keys are mod names and values are indices into m_unsatisfied_mods. + std::map existing_mods; + for(u32 i = 0; i < m_unsatisfied_mods.size(); ++i){ + existing_mods[m_unsatisfied_mods[i].name] = i; + } + + // Add new mods + for(int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack){ + // First iteration: + // Add all the mods that come from modpacks + // Second iteration: + // Add all the mods that didn't come from modpacks + + std::set seen_this_iteration; + + for (const ModSpec &mod : new_mods) { + if (mod.part_of_modpack != (bool)want_from_modpack) continue; + + if (existing_mods.count(mod.name) == 0) { + // GOOD CASE: completely new mod. + m_unsatisfied_mods.push_back(mod); + existing_mods[mod.name] = m_unsatisfied_mods.size() - 1; + } else if(seen_this_iteration.count(mod.name) == 0) { + // BAD CASE: name conflict in different levels. + u32 oldindex = existing_mods[mod.name]; + const ModSpec &oldmod = m_unsatisfied_mods[oldindex]; + warningstream<<"Mod name conflict detected: \"" + < depends; - std::ifstream is((modpath+DIR_DELIM+"depends.txt").c_str(), - std::ios_base::binary); - while(is.good()){ - std::string dep; - std::getline(is, dep); - dep = trim(dep); - if(dep != "") - depends.insert(dep); + } +} + +void ModConfiguration::addModsFromConfig(const std::string &settings_path, const std::set &mods) +{ + Settings conf; + std::set load_mod_names; + + conf.readConfigFile(settings_path.c_str()); + std::vector names = conf.getNames(); + for (const std::string &name : names) { + if (name.compare(0,9,"load_mod_")==0 && conf.getBool(name)) + load_mod_names.insert(name.substr(9)); + } + + std::vector addon_mods; + for (const std::string &i : mods) { + std::vector addon_mods_in_path = flattenMods(getModsInPath(i)); + for (std::vector::const_iterator it = addon_mods_in_path.begin(); + it != addon_mods_in_path.end(); ++it) { + const ModSpec& mod = *it; + if (load_mod_names.count(mod.name) != 0) + addon_mods.push_back(mod); + else + conf.setBool("load_mod_" + mod.name, false); } - ModSpec spec(modname, modpath, depends); - mods_unsorted.push_back(spec); - if(depends.empty()) - mods_satisfied.push(spec); - mod_names[modname] = modpath; - } -} - -// Get a dependency-sorted list of ModSpecs -core::list getMods(core::list &modspaths) - throw(ModError) -{ - std::queue mods_satisfied; - core::list mods_unsorted; - core::list mods_sorted; - // name, path: For detecting name conflicts - std::map mod_names; - for(core::list::Iterator i = modspaths.begin(); - i != modspaths.end(); i++){ - std::string modspath = *i; - collectMods(modspath, mods_satisfied, mods_unsorted, mod_names, ""); - } - // Sort by depencencies - while(!mods_satisfied.empty()){ - ModSpec mod = mods_satisfied.front(); - mods_satisfied.pop(); - mods_sorted.push_back(mod); - for(core::list::Iterator i = mods_unsorted.begin(); - i != mods_unsorted.end(); i++){ - ModSpec &mod2 = *i; - if(mod2.unsatisfied_depends.empty()) - continue; - mod2.unsatisfied_depends.erase(mod.name); - if(!mod2.unsatisfied_depends.empty()) - continue; - mods_satisfied.push(mod2); + } + conf.updateConfigFile(settings_path.c_str()); + + addMods(addon_mods); + checkConflictsAndDeps(); + + // complain about mods declared to be loaded, but not found + for (const ModSpec &addon_mod : addon_mods) + load_mod_names.erase(addon_mod.name); + + std::vector unsatisfiedMods = getUnsatisfiedMods(); + + for (const ModSpec &unsatisfiedMod : unsatisfiedMods) + load_mod_names.erase(unsatisfiedMod.name); + + if (!load_mod_names.empty()) { + errorstream << "The following mods could not be found:"; + for (const std::string &mod : load_mod_names) + errorstream << " \"" << mod << "\""; + errorstream << std::endl; + } +} + +void ModConfiguration::checkConflictsAndDeps() +{ + // report on name conflicts + if (!m_name_conflicts.empty()) { + std::string s = "Unresolved name conflicts for mods "; + for (std::unordered_set::const_iterator it = + m_name_conflicts.begin(); it != m_name_conflicts.end(); ++it) { + if (it != m_name_conflicts.begin()) s += ", "; + s += std::string("\"") + (*it) + "\""; } + s += "."; + throw ModError(s); } - std::ostringstream errs(std::ios::binary); - // Check unsatisfied dependencies - for(core::list::Iterator i = mods_unsorted.begin(); - i != mods_unsorted.end(); i++){ - ModSpec &mod = *i; - if(mod.unsatisfied_depends.empty()) - continue; - errs<<"mod \""<::iterator - i = mod.unsatisfied_depends.begin(); - i != mod.unsatisfied_depends.end(); i++){ - errs<<" \""<<(*i)<<"\""; + + // get the mods in order + resolveDependencies(); +} + +void ModConfiguration::resolveDependencies() +{ + // Step 1: Compile a list of the mod names we're working with + std::set modnames; + for (const ModSpec &mod : m_unsatisfied_mods) { + modnames.insert(mod.name); + } + + // Step 2: get dependencies (including optional dependencies) + // of each mod, split mods into satisfied and unsatisfied + std::list satisfied; + std::list unsatisfied; + for (ModSpec mod : m_unsatisfied_mods) { + mod.unsatisfied_depends = mod.depends; + // check which optional dependencies actually exist + for (const std::string &optdep : mod.optdepends) { + if (modnames.count(optdep) != 0) + mod.unsatisfied_depends.insert(optdep); } - errs<<"."<::Iterator i = mods_sorted.begin(); - i != mods_sorted.end(); i++){ - ModSpec &mod = *i; - TRACESTREAM(<<"name=\""<::iterator i = mod.depends.begin(); - i != mod.depends.end(); i++) - TRACESTREAM(<<" "<<(*i)); - TRACESTREAM(<<" ] unsatisfied_depends=["); - for(std::set::iterator i = mod.unsatisfied_depends.begin(); - i != mod.unsatisfied_depends.end(); i++) - TRACESTREAM(<<" "<<(*i)); - TRACESTREAM(<<" ]"< paths; + std::string path_user = porting::path_user + DIR_DELIM + "clientmods"; + paths.insert(path); + paths.insert(path_user); + + std::string settings_path = path_user + DIR_DELIM + "mods.conf"; + addModsFromConfig(settings_path, paths); +} +#endif + +ModMetadata::ModMetadata(const std::string &mod_name): + m_mod_name(mod_name) +{ +} + +void ModMetadata::clear() +{ + Metadata::clear(); + m_modified = true; +} + +bool ModMetadata::save(const std::string &root_path) +{ + Json::Value json; + for (StringMap::const_iterator it = m_stringvars.begin(); + it != m_stringvars.end(); ++it) { + json[it->first] = it->second; + } + + if (!fs::PathExists(root_path)) { + if (!fs::CreateAllDirs(root_path)) { + errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '" + << root_path << "' tree cannot be created." << std::endl; + return false; + } + } else if (!fs::IsDir(root_path)) { + errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '" + << root_path << "' is not a directory." << std::endl; + return false; } - return mods_sorted; + + bool w_ok = fs::safeWriteToFile(root_path + DIR_DELIM + m_mod_name, + fastWriteJson(json)); + + if (w_ok) { + m_modified = false; + } else { + errorstream << "ModMetadata[" << m_mod_name << "]: failed write file." << std::endl; + } + return w_ok; } +bool ModMetadata::load(const std::string &root_path) +{ + m_stringvars.clear(); + + std::ifstream is((root_path + DIR_DELIM + m_mod_name).c_str(), std::ios_base::binary); + if (!is.good()) { + return false; + } + + Json::Value root; + Json::CharReaderBuilder builder; + builder.settings_["collectComments"] = false; + std::string errs; + if (!Json::parseFromStream(builder, is, &root, &errs)) { + errorstream << "ModMetadata[" << m_mod_name << "]: failed read data " + "(Json decoding failure). Message: " << errs << std::endl; + return false; + } + + const Json::Value::Members attr_list = root.getMemberNames(); + for (const auto &it : attr_list) { + Json::Value attr_value = root[it]; + m_stringvars[it] = attr_value.asString(); + } + + return true; +} + +bool ModMetadata::setString(const std::string &name, const std::string &var) +{ + m_modified = Metadata::setString(name, var); + return m_modified; +}