From adf1c3295b77b3e6e06ac9273214aaa21e6d6479 Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Thu, 28 Aug 2014 22:34:06 +0100 Subject: [PATCH] Masterlist update now only tests condition parsing. Instead of evaluating the conditions, and modifying the data stored in the masterlist object. This will probably also speed up masterlist updating a little. Fixes #257. --- src/backend/git.cpp | 9 +++---- src/backend/metadata.cpp | 58 +++++++++++++++++++++++++++++++++++++--- src/backend/metadata.h | 10 ++++--- src/backend/parsers.h | 46 ++++++++++++++++++------------- 4 files changed, 90 insertions(+), 33 deletions(-) diff --git a/src/backend/git.cpp b/src/backend/git.cpp index 6eb58881..85437b62 100644 --- a/src/backend/git.cpp +++ b/src/backend/git.cpp @@ -424,17 +424,14 @@ namespace loot { try { this->MetadataList::Load(game.MasterlistPath()); - unordered_set tempSet; for (auto &plugin : plugins) { - tempSet.insert(Plugin(plugin).EvalAllConditions(game, language)); + plugin.ParseAllConditions(game); } - plugins = tempSet; for (auto &plugin : regexPlugins) { - plugin.EvalAllConditions(game, language); + plugin.ParseAllConditions(game); } - for (auto &message: messages) { - message.EvalCondition(game, language); + message.ParseCondition(game); } parsingFailed = false; diff --git a/src/backend/metadata.cpp b/src/backend/metadata.cpp index 1a0a816f..0ca2e724 100644 --- a/src/backend/metadata.cpp +++ b/src/backend/metadata.cpp @@ -118,7 +118,7 @@ namespace loot { return _condition; } - bool ConditionStruct::EvalCondition(loot::Game& game) const { + bool ConditionStruct::EvalCondition(Game& game) const { if (_condition.empty()) return true; @@ -128,12 +128,11 @@ namespace loot { if (it != game.conditionCache.end()) return it->second; - condition_grammar grammar; + condition_grammar grammar(game, false); boost::spirit::qi::space_type skipper; std::string::const_iterator begin, end; bool eval; - grammar.SetGame(game); begin = _condition.begin(); end = _condition.end(); @@ -155,6 +154,39 @@ namespace loot { return eval; } + void ConditionStruct::ParseCondition(Game& game) const { + if (_condition.empty()) + return; + + BOOST_LOG_TRIVIAL(trace) << "Testing condition syntax: " << _condition; + + // If the same condition string has already been evaluated, it must be written correctly. + unordered_map::const_iterator it = game.conditionCache.find(boost::locale::to_lower(_condition)); + if (it != game.conditionCache.end()) + return; + + condition_grammar grammar(game, true); + boost::spirit::qi::space_type skipper; + std::string::const_iterator begin, end; + + begin = _condition.begin(); + end = _condition.end(); + + bool r; + try { + r = boost::spirit::qi::phrase_parse(begin, end, grammar, skipper); + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << _condition << "\": " << e.what(); + throw loot::error(loot::error::condition_eval_fail, (boost::format(lc::translate("Failed to parse condition \"%1%\": %2%")) % _condition % e.what()).str()); + } + + if (!r || begin != end) { + BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << _condition << "\"."; + throw loot::error(loot::error::condition_eval_fail, (boost::format(lc::translate("Failed to parse condition \"%1%\".")) % _condition).str()); + } + } + MessageContent::MessageContent() : _language(Language::english) {} MessageContent::MessageContent(const std::string& str, const unsigned int language) : _str(str), _language(language) {} @@ -574,7 +606,7 @@ namespace loot { _dirtyInfo = dirtyInfo; } - Plugin& Plugin::EvalAllConditions(loot::Game& game, const unsigned int language) { + Plugin& Plugin::EvalAllConditions(Game& game, const unsigned int language) { for (auto it = loadAfter.begin(); it != loadAfter.end();) { if (!it->EvalCondition(game)) loadAfter.erase(it++); @@ -634,6 +666,24 @@ namespace loot { return *this; } + void Plugin::ParseAllConditions(Game& game) const { + for (const File& file : loadAfter) { + file.ParseCondition(game); + } + for (const File& file : requirements) { + file.ParseCondition(game); + } + for (const File& file : incompatibilities) { + file.ParseCondition(game); + } + for (const Message& message : messages) { + message.ParseCondition(game); + } + for (const Tag& tag : tags) { + tag.ParseCondition(game); + } + } + bool Plugin::HasNameOnly() const { return !IsPriorityExplicit() && loadAfter.empty() && requirements.empty() && incompatibilities.empty() && messages.empty() && tags.empty() && _dirtyInfo.empty(); } diff --git a/src/backend/metadata.h b/src/backend/metadata.h index 1bf040c2..03efa274 100644 --- a/src/backend/metadata.h +++ b/src/backend/metadata.h @@ -83,7 +83,8 @@ namespace loot { ConditionStruct(const std::string& condition); bool IsConditional() const; - bool EvalCondition(loot::Game& game) const; + bool EvalCondition(Game& game) const; + void ParseCondition(Game& game) const; // Throws error on parsing failure. std::string Condition() const; private: @@ -116,7 +117,7 @@ namespace loot { bool operator < (const Message& rhs) const; bool operator == (const Message& rhs) const; - bool EvalCondition(loot::Game& game, const unsigned int language); + bool EvalCondition(Game& game, const unsigned int language); unsigned int Type() const; std::vector Content() const; @@ -166,7 +167,7 @@ namespace loot { public: Plugin(); Plugin(const std::string& name); - Plugin(loot::Game& game, const std::string& name, const bool headerOnly); + Plugin(Game& game, const std::string& name, const bool headerOnly); //Merges from the given plugin into this one, unless there is already equal metadata present. //For 'enabled' and 'priority' metadata, use the given plugin's values, but if the 'priority' user value is zero, ignore it. @@ -204,7 +205,8 @@ namespace loot { void Tags(const std::set& tags); void DirtyInfo(const std::set& info); - Plugin& EvalAllConditions(loot::Game& game, const unsigned int language); + Plugin& EvalAllConditions(Game& game, const unsigned int language); + void ParseAllConditions(Game& game) const; bool HasNameOnly() const; bool IsRegexPlugin() const; bool LoadsBSA(const Game& game) const; diff --git a/src/backend/parsers.h b/src/backend/parsers.h index bd93daed..27105b25 100644 --- a/src/backend/parsers.h +++ b/src/backend/parsers.h @@ -400,7 +400,7 @@ namespace loot { template class condition_grammar : public qi::grammar { public: - condition_grammar() : condition_grammar::base_type(expression, "condition grammar") { + condition_grammar(Game& game, bool parseOnly) : condition_grammar::base_type(expression, "condition grammar"), _game(game), _parseOnly(parseOnly) { expression = compound [qi::labels::_val = qi::labels::_1] @@ -468,19 +468,18 @@ namespace loot { qi::on_error(invalidPathChars, phoenix::bind(&condition_grammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); } - void SetGame(loot::Game& g) { - game = &g; - } - private: qi::rule expression, compound, condition, function; qi::rule quotedStr, filePath, comparator; qi::rule invalidPathChars; - loot::Game * game; + Game& _game; + bool _parseOnly; //Eval's exact paths. Check for files and ghosted plugins. void CheckFile(bool& result, const std::string& file) { + if (_parseOnly) + return; BOOST_LOG_TRIVIAL(trace) << "Checking to see if the file \"" << file << "\" exists."; @@ -495,9 +494,9 @@ namespace loot { } if (IsPlugin(file)) - result = boost::filesystem::exists(game->DataPath() / file) || boost::filesystem::exists(game->DataPath() / (file + ".ghost")); + result = boost::filesystem::exists(_game.DataPath() / file) || boost::filesystem::exists(_game.DataPath() / (file + ".ghost")); else - result = boost::filesystem::exists(game->DataPath() / file); + result = boost::filesystem::exists(_game.DataPath() / file); if (result) BOOST_LOG_TRIVIAL(trace) << "The file does exist."; @@ -506,6 +505,8 @@ namespace loot { } void CheckRegex(bool& result, const std::string& regexStr) { + if (_parseOnly) + return; result = false; //Can't support a regex string where all path components may be regex, since this could //lead to massive scanning if an unfortunately-named directory is encountered. @@ -549,7 +550,7 @@ namespace loot { //Now we have a valid parent path and a regex filename. Check that //the parent path exists and is a directory. - boost::filesystem::path parent_path = game->DataPath() / parent; + boost::filesystem::path parent_path = _game.DataPath() / parent; if (!boost::filesystem::exists(parent_path) || !boost::filesystem::is_directory(parent_path)) { BOOST_LOG_TRIVIAL(trace) << "The path \"" << parent_path << "\" does not exist or is not a directory."; return; @@ -573,6 +574,8 @@ namespace loot { } void CheckSum(bool& result, const std::string& file, const uint32_t checksum) { + if (_parseOnly) + return; BOOST_LOG_TRIVIAL(trace) << "Checking the CRC of the file \"" << file << "\"."; @@ -582,29 +585,31 @@ namespace loot { } uint32_t crc; - unordered_map::iterator it = game->crcCache.find(boost::to_lower_copy(file)); + unordered_map::iterator it = _game.crcCache.find(boost::to_lower_copy(file)); - if (it != game->crcCache.end()) + if (it != _game.crcCache.end()) crc = it->second; else { if (file == "LOOT") crc = GetCrc32(boost::filesystem::absolute("LOOT.exe")); - if (boost::filesystem::exists(game->DataPath() / file)) - crc = GetCrc32(game->DataPath() / file); - else if (IsPlugin(file) && boost::filesystem::exists(game->DataPath() / (file + ".ghost"))) - crc = GetCrc32(game->DataPath() / (file + ".ghost")); + if (boost::filesystem::exists(_game.DataPath() / file)) + crc = GetCrc32(_game.DataPath() / file); + else if (IsPlugin(file) && boost::filesystem::exists(_game.DataPath() / (file + ".ghost"))) + crc = GetCrc32(_game.DataPath() / (file + ".ghost")); else { result = false; return; } - game->crcCache.emplace(boost::to_lower_copy(file), crc); + _game.crcCache.emplace(boost::to_lower_copy(file), crc); } result = checksum == crc; } void CheckVersion(bool& result, const std::string& file, const std::string& version, const std::string& comparator) { + if (_parseOnly) + return; BOOST_LOG_TRIVIAL(trace) << "Checking version of file \"" << file << "\"."; @@ -621,10 +626,10 @@ namespace loot { if (file == "LOOT") trueVersion = Version(boost::filesystem::absolute("LOOT.exe")); else if (IsPlugin(file)) { - Plugin plugin(*game, file, true); + Plugin plugin(_game, file, true); trueVersion = Version(plugin.Version()); } else - trueVersion = Version(game->DataPath() / file); + trueVersion = Version(_game.DataPath() / file); BOOST_LOG_TRIVIAL(trace) << "Version extracted: " << trueVersion.AsString(); @@ -640,10 +645,13 @@ namespace loot { } void CheckActive(bool& result, const std::string& file) { + if (_parseOnly) + return; + if (file == "LOOT") result = false; else - result = game->IsActive(file); + result = _game.IsActive(file); BOOST_LOG_TRIVIAL(trace) << "Active check result: " << result; }