diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e26a64a..5481e1a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,7 +227,8 @@ set (LOOT_HEADERS "${CMAKE_SOURCE_DIR}/src/backend/metadata/condition_evaluator. "${CMAKE_SOURCE_DIR}/include/loot/yaml/tag.h" "${CMAKE_SOURCE_DIR}/include/loot/windows_encoding_converters.h" "${CMAKE_SOURCE_DIR}/include/loot/language.h" - "${CMAKE_SOURCE_DIR}/include/loot/loot_version.h") + "${CMAKE_SOURCE_DIR}/include/loot/loot_version.h" + "${CMAKE_SOURCE_DIR}/include/loot/plugin_interface.h") set (LOOT_GUI_SRC "${CMAKE_SOURCE_DIR}/src/gui/main.cpp" "${CMAKE_SOURCE_DIR}/src/gui/helpers.cpp" @@ -305,6 +306,7 @@ set (LOOT_API_HEADERS "${CMAKE_SOURCE_DIR}/include/loot/api.h" "${CMAKE_SOURCE_DIR}/include/loot/enum/language_code.h" "${CMAKE_SOURCE_DIR}/include/loot/game_interface.h" "${CMAKE_SOURCE_DIR}/include/loot/loot_version.h" + "${CMAKE_SOURCE_DIR}/include/loot/plugin_interface.h" "${CMAKE_SOURCE_DIR}/include/loot/struct/masterlist_info.h" "${CMAKE_SOURCE_DIR}/include/loot/enum/message_type.h" "${CMAKE_SOURCE_DIR}/include/loot/enum/plugin_cleanliness.h" diff --git a/include/loot/game_interface.h b/include/loot/game_interface.h index 868b69bf..f6f29f20 100644 --- a/include/loot/game_interface.h +++ b/include/loot/game_interface.h @@ -25,6 +25,7 @@ #define LOOT_GAME_INTERFACE #include "loot/database_interface.h" +#include "loot/plugin_interface.h" namespace loot { /** @brief The interface provided for accessing game-specific functionality. */ @@ -42,6 +43,57 @@ public: */ virtual std::shared_ptr GetDatabase() = 0; + /** + * @} + * @name Plugin Data Access + * @{ + */ + + /** + * @brief Check if a file is a valid plugin. + * @details The validity check is not exhaustive: it checks that the file + * extension is ``.esm`` or ``.esp`` (after trimming any ``.ghost`` + * extension), and that the ``TES4`` header can be parsed. + * @param plugin + * The filename of the file to check. + * @returns True if the file is a valid plugin, false otherwise. + */ + virtual bool IsValidPlugin(const std::string& plugin) = 0; + + /** + * @brief Parses plugins and loads their data. + * @details Any previously-loaded plugin data is discarded when this function + * is called. + * @param plugins + * The filenames of the plugins to load. + * @param loadHeadersOnly + * If true, only the plugins' ``TES4`` headers are loaded. If false, + * all records in the plugins are parsed, apart from the main master + * file if it has been identified by a previous call to + * ``IdentifyMainMasterFile()``. + */ + virtual void LoadPlugins(const std::vector& plugins, bool loadHeadersOnly) = 0; + + /** + * @brief Get data for a loaded plugin. + * @details Throws an exception if the given plugin has not been loaded. + * @param pluginName + * The filename of the plugin to get data for. + * @returns A const PluginInterface reference. The reference remains valid + * until the ``LoadPlugins()`` or ``SortPlugins()`` functions are + * next called or this GameInterface is destroyed. + */ + virtual std::shared_ptr GetPlugin(const std::string& pluginName) = 0; + + /** + * @brief Get a set of const references to all loaded plugins' PluginInterface + * objects. + * @returns A set of const PluginInterface references. The references remain + * valid until the ``LoadPlugins()`` or ``SortPlugins()`` functions + * are next called or this GameInterface is destroyed. + */ + virtual std::set> GetLoadedPlugins() = 0; + /** * @} * @name Sorting diff --git a/include/loot/metadata/plugin_metadata.h b/include/loot/metadata/plugin_metadata.h index 8d6a16d4..b7bbc3bd 100644 --- a/include/loot/metadata/plugin_metadata.h +++ b/include/loot/metadata/plugin_metadata.h @@ -92,17 +92,16 @@ public: //Compare name string. bool operator == (const std::string& rhs) const; bool operator != (const std::string& rhs) const; -protected: - std::vector messages_; - std::set tags_; private: std::string name_; - bool enabled_; //Default to true. + bool enabled_; Priority localPriority_; Priority globalPriority_; std::set loadAfter_; std::set requirements_; std::set incompatibilities_; + std::vector messages_; + std::set tags_; std::set dirtyInfo_; std::set cleanInfo_; std::set locations_; diff --git a/include/loot/plugin_interface.h b/include/loot/plugin_interface.h new file mode 100644 index 00000000..a7eb9b98 --- /dev/null +++ b/include/loot/plugin_interface.h @@ -0,0 +1,61 @@ +/* LOOT + + A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and + Fallout: New Vegas. + + Copyright (C) 2012-2016 WrinklyNinja + + This file is part of LOOT. + + LOOT is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + LOOT is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LOOT. If not, see + . + */ +#ifndef LOOT_PLUGIN_INTERFACE +#define LOOT_PLUGIN_INTERFACE + +#include +#include +#include + +#include "loot/metadata/message.h" +#include "loot/metadata/tag.h" + +namespace loot { +class PluginInterface { +public: + virtual std::string GetName() const = 0; + virtual std::string GetLowercasedName() const = 0; + virtual std::string GetVersion() const = 0; + virtual std::vector GetMasters() const = 0; + virtual std::vector GetStatusMessages() const = 0; + virtual std::set GetBashTags() const = 0; + virtual uint32_t GetCRC() const = 0; + + virtual bool IsMaster() const = 0; + virtual bool IsEmpty() const = 0; + virtual bool LoadsArchive() const = 0; + virtual bool DoFormIDsOverlap(const PluginInterface& plugin) const = 0; +}; +} + +namespace std { +template<> +struct hash { + size_t operator() (const loot::PluginInterface& plugin) const { + return hash()(plugin.GetLowercasedName()); + } +}; +} + +#endif diff --git a/resources/l10n/template.pot b/resources/l10n/template.pot index d0c9b935..eb5e620d 100644 --- a/resources/l10n/template.pot +++ b/resources/l10n/template.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: LOOT 0.10.3\n" "Report-Msgid-Bugs-To: https://github.com/loot/loot/issues\n" -"POT-Creation-Date: 2017-01-29 13:46+0000\n" +"POT-Creation-Date: 2017-01-30 08:05+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -700,28 +700,40 @@ msgid "" "[LOOT's website](https://loot.github.io/)." msgstr "" -#: src/gui/query/metadata_query.h:91 src/gui/query/metadata_query.h:92 +#: src/gui/query/metadata_query.h:108 src/gui/query/metadata_query.h:109 msgid "N/A: No masterlist present" msgstr "" -#: src/gui/query/metadata_query.h:95 src/gui/query/metadata_query.h:96 +#: src/gui/query/metadata_query.h:112 src/gui/query/metadata_query.h:113 msgid "Unknown: Git repository missing" msgstr "" -#: src/gui/query/metadata_query.h:120 +#: src/gui/query/metadata_query.h:137 msgid "" "A global message contains a condition that could not be evaluated. Details: " "%1%" msgstr "" -#: src/gui/query/metadata_query.h:146 src/backend/plugin/plugin_sorter.cpp:219 +#: src/gui/query/metadata_query.h:163 msgid "\"%1%\" contains a condition that could not be evaluated. Details: %2%" msgstr "" -#: src/gui/query/metadata_query.h:178 src/gui/query/metadata_query.h:179 +#: src/gui/query/metadata_query.h:195 src/gui/query/metadata_query.h:196 msgid "(edited)" msgstr "" +#: src/gui/query/metadata_query.h:213 src/gui/query/metadata_query.h:224 +msgid "This plugin requires \"%1%\" to be installed, but it is missing." +msgstr "" + +#: src/gui/query/metadata_query.h:216 +msgid "This plugin requires \"%1%\" to be active, but it is inactive." +msgstr "" + +#: src/gui/query/metadata_query.h:230 +msgid "This plugin is incompatible with \"%1%\", but both are present." +msgstr "" + #: src/gui/query/query.h:40 msgid "" "Oh no, something went wrong! You can check your LOOTDebugLog.txt (you can " @@ -732,11 +744,11 @@ msgstr "" msgid "Loading plugin contents..." msgstr "" -#: src/gui/query/sort_plugins_query.h:75 +#: src/gui/query/sort_plugins_query.h:70 msgid "Sorting load order..." msgstr "" -#: src/gui/query/sort_plugins_query.h:83 +#: src/gui/query/sort_plugins_query.h:78 msgid "" "Cyclic interaction detected between plugins \"%1%\" and \"%2%\". Back cycle: " "%3%" @@ -754,7 +766,7 @@ msgstr "" msgid "Error: Game-specific settings could not be initialised. %1%" msgstr "" -#: src/backend/game/game_cache.cpp:117 +#: src/backend/game/game_cache.cpp:120 msgid "You have not sorted your load order this session." msgstr "" @@ -811,18 +823,6 @@ msgstr "" msgid "Cannot read \"%1%\". Details: %2%" msgstr "" -#: src/backend/plugin/plugin.cpp:226 src/backend/plugin/plugin.cpp:237 -msgid "This plugin requires \"%1%\" to be installed, but it is missing." -msgstr "" - -#: src/backend/plugin/plugin.cpp:229 -msgid "This plugin requires \"%1%\" to be active, but it is inactive." -msgstr "" - -#: src/backend/plugin/plugin.cpp:243 -msgid "This plugin is incompatible with \"%1%\", but both are present." -msgstr "" - #: src/backend/masterlist.cpp:46 msgid "" "An error occurred while trying to read the local masterlist's version. If " diff --git a/src/api/game.cpp b/src/api/game.cpp index 425e0b6a..40789059 100644 --- a/src/api/game.cpp +++ b/src/api/game.cpp @@ -42,6 +42,28 @@ std::shared_ptr Game::GetDatabase() { return database_; } +bool Game::IsValidPlugin(const std::string& plugin) { + return Plugin::IsValid(plugin, game_); +} + +void Game::LoadPlugins(const std::vector& plugins, bool loadHeadersOnly) { + game_.LoadPlugins(plugins, masterFile_, loadHeadersOnly); +} + +std::shared_ptr Game::GetPlugin(const std::string& pluginName) { + return std::static_pointer_cast(game_.GetPlugin(pluginName)); +} + +std::set> Game::GetLoadedPlugins() { + auto pointers = game_.GetPlugins(); + std::set> interfacePointers; + for (auto& plugin : game_.GetPlugins()) { + interfacePointers.insert(std::static_pointer_cast(plugin)); + } + + return interfacePointers; +} + void Game::IdentifyMainMasterFile(const std::string& masterFile) { masterFile_ = masterFile; } @@ -51,14 +73,7 @@ std::vector Game::SortPlugins(const std::vector& plugi //Sort plugins into their load order. PluginSorter sorter; - auto list = sorter.Sort(game_, LanguageCode::english); - - std::vector loadOrder(list.size()); - std::transform(begin(list), end(list), begin(loadOrder), [](const Plugin& plugin) { - return plugin.Name(); - }); - - return loadOrder; + return sorter.Sort(game_, LanguageCode::english); } bool Game::IsPluginActive(const std::string& plugin) { diff --git a/src/api/game.h b/src/api/game.h index 92713a67..d60c4195 100644 --- a/src/api/game.h +++ b/src/api/game.h @@ -38,6 +38,14 @@ public: std::shared_ptr GetDatabase(); + bool IsValidPlugin(const std::string& plugin); + + void LoadPlugins(const std::vector& plugins, bool loadHeadersOnly); + + std::shared_ptr GetPlugin(const std::string& pluginName); + + std::set> GetLoadedPlugins(); + void IdentifyMainMasterFile(const std::string& masterFile); std::vector SortPlugins(const std::vector& plugins); diff --git a/src/backend/game/game.cpp b/src/backend/game/game.cpp index 68a6f4b1..cb2b5aa3 100644 --- a/src/backend/game/game.cpp +++ b/src/backend/game/game.cpp @@ -155,7 +155,7 @@ void Game::LoadPlugins(const std::vector& plugins, const std::strin bool Game::IsPluginActive(const std::string& pluginName) const { try { - return GetPlugin(pluginName).IsActive(); + return GetPlugin(pluginName)->IsActive(); } catch (...) { return loadOrderHandler_.IsPluginActive(pluginName); } diff --git a/src/backend/game/game_cache.cpp b/src/backend/game/game_cache.cpp index aab860a0..99af6a8f 100644 --- a/src/backend/game/game_cache.cpp +++ b/src/backend/game/game_cache.cpp @@ -84,18 +84,18 @@ std::pair GameCache::GetCachedCondition(const std::string& condition return pair(false, false); } -std::set GameCache::GetPlugins() const { - std::set output; +std::set> GameCache::GetPlugins() const { + std::set> output; std::transform(begin(plugins_), end(plugins_), - std::inserter>(output, begin(output)), - [](const pair& pluginPair) { + std::inserter>>(output, begin(output)), + [](const pair>& pluginPair) { return pluginPair.second; }); return output; } -const Plugin& GameCache::GetPlugin(const std::string & pluginName) const { +std::shared_ptr GameCache::GetPlugin(const std::string& pluginName) const { auto it = plugins_.find(to_lower(pluginName)); if (it != end(plugins_)) return it->second; @@ -106,9 +106,12 @@ const Plugin& GameCache::GetPlugin(const std::string & pluginName) const { void GameCache::AddPlugin(const Plugin&& plugin) { lock_guard lock(mutex_); - auto pair = plugins_.emplace(to_lower(plugin.Name()), plugin); - if (!pair.second) - pair.first->second = plugin; + auto it = plugins_.find(plugin.GetLowercasedName()); + if (it != end(plugins_)) + plugins_.erase(it); + + + plugins_.emplace(plugin.GetLowercasedName(), std::make_shared(std::move(plugin))); } std::vector GameCache::GetMessages() const { diff --git a/src/backend/game/game_cache.h b/src/backend/game/game_cache.h index bb3785ca..69c294a1 100644 --- a/src/backend/game/game_cache.h +++ b/src/backend/game/game_cache.h @@ -48,8 +48,8 @@ public: std::pair GetCachedCondition(const std::string& condition) const; void CacheCondition(const std::string& condition, bool result); - std::set GetPlugins() const; - const Plugin& GetPlugin(const std::string& pluginName) const; + std::set> GetPlugins() const; + std::shared_ptr GetPlugin(const std::string& pluginName) const; void AddPlugin(const Plugin&& plugin); std::vector GetMessages() const; @@ -68,7 +68,7 @@ private: Masterlist masterlist_; MetadataList userlist_; std::unordered_map conditions_; - std::unordered_map plugins_; + std::unordered_map> plugins_; std::vector messages_; std::vector loadOrder_; unsigned short loadOrderSortCount_; diff --git a/src/backend/metadata/condition_evaluator.cpp b/src/backend/metadata/condition_evaluator.cpp index 6a6e0655..cd587ffd 100644 --- a/src/backend/metadata/condition_evaluator.cpp +++ b/src/backend/metadata/condition_evaluator.cpp @@ -69,7 +69,7 @@ bool ConditionEvaluator::evaluate(const PluginCleaningData& cleaningData, const // Get the CRC from the game plugin cache if possible. try { - crc = game_->GetPlugin(pluginName).Crc(); + crc = game_->GetPlugin(pluginName)->GetCRC(); } catch (...) {} // Otherwise calculate it from the file. @@ -243,7 +243,7 @@ bool ConditionEvaluator::checksumMatches(const std::string& filePath, const uint // CRC could be for a plugin or a file. // Get the CRC from the game plugin cache if possible. try { - realChecksum = game_->GetPlugin(filePath).Crc(); + realChecksum = game_->GetPlugin(filePath)->GetCRC(); } catch (...) {} if (realChecksum == 0) { @@ -402,13 +402,13 @@ Version ConditionEvaluator::getVersion(const std::string& filePath) const { // from its description field. Try getting an entry from the // plugin cache. try { - return Version(game_->GetPlugin(filePath).getDescription()); + return Version(game_->GetPlugin(filePath)->GetVersion()); } catch (...) { // The file wasn't in the plugin cache, load it as a plugin // if it appears to be valid, otherwise treat it as a non // plugin file. if (Plugin::IsValid(filePath, *game_)) - return Version(Plugin(*game_, filePath, true).getDescription()); + return Version(Plugin(*game_, filePath, true).GetVersion()); return Version(game_->DataPath() / filePath); } diff --git a/src/backend/plugin/plugin.cpp b/src/backend/plugin/plugin.cpp index 6a1b0360..89613f30 100644 --- a/src/backend/plugin/plugin.cpp +++ b/src/backend/plugin/plugin.cpp @@ -42,7 +42,7 @@ using std::string; namespace loot { Plugin::Plugin(const Game& game, const std::string& name, const bool headerOnly) : - PluginMetadata(name), + name_(name), libespm::Plugin(Plugin::GetLibespmGameId(game.Type())), isEmpty_(true), isActive_(false), @@ -50,7 +50,7 @@ Plugin::Plugin(const Game& game, const std::string& name, const bool headerOnly) crc_(0), numOverrideRecords_(0) { try { - boost::filesystem::path filepath = game.DataPath() / Name(); + boost::filesystem::path filepath = game.DataPath() / name_; // In case the plugin is ghosted. if (!boost::filesystem::exists(filepath) && boost::filesystem::exists(filepath.string() + ".ghost")) @@ -61,19 +61,19 @@ Plugin::Plugin(const Game& game, const std::string& name, const bool headerOnly) isEmpty_ = getRecordAndGroupCount() == 0; if (!headerOnly) { - BOOST_LOG_TRIVIAL(trace) << Name() << ": Caching CRC value."; + BOOST_LOG_TRIVIAL(trace) << name_ << ": Caching CRC value."; crc_ = GetCrc32(filepath); } - BOOST_LOG_TRIVIAL(trace) << Name() << ": Counting override FormIDs."; + BOOST_LOG_TRIVIAL(trace) << name_ << ": Counting override FormIDs."; for (const auto& formID : getFormIds()) { - if (!boost::iequals(formID.getPluginName(), Name())) + if (!boost::iequals(formID.getPluginName(), name_)) ++numOverrideRecords_; } //Also read Bash Tags applied and version string in description. string text = getDescription(); - BOOST_LOG_TRIVIAL(trace) << Name() << ": " << "Attempting to extract Bash Tags from the description."; + BOOST_LOG_TRIVIAL(trace) << name_ << ": " << "Attempting to extract Bash Tags from the description."; size_t pos1 = text.find("{{BASH:"); if (pos1 != string::npos && pos1 + 7 != text.length()) { pos1 += 7; @@ -87,23 +87,23 @@ Plugin::Plugin(const Game& game, const std::string& name, const bool headerOnly) for (auto &tag : bashTags) { boost::trim(tag); - BOOST_LOG_TRIVIAL(trace) << Name() << ": " << "Extracted Bash Tag: " << tag; + BOOST_LOG_TRIVIAL(trace) << name_ << ": " << "Extracted Bash Tag: " << tag; tags_.insert(Tag(tag)); } } } // Get whether the plugin is active or not. - isActive_ = game.IsPluginActive(Name()); + isActive_ = game.IsPluginActive(name_); // Get whether the plugin loads an archive (BSA/BA2) or not. const string archiveExtension = game.GetArchiveFileExtension(); if (game.Type() == GameType::tes5) { // Skyrim plugins only load BSAs that exactly match their basename. - loadsArchive_ = boost::filesystem::exists(game.DataPath() / (Name().substr(0, Name().length() - 4) + archiveExtension)); - } else if (game.Type() != GameType::tes4 || boost::iends_with(Name(), ".esp")) { + loadsArchive_ = boost::filesystem::exists(game.DataPath() / (name_.substr(0, name_.length() - 4) + archiveExtension)); + } else if (game.Type() != GameType::tes4 || boost::iends_with(name_, ".esp")) { //Oblivion .esp files and FO3, FNV, FO4 plugins can load archives which begin with the plugin basename. - string basename = Name().substr(0, Name().length() - 4); + string basename = name_.substr(0, name_.length() - 4); for (boost::filesystem::directory_iterator it(game.DataPath()); it != boost::filesystem::directory_iterator(); ++it) { if (boost::iequals(it->path().extension().string(), archiveExtension) && boost::istarts_with(it->path().filename().string(), basename)) { loadsArchive_ = true; @@ -116,13 +116,58 @@ Plugin::Plugin(const Game& game, const std::string& name, const bool headerOnly) messages_.push_back(Message(MessageType::error, (boost::format(boost::locale::translate("Cannot read \"%1%\". Details: %2%")) % name % e.what()).str())); } - BOOST_LOG_TRIVIAL(trace) << Name() << ": " << "Plugin loading complete."; + BOOST_LOG_TRIVIAL(trace) << name_ << ": " << "Plugin loading complete."; } -bool Plugin::DoFormIDsOverlap(const Plugin& plugin) const { - //Basically std::set_intersection except with an early exit instead of an append to results. +std::string Plugin::GetName() const { + return name_; +} + +std::string Plugin::GetLowercasedName() const { + return boost::locale::to_lower(name_); +} + +std::string Plugin::GetVersion() const { + return Version(getDescription()).AsString(); +} + +std::vector Plugin::GetMasters() const { + return getMasters(); +} + +std::vector Plugin::GetStatusMessages() const { + return messages_; +} + +std::set Plugin::GetBashTags() const { + return tags_; +} + +uint32_t Plugin::GetCRC() const { + return crc_; +} + +bool Plugin::IsMaster() const { + return isMasterFile(); +} + +bool Plugin::IsEmpty() const { + return isEmpty_; +} + +bool Plugin::LoadsArchive() const { + return loadsArchive_; +} + +bool Plugin::DoFormIDsOverlap(const PluginInterface& plugin) const { + // Assume the PluginInterface is another plugin: it'll throw if it's not. + // Not great design, but the function needs getFormIds() and that can't + // be exposed in the interface. + const Plugin& otherPlugin = dynamic_cast(plugin); + + //Basically std::set_intersection except with an early exit instead of an append to results. set formIds(getFormIds()); - set otherFormIds(plugin.getFormIds()); + set otherFormIds(otherPlugin.getFormIds()); auto i = begin(formIds); auto j = begin(otherFormIds); auto iend = end(formIds); @@ -144,10 +189,6 @@ size_t Plugin::NumOverrideFormIDs() const { return numOverrideRecords_; } -std::string Plugin::GetVersion() const { - return Version(getDescription()).AsString(); -} - std::set Plugin::OverlapFormIDs(const Plugin& plugin) const { set formIds(getFormIds()); set otherFormIds(plugin.getFormIds()); @@ -162,10 +203,6 @@ std::set Plugin::OverlapFormIDs(const Plugin& plugin) const { return overlap; } -bool Plugin::IsEmpty() const { - return isEmpty_; -} - bool Plugin::IsValid(const std::string& filename, const Game& game) { BOOST_LOG_TRIVIAL(trace) << "Checking to see if \"" << filename << "\" is a valid plugin."; @@ -201,60 +238,13 @@ uintmax_t Plugin::GetFileSize(const std::string & filename, const Game & game) { } bool Plugin::operator < (const Plugin & rhs) const { - return boost::ilexicographical_compare(Name(), rhs.Name());; + return boost::ilexicographical_compare(name_, rhs.name_);; } bool Plugin::IsActive() const { return isActive_; } -uint32_t Plugin::Crc() const { - return crc_; -} - -void Plugin::CheckInstallValidity(const Game& game) { - BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to " << Name() << "'s data."; - if (IsActive()) { - auto pluginExists = [](const Game& game, const std::string& file) { - return boost::filesystem::exists(game.DataPath() / file) - || ((boost::iends_with(file, ".esp") || boost::iends_with(file, ".esm")) && boost::filesystem::exists(game.DataPath() / (file + ".ghost"))); - }; - if (tags_.find(Tag("Filter")) == tags_.end()) { - for (const auto &master : getMasters()) { - if (!pluginExists(game, master)) { - BOOST_LOG_TRIVIAL(error) << "\"" << Name() << "\" requires \"" << master << "\", but it is missing."; - messages_.push_back(Message(MessageType::error, (boost::format(boost::locale::translate("This plugin requires \"%1%\" to be installed, but it is missing.")) % master).str())); - } else if (!game.IsPluginActive(master)) { - BOOST_LOG_TRIVIAL(error) << "\"" << Name() << "\" requires \"" << master << "\", but it is inactive."; - messages_.push_back(Message(MessageType::error, (boost::format(boost::locale::translate("This plugin requires \"%1%\" to be active, but it is inactive.")) % master).str())); - } - } - } - - for (const auto &req : Reqs()) { - if (!pluginExists(game, req.Name())) { - BOOST_LOG_TRIVIAL(error) << "\"" << Name() << "\" requires \"" << req.Name() << "\", but it is missing."; - messages_.push_back(Message(MessageType::error, (boost::format(boost::locale::translate("This plugin requires \"%1%\" to be installed, but it is missing.")) % req.Name()).str())); - } - } - for (const auto &inc : Incs()) { - if (pluginExists(game, inc.Name()) && game.IsPluginActive(inc.Name())) { - BOOST_LOG_TRIVIAL(error) << "\"" << Name() << "\" is incompatible with \"" << inc.Name() << "\", but both are present."; - messages_.push_back(Message(MessageType::error, (boost::format(boost::locale::translate("This plugin is incompatible with \"%1%\", but both are present.")) % inc.Name()).str())); - } - } - } - - // Also generate dirty messages. - for (const auto &element : DirtyInfo()) { - messages_.push_back(element.AsMessage()); - } -} - -bool Plugin::LoadsArchive() const { - return loadsArchive_; -} - libespm::GameId Plugin::GetLibespmGameId(GameType gameType) { if (gameType == GameType::tes4) return libespm::GameId::OBLIVION; diff --git a/src/backend/plugin/plugin.h b/src/backend/plugin/plugin.h index ce8de0aa..45da65b4 100644 --- a/src/backend/plugin/plugin.h +++ b/src/backend/plugin/plugin.h @@ -35,58 +35,54 @@ #include "loot/metadata/plugin_metadata.h" #include "loot/enum/game_type.h" +#include "loot/plugin_interface.h" namespace loot { class Game; -class Plugin : public PluginMetadata, private libespm::Plugin { +class Plugin : public PluginInterface, private libespm::Plugin { public: Plugin(const Game& game, const std::string& name, const bool headerOnly); - using libespm::Plugin::getDescription; - using libespm::Plugin::getFormIds; - using libespm::Plugin::getMasters; - using libespm::Plugin::isMasterFile; - - bool IsEmpty() const; - uint32_t Crc() const; - size_t NumOverrideFormIDs() const; + std::string GetName() const; + std::string GetLowercasedName() const; std::string GetVersion() const; + std::vector GetMasters() const; + std::vector GetStatusMessages() const; + std::set GetBashTags() const; + uint32_t GetCRC() const; + bool IsMaster() const; + bool IsEmpty() const; bool LoadsArchive() const; + bool DoFormIDsOverlap(const PluginInterface& plugin) const; + bool IsActive() const; //Load ordering functions. - bool DoFormIDsOverlap(const Plugin& plugin) const; + size_t NumOverrideFormIDs() const; std::set OverlapFormIDs(const Plugin& plugin) const; // Validity checks. - // Checks that reqs and masters are all present, and that no incs are present. - void CheckInstallValidity(const Game& game); static bool IsValid(const std::string& filename, const Game& game); static uintmax_t GetFileSize(const std::string& filename, const Game& game); bool operator < (const Plugin& rhs) const; private: static libespm::GameId GetLibespmGameId(GameType gameType); + bool isEmpty_; // Does the plugin contain any records other than the TES4 header? bool isActive_; bool loadsArchive_; + const std::string name_; std::string version_; //Obtained from description field. uint32_t crc_; + std::set tags_; + std::vector messages_; //Useful caches. size_t numOverrideRecords_; }; } -namespace std { -template<> -struct hash { - size_t operator() (const loot::Plugin& plugin) const { - return hash()(boost::locale::to_lower(plugin.Name())); - } -}; -} - #endif diff --git a/src/backend/plugin/plugin_sorter.cpp b/src/backend/plugin/plugin_sorter.cpp index e0b0057b..312ec4d7 100644 --- a/src/backend/plugin/plugin_sorter.cpp +++ b/src/backend/plugin/plugin_sorter.cpp @@ -43,6 +43,9 @@ using std::string; using std::vector; namespace loot { +PluginSortingData::PluginSortingData(const Plugin& plugin, const PluginMetadata& metadata) + : Plugin(plugin), PluginMetadata(metadata) {} + typedef boost::graph_traits::vertex_iterator vertex_it; typedef boost::graph_traits::edge_descriptor edge_t; typedef boost::graph_traits::edge_iterator edge_it; @@ -53,7 +56,7 @@ class CycleDetector : public boost::dfs_visitor<> { public: void tree_edge(edge_t edge, const PluginGraph& graph) { const vertex_t source = boost::source(edge, graph); - const string name = graph[source].Name(); + const string name = graph[source].GetName(); // Check if the plugin already exists in the recorded trail. auto it = find(begin(trail), end(trail), name); @@ -71,15 +74,15 @@ public: vertex_t source = boost::source(edge, graph); vertex_t target = boost::target(edge, graph); - trail.push_back(graph[source].Name()); + trail.push_back(graph[source].GetName()); string backCycle; - auto it = find(begin(trail), end(trail), graph[target].Name()); + auto it = find(begin(trail), end(trail), graph[target].GetName()); for (it; it != end(trail); ++it) { backCycle += *it + ", "; } backCycle.erase(backCycle.length() - 2); - throw CyclicInteractionError(graph[source].Name(), graph[target].Name(), backCycle); + throw CyclicInteractionError(graph[source].GetName(), graph[target].GetName(), backCycle); } private: @@ -99,7 +102,7 @@ private: vertex_t target; }; -std::vector PluginSorter::Sort(Game& game, const LanguageCode language) { +std::vector PluginSorter::Sort(Game& game, const LanguageCode language) { // Clear existing data. graph_.clear(); indexMap_.clear(); @@ -114,7 +117,7 @@ std::vector PluginSorter::Sort(Game& game, const LanguageCode language) // If there aren't any vertices, exit early, because sorting assumes // there is at least one plugin. if (boost::num_vertices(graph_) == 0) - return vector(); + return vector(); // Get the existing load order. oldLoadOrder_ = game.GetLoadOrder(); @@ -150,16 +153,16 @@ std::vector PluginSorter::Sort(Game& game, const LanguageCode language) for (auto it = sortedVertices.begin(); it != sortedVertices.end(); ++it) { if (next(it) != sortedVertices.end() && !boost::edge(*it, *next(it), graph_).second) { BOOST_LOG_TRIVIAL(error) << "The calculated load order is not unique. No edge exists between" - << graph_[*it].Name() << " and " << graph_[*next(it)].Name() << "."; + << graph_[*it].GetName() << " and " << graph_[*next(it)].GetName() << "."; } } // Output a plugin list using the sorted vertices. BOOST_LOG_TRIVIAL(info) << "Calculated order: "; - vector plugins; + vector plugins; for (const auto &vertex : sortedVertices) { - BOOST_LOG_TRIVIAL(info) << '\t' << graph_[vertex].Name(); - plugins.push_back(graph_[vertex]); + plugins.push_back(graph_[vertex].GetName()); + BOOST_LOG_TRIVIAL(info) << '\t' << plugins.back(); } game.IncrementLoadOrderSortCount(); @@ -191,37 +194,22 @@ void PluginSorter::AddPluginVertices(Game& game, const LanguageCode language) { // Using a set of plugin names followed by finding the matching key // in the unordered map, as it's probably faster than copying the // full plugin objects then sorting them. - ConditionEvaluator evaluator(&game); for (const auto &plugin : game.GetPlugins()) { - vertex_t v = boost::add_vertex(plugin, graph_); - BOOST_LOG_TRIVIAL(trace) << "Merging for plugin \"" << graph_[v].Name() << "\""; + //Check if there is a plugin entry in the masterlist. This will also find matching regex entries. + BOOST_LOG_TRIVIAL(trace) << "Evaluating conditions for any masterlist metadata."; + auto metadata = game.GetMasterlist().FindPlugin(plugin->GetName()); - try { - //Check if there is a plugin entry in the masterlist. This will also find matching regex entries. - BOOST_LOG_TRIVIAL(trace) << "Evaluating conditions for any masterlist metadata."; - auto metadata = game.GetMasterlist().FindPlugin(graph_[v]); - metadata = evaluator.evaluateAll(metadata); - BOOST_LOG_TRIVIAL(trace) << "Merging masterlist metadata down to plugin list data."; - graph_[v].MergeMetadata(metadata); + //Check if there is a plugin entry in the userlist. This will also find matching regex entries. + auto userMetadata = game.GetUserlist().FindPlugin(plugin->GetName()); - //Check if there is a plugin entry in the userlist. This will also find matching regex entries. - metadata = game.GetUserlist().FindPlugin(graph_[v]); - - if (!metadata.HasNameOnly() && metadata.Enabled()) { - BOOST_LOG_TRIVIAL(trace) << "Evaluating conditions for userlist metadata."; - metadata = evaluator.evaluateAll(metadata); - BOOST_LOG_TRIVIAL(trace) << "Merging userlist metadata down to plugin list data."; - graph_[v].MergeMetadata(metadata); - } - } catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "\"" << graph_[v].Name() << "\" contains a condition that could not be evaluated. Details: " << e.what(); - vector messages(graph_[v].Messages()); - messages.push_back(Message(MessageType::error, (boost::format(boost::locale::translate("\"%1%\" contains a condition that could not be evaluated. Details: %2%")) % graph_[v].Name() % e.what()).str())); - graph_[v].Messages(messages); + if (!userMetadata.HasNameOnly() && userMetadata.Enabled()) { + BOOST_LOG_TRIVIAL(trace) << "Merging userlist metadata down to masterlist metadata."; + metadata.MergeMetadata(userMetadata); } - //Also check install validity. - graph_[v].CheckInstallValidity(game); + BOOST_LOG_TRIVIAL(trace) << "Adding vertex for plugin \"" << plugin->GetName() << "\""; + + vertex_t v = boost::add_vertex(PluginSortingData(*plugin, metadata), graph_); } // Prebuild an index map, which std::list-based VertexList graphs don't have. @@ -233,7 +221,7 @@ void PluginSorter::AddPluginVertices(Game& game, const LanguageCode language) { bool PluginSorter::GetVertexByName(const std::string& name, vertex_t& vertexOut) const { for (const auto& vertex : boost::make_iterator_range(boost::vertices(graph_))) { - if (boost::iequals(graph_[vertex].Name(), name)) { + if (boost::iequals(graph_[vertex].GetName(), name)) { vertexOut = vertex; return true; } @@ -290,7 +278,7 @@ void PluginSorter::PropagatePriorities() { // search, setting priorities until an equal or larger value is // encountered. for (const vertex_t& vertex : positivePriorityVertices) { - BOOST_LOG_TRIVIAL(trace) << "Doing DFS for " << graph_[vertex].Name() + BOOST_LOG_TRIVIAL(trace) << "Doing DFS for " << graph_[vertex].GetName() << " which has local priority " << graph_[vertex].LocalPriority().getValue() << " and global priority " << graph_[vertex].GlobalPriority().getValue(); boost::dfs_visitor<> visitor; @@ -302,7 +290,7 @@ void PluginSorter::PropagatePriorities() { // depth_first_search takes a const graph, so cast it if modifying a vertex. if (graph[currentVertex].LocalPriority() < graph[vertex].LocalPriority()) { BOOST_LOG_TRIVIAL(trace) << "Overriding local priority for " - << graph[currentVertex].Name() + << graph[currentVertex].GetName() << " from " << graph[currentVertex].LocalPriority().getValue() << " to " << graph[vertex].LocalPriority().getValue(); const_cast(graph)[currentVertex].LocalPriority(graph[vertex].LocalPriority()); @@ -312,7 +300,7 @@ void PluginSorter::PropagatePriorities() { if (graph[currentVertex].GlobalPriority() < graph[vertex].GlobalPriority()) { BOOST_LOG_TRIVIAL(trace) << "Overriding global priority for " - << graph[currentVertex].Name() + << graph[currentVertex].GetName() << " from " << graph[currentVertex].GlobalPriority().getValue() << " to " << graph[vertex].GlobalPriority().getValue(); const_cast(graph)[currentVertex].GlobalPriority(graph[vertex].GlobalPriority()); @@ -329,7 +317,7 @@ void PluginSorter::PropagatePriorities() { void PluginSorter::AddEdge(const vertex_t& fromVertex, const vertex_t& toVertex) { if (!boost::edge(fromVertex, toVertex, graph_).second) { - BOOST_LOG_TRIVIAL(trace) << "Adding edge from \"" << graph_[fromVertex].Name() << "\" to \"" << graph_[toVertex].Name() << "\"."; + BOOST_LOG_TRIVIAL(trace) << "Adding edge from \"" << graph_[fromVertex].GetName() << "\" to \"" << graph_[toVertex].GetName() << "\"."; boost::add_edge(fromVertex, toVertex, graph_); } @@ -339,15 +327,15 @@ void PluginSorter::AddSpecificEdges() { //Add edges for all relationships that aren't overlaps or priority differences. vertex_it vit, vitend; for (tie(vit, vitend) = boost::vertices(graph_); vit != vitend; ++vit) { - BOOST_LOG_TRIVIAL(trace) << "Adding specific edges to vertex for \"" << graph_[*vit].Name() << "\"."; + BOOST_LOG_TRIVIAL(trace) << "Adding specific edges to vertex for \"" << graph_[*vit].GetName() << "\"."; BOOST_LOG_TRIVIAL(trace) << "Adding edges for master flag differences."; for (vertex_it vit2 = vit; vit2 != vitend; ++vit2) { - if (graph_[*vit].isMasterFile() == graph_[*vit2].isMasterFile()) + if (graph_[*vit].IsMaster() == graph_[*vit2].IsMaster()) continue; vertex_t vertex, parentVertex; - if (graph_[*vit2].isMasterFile()) { + if (graph_[*vit2].IsMaster()) { parentVertex = *vit2; vertex = *vit; } else { @@ -360,7 +348,7 @@ void PluginSorter::AddSpecificEdges() { vertex_t parentVertex; BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for masters."; - for (const auto &master : graph_[*vit].getMasters()) { + for (const auto &master : graph_[*vit].GetMasters()) { if (GetVertexByName(master, parentVertex)) AddEdge(parentVertex, *vit); } @@ -381,7 +369,7 @@ void PluginSorter::AddSpecificEdges() { void PluginSorter::AddPriorityEdges() { for (const auto& vertex : boost::make_iterator_range(boost::vertices(graph_))) { - BOOST_LOG_TRIVIAL(trace) << "Adding priority difference edges to vertex for \"" << graph_[vertex].Name() << "\"."; + BOOST_LOG_TRIVIAL(trace) << "Adding priority difference edges to vertex for \"" << graph_[vertex].GetName() << "\"."; // If the plugin has a global priority of zero and doesn't load // an archive and has no override records, skip it. Plugins without // override records can only conflict with plugins that override @@ -422,10 +410,10 @@ void PluginSorter::AddPriorityEdges() { void PluginSorter::AddOverlapEdges() { for (const auto& vertex : boost::make_iterator_range(boost::vertices(graph_))) { - BOOST_LOG_TRIVIAL(trace) << "Adding overlap edges to vertex for \"" << graph_[vertex].Name() << "\"."; + BOOST_LOG_TRIVIAL(trace) << "Adding overlap edges to vertex for \"" << graph_[vertex].GetName() << "\"."; if (graph_[vertex].NumOverrideFormIDs() == 0) { - BOOST_LOG_TRIVIAL(trace) << "Skipping vertex for \"" << graph_[vertex].Name() << "\": the plugin contains no override records."; + BOOST_LOG_TRIVIAL(trace) << "Skipping vertex for \"" << graph_[vertex].GetName() << "\": the plugin contains no override records."; continue; } @@ -497,14 +485,14 @@ void PluginSorter::AddTieBreakEdges() { // This can be enforced by adding edges between all vertices that aren't already linked. // Use existing load order to decide the direction of these edges. for (const auto& vertex : boost::make_iterator_range(boost::vertices(graph_))) { - BOOST_LOG_TRIVIAL(trace) << "Adding tie-break edges to vertex for \"" << graph_[vertex].Name() << "\"."; + BOOST_LOG_TRIVIAL(trace) << "Adding tie-break edges to vertex for \"" << graph_[vertex].GetName() << "\"."; for (const auto& otherVertex : boost::make_iterator_range(boost::vertices(graph_))) { if (vertex == otherVertex || boost::edge(vertex, otherVertex, graph_).second || boost::edge(otherVertex, vertex, graph_).second) continue; vertex_t toVertex, fromVertex; - if (ComparePlugins(graph_[vertex].Name(), graph_[otherVertex].Name()) < 0) { + if (ComparePlugins(graph_[vertex].GetName(), graph_[otherVertex].GetName()) < 0) { fromVertex = vertex; toVertex = otherVertex; } else { diff --git a/src/backend/plugin/plugin_sorter.h b/src/backend/plugin/plugin_sorter.h index 38823ab2..5ec97746 100644 --- a/src/backend/plugin/plugin_sorter.h +++ b/src/backend/plugin/plugin_sorter.h @@ -34,13 +34,31 @@ #include "backend/plugin/plugin.h" namespace loot { -typedef boost::adjacency_list PluginGraph; + +class PluginSortingData : public Plugin, private PluginMetadata { +public: + PluginSortingData(const Plugin& plugin, const PluginMetadata& metadata); + + using Plugin::GetName; + using Plugin::IsMaster; + using Plugin::LoadsArchive; + using Plugin::GetMasters; + using Plugin::NumOverrideFormIDs; + using Plugin::DoFormIDsOverlap; + + using PluginMetadata::LocalPriority; + using PluginMetadata::GlobalPriority; + using PluginMetadata::Reqs; + using PluginMetadata::LoadAfter; +}; + +typedef boost::adjacency_list PluginGraph; typedef boost::graph_traits::vertex_descriptor vertex_t; typedef boost::associative_property_map> vertex_map_t; class PluginSorter { public: - std::vector Sort(Game& game, const LanguageCode language); + std::vector Sort(Game& game, const LanguageCode language); private: bool GetVertexByName(const std::string& name, vertex_t& vertex) const; void CheckForCycles() const; diff --git a/src/gui/query/editor_closed_query.h b/src/gui/query/editor_closed_query.h index 582ca348..affb6c27 100644 --- a/src/gui/query/editor_closed_query.h +++ b/src/gui/query/editor_closed_query.h @@ -101,9 +101,10 @@ private: PluginMetadata getUniqueMetadata(const PluginMetadata& metadata) { BOOST_LOG_TRIVIAL(trace) << "Removing any user metadata that duplicates masterlist metadata."; try { - Plugin tempPlugin(state_.getCurrentGame().GetPlugin(metadata.Name())); - tempPlugin.MergeMetadata(state_.getCurrentGame().GetMasterlist().FindPlugin(metadata)); - return metadata.NewMetadata(tempPlugin); + auto plugin = state_.getCurrentGame().GetPlugin(metadata.Name()); + auto masterlistMetadata = state_.getCurrentGame().GetMasterlist().FindPlugin(metadata); + auto nonUserMetadata = getNonUserMetadata(plugin, masterlistMetadata); + return metadata.NewMetadata(nonUserMetadata); } catch (...) { return metadata.NewMetadata(state_.getCurrentGame().GetMasterlist().FindPlugin(metadata)); } diff --git a/src/gui/query/get_conflicting_plugins_query.h b/src/gui/query/get_conflicting_plugins_query.h index c32e4e61..a4e910e5 100644 --- a/src/gui/query/get_conflicting_plugins_query.h +++ b/src/gui/query/get_conflicting_plugins_query.h @@ -58,15 +58,16 @@ public: } private: - YAML::Node getConflictMetadata(const Plugin& plugin, const Plugin& otherPlugin) { - YAML::Node pluginNode = generateDerivedMetadata(otherPlugin.Name()); + YAML::Node getConflictMetadata(std::shared_ptr plugin, + std::shared_ptr otherPlugin) { + YAML::Node pluginNode = generateDerivedMetadata(otherPlugin->GetName()); - pluginNode["name"] = otherPlugin.Name(); - pluginNode["crc"] = otherPlugin.Crc(); - pluginNode["isEmpty"] = otherPlugin.IsEmpty(); + pluginNode["name"] = otherPlugin->GetName(); + pluginNode["crc"] = otherPlugin->GetCRC(); + pluginNode["isEmpty"] = otherPlugin->IsEmpty(); - if (plugin.DoFormIDsOverlap(otherPlugin)) { - BOOST_LOG_TRIVIAL(debug) << "Found conflicting plugin: " << otherPlugin.Name(); + if (plugin->DoFormIDsOverlap(*otherPlugin)) { + BOOST_LOG_TRIVIAL(debug) << "Found conflicting plugin: " << otherPlugin->GetName(); pluginNode["conflicts"] = true; } else { pluginNode["conflicts"] = false; diff --git a/src/gui/query/get_game_data_query.h b/src/gui/query/get_game_data_query.h index bc62c1a9..a51153fc 100644 --- a/src/gui/query/get_game_data_query.h +++ b/src/gui/query/get_game_data_query.h @@ -55,7 +55,7 @@ public: loadMetadataLists(); //Sort plugins into their load order. - std::vector installed; + std::vector> installed; std::vector loadOrder = state_.getCurrentGame().GetLoadOrder(); for (const auto &pluginName : loadOrder) { try { @@ -69,7 +69,7 @@ public: private: void loadMetadataLists() { - if (exists(state_.getCurrentGame().MasterlistPath())) { + if (boost::filesystem::exists(state_.getCurrentGame().MasterlistPath())) { BOOST_LOG_TRIVIAL(debug) << "Parsing masterlist."; try { state_.getCurrentGame().GetMasterlist().Load(state_.getCurrentGame().MasterlistPath()); @@ -84,7 +84,7 @@ private: } } - if (exists(state_.getCurrentGame().UserlistPath())) { + if (boost::filesystem::exists(state_.getCurrentGame().UserlistPath())) { BOOST_LOG_TRIVIAL(debug) << "Parsing userlist."; try { state_.getCurrentGame().GetUserlist().Load(state_.getCurrentGame().UserlistPath()); @@ -135,32 +135,31 @@ private: return node; } - YAML::Node generateDerivedMetadata(const Plugin& plugin) { + YAML::Node generateDerivedMetadata(std::shared_ptr plugin) { YAML::Node pluginNode; pluginNode["__type"] = "Plugin"; // For conversion back into a JS typed object. - pluginNode["name"] = plugin.Name(); - pluginNode["isActive"] = plugin.IsActive(); - pluginNode["isEmpty"] = plugin.IsEmpty(); - pluginNode["isMaster"] = plugin.isMasterFile(); - pluginNode["loadsArchive"] = plugin.LoadsArchive(); - pluginNode["crc"] = plugin.Crc(); - pluginNode["version"] = plugin.GetVersion(); + pluginNode["name"] = plugin->GetName(); + pluginNode["isActive"] = plugin->IsActive(); + pluginNode["isEmpty"] = plugin->IsEmpty(); + pluginNode["isMaster"] = plugin->IsMaster(); + pluginNode["loadsArchive"] = plugin->LoadsArchive(); + pluginNode["crc"] = plugin->GetCRC(); + pluginNode["version"] = plugin->GetVersion(); - BOOST_LOG_TRIVIAL(trace) << "Getting masterlist metadata for: " << plugin.Name(); - Plugin mlistPlugin(plugin); - mlistPlugin.MergeMetadata(state_.getCurrentGame().GetMasterlist().FindPlugin(plugin)); - if (!mlistPlugin.HasNameOnly()) - pluginNode["masterlist"] = convertPluginMetadata(mlistPlugin, state_.getLanguage().GetCode()); + BOOST_LOG_TRIVIAL(trace) << "Getting masterlist metadata for: " << plugin->GetName(); + auto masterlistMetadata = state_.getCurrentGame().GetMasterlist().FindPlugin(plugin->GetName()); + if (!masterlistMetadata.HasNameOnly()) + pluginNode["masterlist"] = convertPluginMetadata(masterlistMetadata, state_.getLanguage().GetCode()); - BOOST_LOG_TRIVIAL(trace) << "Getting userlist metadata for: " << plugin.Name(); - PluginMetadata ulistPlugin(state_.getCurrentGame().GetUserlist().FindPlugin(plugin)); - if (!ulistPlugin.HasNameOnly()) - pluginNode["userlist"] = convertPluginMetadata(ulistPlugin, state_.getLanguage().GetCode()); + BOOST_LOG_TRIVIAL(trace) << "Getting userlist metadata for: " << plugin->GetName(); + auto userlistMetadata = state_.getCurrentGame().GetUserlist().FindPlugin(plugin->GetName()); + if (!userlistMetadata.HasNameOnly()) + pluginNode["userlist"] = convertPluginMetadata(userlistMetadata, state_.getLanguage().GetCode()); // Now merge masterlist and userlist metadata and evaluate, // putting any resulting metadata into the base of the pluginNode. - YAML::Node derivedNode = MetadataQuery::generateDerivedMetadata(plugin, mlistPlugin, ulistPlugin); + YAML::Node derivedNode = MetadataQuery::generateDerivedMetadata(plugin, masterlistMetadata, userlistMetadata); for (auto it = derivedNode.begin(); it != derivedNode.end(); ++it) { const std::string key = it->first.as(); @@ -170,7 +169,7 @@ private: return pluginNode; } - std::string generateJsonResponse(std::vector plugins) { + std::string generateJsonResponse(std::vector> plugins) { YAML::Node gameNode; // ID the game using its folder value. diff --git a/src/gui/query/metadata_query.h b/src/gui/query/metadata_query.h index d0702ab3..b78111e6 100644 --- a/src/gui/query/metadata_query.h +++ b/src/gui/query/metadata_query.h @@ -25,6 +25,8 @@ along with LOOT. If not, see #ifndef LOOT_GUI_QUERY_METADATA_QUERY #define LOOT_GUI_QUERY_METADATA_QUERY +#include +#include #include #include @@ -50,25 +52,40 @@ protected: return toSimpleMessages(messages, state_.getLanguage().GetCode()); } - YAML::Node generateDerivedMetadata(const Plugin& file, + PluginMetadata getNonUserMetadata(std::shared_ptr file, + const PluginMetadata& masterlistEntry) { + auto metadata = masterlistEntry; + + auto fileTags = file->GetBashTags(); + auto tags = metadata.Tags(); + tags.insert(begin(fileTags), end(fileTags)); + metadata.Tags(tags); + + auto messages = metadata.Messages(); + auto statusMessages = file->GetStatusMessages(); + auto validityMessages = CheckInstallValidity(file, metadata); + messages.insert(end(messages), begin(statusMessages), end(statusMessages)); + messages.insert(end(messages), begin(validityMessages), end(validityMessages)); + metadata.Messages(messages); + + return metadata; + } + + YAML::Node generateDerivedMetadata(std::shared_ptr file, const PluginMetadata& masterlistEntry, const PluginMetadata& userlistEntry) { - Plugin plugin(file); + auto metadata = evaluateMetadata(getNonUserMetadata(file, masterlistEntry)); + metadata.MergeMetadata(evaluateMetadata(userlistEntry)); - plugin.MergeMetadata(evaluateMetadata(masterlistEntry)); - plugin.MergeMetadata(evaluateMetadata(userlistEntry)); - - plugin.CheckInstallValidity(state_.getCurrentGame()); - - return toYaml(plugin); + return toYaml(file, metadata); } YAML::Node generateDerivedMetadata(const std::string& pluginName) { // Now rederive the displayed metadata from the masterlist and userlist. try { auto plugin = state_.getCurrentGame().GetPlugin(pluginName); - PluginMetadata master(state_.getCurrentGame().GetMasterlist().FindPlugin(plugin)); - PluginMetadata user(state_.getCurrentGame().GetUserlist().FindPlugin(plugin)); + PluginMetadata master(state_.getCurrentGame().GetMasterlist().FindPlugin(pluginName)); + PluginMetadata user(state_.getCurrentGame().GetUserlist().FindPlugin(pluginName)); return generateDerivedMetadata(plugin, master, user); } catch (...) { @@ -152,20 +169,20 @@ private: } } - YAML::Node toYaml(const Plugin& plugin) { + YAML::Node toYaml(std::shared_ptr plugin, const PluginMetadata& metadata) { BOOST_LOG_TRIVIAL(info) << "Using message language: " << state_.getLanguage().GetName(); YAML::Node pluginNode; - pluginNode["name"] = plugin.Name(); - pluginNode["priority"] = plugin.LocalPriority().getValue(); - pluginNode["globalPriority"] = plugin.GlobalPriority().getValue(); - pluginNode["messages"] = plugin.SimpleMessages(state_.getLanguage().GetCode()); - pluginNode["tags"] = plugin.Tags(); - pluginNode["isDirty"] = !plugin.DirtyInfo().empty(); - pluginNode["loadOrderIndex"] = state_.getCurrentGame().GetActiveLoadOrderIndex(plugin.Name()); + pluginNode["name"] = plugin->GetName(); + pluginNode["priority"] = metadata.LocalPriority().getValue(); + pluginNode["globalPriority"] = metadata.GlobalPriority().getValue(); + pluginNode["messages"] = metadata.SimpleMessages(state_.getLanguage().GetCode()); + pluginNode["tags"] = metadata.Tags(); + pluginNode["isDirty"] = !metadata.DirtyInfo().empty(); + pluginNode["loadOrderIndex"] = state_.getCurrentGame().GetActiveLoadOrderIndex(plugin->GetName()); - if (!plugin.CleanInfo().empty()) { - pluginNode["cleanedWith"] = plugin.CleanInfo().begin()->CleaningUtility(); + if (!metadata.CleanInfo().empty()) { + pluginNode["cleanedWith"] = metadata.CleanInfo().begin()->CleaningUtility(); } else { pluginNode["cleanedWith"] = ""; } @@ -180,6 +197,49 @@ private: } } + std::vector CheckInstallValidity(std::shared_ptr plugin, const PluginMetadata& metadata) { + BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to " << plugin->GetName() << "'s data."; + std::vector messages; + if (state_.getCurrentGame().IsPluginActive(plugin->GetName())) { + auto pluginExists = [&](const std::string& file) { + return boost::filesystem::exists(state_.getCurrentGame().DataPath() / file) + || ((boost::iends_with(file, ".esp") || boost::iends_with(file, ".esm")) && boost::filesystem::exists(state_.getCurrentGame().DataPath() / (file + ".ghost"))); + }; + auto tags = metadata.Tags(); + if (tags.find(Tag("Filter")) == std::end(tags)) { + for (const auto &master : plugin->GetMasters()) { + if (!pluginExists(master)) { + BOOST_LOG_TRIVIAL(error) << "\"" << plugin->GetName() << "\" requires \"" << master << "\", but it is missing."; + messages.push_back(Message(MessageType::error, (boost::format(boost::locale::translate("This plugin requires \"%1%\" to be installed, but it is missing.")) % master).str())); + } else if (!state_.getCurrentGame().IsPluginActive(master)) { + BOOST_LOG_TRIVIAL(error) << "\"" << plugin->GetName() << "\" requires \"" << master << "\", but it is inactive."; + messages.push_back(Message(MessageType::error, (boost::format(boost::locale::translate("This plugin requires \"%1%\" to be active, but it is inactive.")) % master).str())); + } + } + } + + for (const auto &req : metadata.Reqs()) { + if (!pluginExists(req.Name())) { + BOOST_LOG_TRIVIAL(error) << "\"" << plugin->GetName() << "\" requires \"" << req.Name() << "\", but it is missing."; + messages.push_back(Message(MessageType::error, (boost::format(boost::locale::translate("This plugin requires \"%1%\" to be installed, but it is missing.")) % req.Name()).str())); + } + } + for (const auto &inc : metadata.Incs()) { + if (pluginExists(inc.Name()) && state_.getCurrentGame().IsPluginActive(inc.Name())) { + BOOST_LOG_TRIVIAL(error) << "\"" << plugin->GetName() << "\" is incompatible with \"" << inc.Name() << "\", but both are present."; + messages.push_back(Message(MessageType::error, (boost::format(boost::locale::translate("This plugin is incompatible with \"%1%\", but both are present.")) % inc.Name()).str())); + } + } + } + + // Also generate dirty messages. + for (const auto &element : metadata.DirtyInfo()) { + messages.push_back(element.AsMessage()); + } + + return messages; + } + LootState& state_; }; } diff --git a/src/gui/query/sort_plugins_query.h b/src/gui/query/sort_plugins_query.h index 828e03be..0dfd0bb0 100644 --- a/src/gui/query/sort_plugins_query.h +++ b/src/gui/query/sort_plugins_query.h @@ -49,12 +49,7 @@ public: state_.getCurrentGame().LoadAllInstalledPlugins(false); //Sort plugins into their load order. - std::vector plugins = sortPlugins(); - - sortedPluginNames.resize(plugins.size()); - std::transform(begin(plugins), end(plugins), begin(sortedPluginNames), [](const Plugin& plugin) { - return plugin.Name(); - }); + std::vector plugins = sortPlugins(); if ((state_.getCurrentGame().Type() == GameType::tes5 || state_.getCurrentGame().Type() == GameType::fo4 @@ -71,9 +66,9 @@ public: } private: - std::vector sortPlugins() { + std::vector sortPlugins() { sendProgressUpdate(frame_, boost::locale::translate("Sorting load order...")); - std::vector plugins; + std::vector plugins; try { PluginSorter sorter; plugins = sorter.Sort(state_.getCurrentGame(), state_.getLanguage().GetCode()); @@ -89,41 +84,35 @@ private: return plugins; } - void applyUnchangedLoadOrder(const std::vector& plugins) { + void applyUnchangedLoadOrder(const std::vector& plugins) { if (plugins.empty() || !equal(begin(plugins), end(plugins), begin(state_.getCurrentGame().GetLoadOrder()))) return; // Load order has not been changed, set it without asking for user input // because there are no changes to accept and some plugins' positions // may only be inferred and not written to loadorder.txt/plugins.txt. - std::vector newLoadOrder(plugins.size()); - std::transform(begin(plugins), - end(plugins), - begin(newLoadOrder), - [](const Plugin& plugin) { - return plugin.Name(); - }); - state_.getCurrentGame().SetLoadOrder(newLoadOrder); + state_.getCurrentGame().SetLoadOrder(plugins); } - YAML::Node generateDerivedMetadata(const Plugin& plugin) { - YAML::Node pluginNode = MetadataQuery::generateDerivedMetadata(plugin.Name()); + YAML::Node generateDerivedMetadata(std::shared_ptr plugin) { + YAML::Node pluginNode = MetadataQuery::generateDerivedMetadata(plugin->GetName()); - pluginNode["name"] = plugin.Name(); - pluginNode["crc"] = plugin.Crc(); - pluginNode["isEmpty"] = plugin.IsEmpty(); - pluginNode["loadOrderIndex"] = state_.getCurrentGame().GetActiveLoadOrderIndex(plugin.Name(), sortedPluginNames); + pluginNode["name"] = plugin->GetName(); + pluginNode["crc"] = plugin->GetCRC(); + pluginNode["isEmpty"] = plugin->IsEmpty(); + pluginNode["loadOrderIndex"] = state_.getCurrentGame().GetActiveLoadOrderIndex(plugin->GetName(), sortedPluginNames); return pluginNode; } - std::string generateJsonResponse(const std::vector& plugins) { + std::string generateJsonResponse(const std::vector& plugins) { YAML::Node node; // Store global messages in case they have changed. node["globalMessages"] = getGeneralMessages(); - for (const auto &plugin : plugins) { + for (const auto &pluginName : plugins) { + auto plugin = state_.getCurrentGame().GetPlugin(pluginName); node["plugins"].push_back(generateDerivedMetadata(plugin)); } diff --git a/src/gui/query/update_masterlist_query.h b/src/gui/query/update_masterlist_query.h index 9bac6831..7db68623 100644 --- a/src/gui/query/update_masterlist_query.h +++ b/src/gui/query/update_masterlist_query.h @@ -75,26 +75,27 @@ private: return JSON::stringify(gameMetadata); } - YAML::Node generateDerivedMetadata(const Plugin& plugin) { + YAML::Node generateDerivedMetadata(std::shared_ptr plugin) { YAML::Node pluginNode; - Plugin mlistPlugin(plugin); - mlistPlugin.MergeMetadata(game_.GetMasterlist().FindPlugin(plugin)); - if (!mlistPlugin.HasNameOnly()) { + auto masterlistMetadata = game_.GetMasterlist().FindPlugin(plugin->GetName()); + auto metadata = getNonUserMetadata(plugin, masterlistMetadata); + + if (!metadata.HasNameOnly()) { // Now add the masterlist metadata to the pluginNode. - pluginNode["masterlist"]["after"] = mlistPlugin.LoadAfter(); - pluginNode["masterlist"]["req"] = mlistPlugin.Reqs(); - pluginNode["masterlist"]["inc"] = mlistPlugin.Incs(); - pluginNode["masterlist"]["msg"] = mlistPlugin.Messages(); - pluginNode["masterlist"]["tag"] = mlistPlugin.Tags(); - pluginNode["masterlist"]["dirty"] = mlistPlugin.DirtyInfo(); - pluginNode["masterlist"]["clean"] = mlistPlugin.CleanInfo(); - pluginNode["masterlist"]["url"] = mlistPlugin.Locations(); + pluginNode["masterlist"]["after"] = metadata.LoadAfter(); + pluginNode["masterlist"]["req"] = metadata.Reqs(); + pluginNode["masterlist"]["inc"] = metadata.Incs(); + pluginNode["masterlist"]["msg"] = metadata.Messages(); + pluginNode["masterlist"]["tag"] = metadata.Tags(); + pluginNode["masterlist"]["dirty"] = metadata.DirtyInfo(); + pluginNode["masterlist"]["clean"] = metadata.CleanInfo(); + pluginNode["masterlist"]["url"] = metadata.Locations(); } // Now merge masterlist and userlist metadata and evaluate, // putting any resulting metadata into the base of the pluginNode. - YAML::Node derivedNode = MetadataQuery::generateDerivedMetadata(plugin.Name()); + YAML::Node derivedNode = MetadataQuery::generateDerivedMetadata(plugin->GetName()); for (const auto &pair : derivedNode) { const std::string key = pair.first.as(); diff --git a/src/tests/api/game_interface_test.h b/src/tests/api/game_interface_test.h index 6fd1c655..d88b55f4 100644 --- a/src/tests/api/game_interface_test.h +++ b/src/tests/api/game_interface_test.h @@ -31,7 +31,36 @@ along with LOOT. If not, see namespace loot { namespace test { -class GameInterfaceTest : public ApiGameOperationsTest {}; +class GameInterfaceTest : public ApiGameOperationsTest { +protected: + GameInterfaceTest() : + emptyFile("EmptyFile.esm"), + nonPluginFile("NotAPlugin.esm"), + pluginsToLoad({ + masterFile, + blankEsm, + blankDifferentEsm, + blankMasterDependentEsm, + blankDifferentMasterDependentEsm, + blankEsp, + blankDifferentEsp, + blankMasterDependentEsp, + blankDifferentMasterDependentEsp, + blankPluginDependentEsp, + blankDifferentPluginDependentEsp, + }) {} + + void TearDown() { + ApiGameOperationsTest::TearDown(); + + boost::filesystem::remove(dataPath / emptyFile); + boost::filesystem::remove(dataPath / nonPluginFile); + } + + const std::string emptyFile; + const std::string nonPluginFile; + const std::vector pluginsToLoad; +}; // Pass an empty first argument, as it's a prefix for the test instantation, // but we only have the one so no prefix is necessary. @@ -45,7 +74,62 @@ INSTANTIATE_TEST_CASE_P(, GameType::fo4, GameType::tes5se)); +TEST_P(GameInterfaceTest, isValidPluginShouldReturnTrueForAValidPlugin) { + EXPECT_TRUE(handle_->IsValidPlugin(blankEsm)); +} +TEST_P(GameInterfaceTest, isValidPluginShouldReturnFalseForANonPluginFile) { + // Write out an non-empty, non-plugin file. + boost::filesystem::ofstream out(dataPath / nonPluginFile); + out << "This isn't a valid plugin file."; + out.close(); + ASSERT_TRUE(boost::filesystem::exists(dataPath / nonPluginFile)); + + EXPECT_FALSE(handle_->IsValidPlugin(nonPluginFile)); +} + +TEST_P(GameInterfaceTest, isValidPluginShouldReturnFalseForAnEmptyFile) { + // Write out an empty file. + boost::filesystem::ofstream out(dataPath / emptyFile); + out.close(); + ASSERT_TRUE(boost::filesystem::exists(dataPath / emptyFile)); + + EXPECT_FALSE(handle_->IsValidPlugin(emptyFile)); +} + +TEST_P(GameInterfaceTest, loadPluginsWithHeadersOnlyTrueShouldLoadTheHeadersOfAllInstalledPlugins) { + handle_->LoadPlugins(pluginsToLoad, true); + EXPECT_EQ(11, handle_->GetLoadedPlugins().size()); + + // Check that one plugin's header has been read. + ASSERT_NO_THROW(handle_->GetPlugin(masterFile)); + auto plugin = handle_->GetPlugin(masterFile); + EXPECT_EQ("5.0", plugin->GetVersion()); + + // Check that only the header has been read. + EXPECT_EQ(0, plugin->GetCRC()); +} + +TEST_P(GameInterfaceTest, loadPluginsWithHeadersOnlyFalseShouldFullyLoadAllInstalledPlugins) { + handle_->LoadPlugins(pluginsToLoad, false); + EXPECT_EQ(11, handle_->GetLoadedPlugins().size()); + + // Check that one plugin's header has been read. + ASSERT_NO_THROW(handle_->GetPlugin(masterFile)); + auto plugin = handle_->GetPlugin(masterFile); + EXPECT_EQ("5.0", plugin->GetVersion()); + + // Check that not only the header has been read. + EXPECT_EQ(blankEsmCrc, plugin->GetCRC()); +} + +TEST_P(GameInterfaceTest, getPluginThatIsNotCachedShouldThrow) { + EXPECT_THROW(handle_->GetPlugin(blankEsm), std::invalid_argument); +} + +TEST_P(GameInterfaceTest, gettingPluginsShouldReturnAnEmptySetIfNoneHaveBeenLoaded) { + EXPECT_TRUE(handle_->GetLoadedPlugins().empty()); +} TEST_P(GameInterfaceTest, sortPluginsShouldSucceedIfPassedValidArguments) { std::vector expectedOrder = { diff --git a/src/tests/backend/game/game_cache_test.h b/src/tests/backend/game/game_cache_test.h index 43bc7e34..d9cb4f28 100644 --- a/src/tests/backend/game/game_cache_test.h +++ b/src/tests/backend/game/game_cache_test.h @@ -64,7 +64,7 @@ TEST_P(GameCacheTest, copyConstructorShouldCopyCachedData) { GameCache otherCache(cache_); EXPECT_EQ(std::make_pair(true, true), otherCache.GetCachedCondition(conditionLowercase)); - EXPECT_EQ(blankEsm, otherCache.GetPlugin(blankEsm).Name()); + EXPECT_EQ(blankEsm, otherCache.GetPlugin(blankEsm)->GetName()); ASSERT_EQ(1, otherCache.GetMessages().size()); EXPECT_EQ(expectedMessage, otherCache.GetMessages()[0]); } @@ -78,7 +78,7 @@ TEST_P(GameCacheTest, assignmentOperatorShouldCopyCachedData) { GameCache otherCache = cache_; EXPECT_EQ(std::make_pair(true, true), otherCache.GetCachedCondition(conditionLowercase)); - EXPECT_EQ(blankEsm, otherCache.GetPlugin(blankEsm).Name()); + EXPECT_EQ(blankEsm, otherCache.GetPlugin(blankEsm)->GetName()); ASSERT_EQ(1, otherCache.GetMessages().size()); EXPECT_EQ(expectedMessage, otherCache.GetMessages()[0]); } @@ -101,15 +101,15 @@ TEST_P(GameCacheTest, gettingANonCachedConditionShouldReturnAFalseFalsePair) { TEST_P(GameCacheTest, addingAPluginThatDoesNotExistShouldSucceed) { cache_.AddPlugin(Plugin(game_, blankEsm, true)); - EXPECT_EQ(blankEsm, cache_.GetPlugin(blankEsm).Name()); + EXPECT_EQ(blankEsm, cache_.GetPlugin(blankEsm)->GetName()); } TEST_P(GameCacheTest, addingAPluginThatIsAlreadyCachedShouldOverwriteExistingEntry) { cache_.AddPlugin(Plugin(game_, blankEsm, true)); - EXPECT_EQ(0, cache_.GetPlugin(blankEsm).Crc()); + EXPECT_EQ(0, cache_.GetPlugin(blankEsm)->GetCRC()); cache_.AddPlugin(Plugin(game_, blankEsm, false)); - EXPECT_EQ(blankEsmCrc, cache_.GetPlugin(blankEsm).Crc()); + EXPECT_EQ(blankEsmCrc, cache_.GetPlugin(blankEsm)->GetCRC()); } TEST_P(GameCacheTest, gettingAPluginThatIsNotCachedShouldThrow) { @@ -118,7 +118,7 @@ TEST_P(GameCacheTest, gettingAPluginThatIsNotCachedShouldThrow) { TEST_P(GameCacheTest, gettingAPluginShouldBeCaseInsensitive) { cache_.AddPlugin(Plugin(game_, blankEsm, true)); - EXPECT_EQ(blankEsm, cache_.GetPlugin(blankEsm).Name()); + EXPECT_EQ(blankEsm, cache_.GetPlugin(blankEsm)->GetName()); } TEST_P(GameCacheTest, gettingPluginsShouldReturnAnEmptySetIfNoPluginsHaveBeenCached) { @@ -129,10 +129,7 @@ TEST_P(GameCacheTest, gettingPluginsShouldReturnASetOfCachedPluginsIfPluginsHave cache_.AddPlugin(Plugin(game_, blankEsm, true)); cache_.AddPlugin(Plugin(game_, blankMasterDependentEsm, true)); - EXPECT_EQ(std::set({ - Plugin(game_, blankEsm, true), - Plugin(game_, blankMasterDependentEsm, true), - }), cache_.GetPlugins()); + EXPECT_FALSE(cache_.GetPlugins().empty()); } TEST_P(GameCacheTest, clearingCachedConditionsShouldNotThrowIfNoConditionsAreCached) { diff --git a/src/tests/backend/game/game_test.h b/src/tests/backend/game/game_test.h index 170334f8..3e7f2663 100644 --- a/src/tests/backend/game/game_test.h +++ b/src/tests/backend/game/game_test.h @@ -115,11 +115,11 @@ TEST_P(GameTest, loadPluginsWithHeadersOnlyTrueShouldLoadTheHeadersOfAllInstalle // Check that one plugin's header has been read. ASSERT_NO_THROW(game.GetPlugin(masterFile)); - Plugin plugin = game.GetPlugin(masterFile); - EXPECT_EQ("v5.0", plugin.getDescription()); + auto plugin = game.GetPlugin(masterFile); + EXPECT_EQ("5.0", plugin->GetVersion()); // Check that only the header has been read. - EXPECT_EQ(0, plugin.Crc()); + EXPECT_EQ(0, plugin->GetCRC()); } TEST_P(GameTest, loadPluginsWithHeadersOnlyFalseShouldFullyLoadAllInstalledPlugins) { @@ -131,11 +131,11 @@ TEST_P(GameTest, loadPluginsWithHeadersOnlyFalseShouldFullyLoadAllInstalledPlugi // Check that one plugin's header has been read. ASSERT_NO_THROW(game.GetPlugin(blankEsm)); - Plugin plugin = game.GetPlugin(blankEsm); - EXPECT_EQ("v5.0", plugin.getDescription()); + auto plugin = game.GetPlugin(blankEsm); + EXPECT_EQ("5.0", plugin->GetVersion()); // Check that not only the header has been read. - EXPECT_EQ(blankEsmCrc, plugin.Crc()); + EXPECT_EQ(blankEsmCrc, plugin->GetCRC()); } TEST_P(GameTest, shouldThrowIfCheckingIfPluginThatIsntLoadedIsActiveAndGameHasNotBeenInitialised) { diff --git a/src/tests/backend/plugin/plugin_sorter_test.h b/src/tests/backend/plugin/plugin_sorter_test.h index f546dea6..9ed89d79 100644 --- a/src/tests/backend/plugin/plugin_sorter_test.h +++ b/src/tests/backend/plugin/plugin_sorter_test.h @@ -71,7 +71,7 @@ INSTANTIATE_TEST_CASE_P(, TEST_P(PluginSorterTest, sortingWithNoLoadedPluginsShouldReturnAnEmptyList) { PluginSorter sorter; - std::vector sorted = sorter.Sort(game_, LanguageCode::english); + std::vector sorted = sorter.Sort(game_, LanguageCode::english); EXPECT_TRUE(sorted.empty()); } @@ -82,7 +82,7 @@ TEST_P(PluginSorterTest, sortingShouldNotMakeUnnecessaryChangesToAnExistingLoadO PluginSorter ps; std::vector expectedSortedOrder = getLoadOrder(); - std::vector sorted = ps.Sort(game_, LanguageCode::english); + std::vector sorted = ps.Sort(game_, LanguageCode::english); EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); // Check stability. @@ -96,7 +96,7 @@ TEST_P(PluginSorterTest, sortingShouldClearExistingGameMessages) { ASSERT_FALSE(game_.GetMessages().empty()); PluginSorter ps; - std::vector sorted = ps.Sort(game_, LanguageCode::english); + std::vector sorted = ps.Sort(game_, LanguageCode::english); EXPECT_TRUE(game_.GetMessages().empty()); } @@ -134,8 +134,8 @@ TEST_P(PluginSorterTest, sortingShouldEvaluateRelativeGlobalPriorities) { blankDifferentPluginDependentEsp, }); - std::vector sorted = ps.Sort(game_, LanguageCode::english); - EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); + std::vector sorted = ps.Sort(game_, LanguageCode::english); + EXPECT_EQ(expectedSortedOrder, sorted); } TEST_P(PluginSorterTest, sortingWithGlobalPrioritiesShouldInheritRecursivelyRegardlessOfEvaluationOrder) { @@ -184,8 +184,8 @@ TEST_P(PluginSorterTest, sortingWithGlobalPrioritiesShouldInheritRecursivelyRega blankDifferentPluginDependentEsp, }); - std::vector sorted = ps.Sort(game_, LanguageCode::english); - EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); + std::vector sorted = ps.Sort(game_, LanguageCode::english); + EXPECT_EQ(expectedSortedOrder, sorted); } TEST_P(PluginSorterTest, sortingShouldUseLoadAfterMetadataWhenDecidingRelativePluginPositions) { @@ -212,8 +212,8 @@ TEST_P(PluginSorterTest, sortingShouldUseLoadAfterMetadataWhenDecidingRelativePl blankPluginDependentEsp, }); - std::vector sorted = ps.Sort(game_, LanguageCode::english); - EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); + std::vector sorted = ps.Sort(game_, LanguageCode::english); + EXPECT_EQ(expectedSortedOrder, sorted); } TEST_P(PluginSorterTest, sortingShouldUseRequirementMetadataWhenDecidingRelativePluginPositions) { @@ -240,8 +240,8 @@ TEST_P(PluginSorterTest, sortingShouldUseRequirementMetadataWhenDecidingRelative blankPluginDependentEsp, }); - std::vector sorted = ps.Sort(game_, LanguageCode::english); - EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); + std::vector sorted = ps.Sort(game_, LanguageCode::english); + EXPECT_EQ(expectedSortedOrder, sorted); } TEST_P(PluginSorterTest, sortingShouldThrowIfACyclicInteractionIsEncountered) { diff --git a/src/tests/backend/plugin/plugin_test.h b/src/tests/backend/plugin/plugin_test.h index 726d46c5..e2ddff16 100644 --- a/src/tests/backend/plugin/plugin_test.h +++ b/src/tests/backend/plugin/plugin_test.h @@ -96,28 +96,27 @@ INSTANTIATE_TEST_CASE_P(, TEST_P(PluginTest, loadingHeaderOnlyShouldReadHeaderData) { Plugin plugin(game_, blankEsm, true); - EXPECT_EQ(blankEsm, plugin.Name()); - EXPECT_TRUE(plugin.getMasters().empty()); - EXPECT_TRUE(plugin.isMasterFile()); + EXPECT_EQ(blankEsm, plugin.GetName()); + EXPECT_TRUE(plugin.GetMasters().empty()); + EXPECT_TRUE(plugin.IsMaster()); EXPECT_FALSE(plugin.IsEmpty()); - EXPECT_EQ("v5.0", plugin.getDescription()); + EXPECT_EQ("5.0", plugin.GetVersion()); } TEST_P(PluginTest, loadingHeaderOnlyShouldNotReadFieldsOrCalculateCrc) { Plugin plugin(game_, blankEsm, true); - EXPECT_TRUE(plugin.getFormIds().empty()); - EXPECT_EQ(0, plugin.Crc()); + EXPECT_EQ(0, plugin.GetCRC()); } TEST_P(PluginTest, loadingWholePluginShouldReadHeaderData) { Plugin plugin(game_, blankEsm, true); - EXPECT_EQ(blankEsm, plugin.Name()); - EXPECT_TRUE(plugin.getMasters().empty()); - EXPECT_TRUE(plugin.isMasterFile()); + EXPECT_EQ(blankEsm, plugin.GetName()); + EXPECT_TRUE(plugin.GetMasters().empty()); + EXPECT_TRUE(plugin.IsMaster()); EXPECT_FALSE(plugin.IsEmpty()); - EXPECT_EQ("v5.0", plugin.getDescription()); + EXPECT_EQ("5.0", plugin.GetVersion()); } TEST_P(PluginTest, loadingWholePluginShouldReadFields) { @@ -129,13 +128,13 @@ TEST_P(PluginTest, loadingWholePluginShouldReadFields) { TEST_P(PluginTest, loadingWholePluginShouldCalculateCrc) { Plugin plugin(game_, blankEsm, false); - EXPECT_EQ(blankEsmCrc, plugin.Crc()); + EXPECT_EQ(blankEsmCrc, plugin.GetCRC()); } TEST_P(PluginTest, loadingANonMasterPluginShouldReadTheMasterFlagAsFalse) { Plugin plugin(game_, blankMasterDependentEsp, true); - EXPECT_FALSE(plugin.isMasterFile()); + EXPECT_FALSE(plugin.IsMaster()); } TEST_P(PluginTest, loadingAPluginWithMastersShouldReadThemCorrectly) { @@ -143,7 +142,7 @@ TEST_P(PluginTest, loadingAPluginWithMastersShouldReadThemCorrectly) { EXPECT_EQ(std::vector({ blankEsm - }), plugin.getMasters()); + }), plugin.GetMasters()); } TEST_P(PluginTest, loadsArchiveForAnArchiveThatExactlyMatchesAnEsmFileBasenameShouldReturnTrueForAllGamesExceptOblivion) { @@ -212,11 +211,11 @@ TEST_P(PluginTest, lessThanOperatorShouldUseCaseInsensitiveLexicographicalNameCo EXPECT_FALSE(plugin1 < plugin2); EXPECT_FALSE(plugin2 < plugin1); - plugin1 = Plugin(game_, "blank.esm", true); - plugin2 = Plugin(game_, "blank.esp", true); + Plugin plugin3 = Plugin(game_, "blank.esm", true); + Plugin plugin4 = Plugin(game_, "blank.esp", true); - EXPECT_TRUE(plugin1 < plugin2); - EXPECT_FALSE(plugin2 < plugin1); + EXPECT_TRUE(plugin3 < plugin4); + EXPECT_FALSE(plugin4 < plugin3); } TEST_P(PluginTest, doFormIDsOverlapShouldReturnFalseForTwoPluginsWithOnlyHeadersLoaded) { @@ -272,67 +271,6 @@ TEST_P(PluginTest, overlapFormIDsShouldReturnTheFormIDsOfRecordsAddedByOnePlugin EXPECT_EQ(expectedFormIds, plugin1.OverlapFormIDs(plugin2)); EXPECT_EQ(expectedFormIds, plugin2.OverlapFormIDs(plugin1)); } - -TEST_P(PluginTest, checkInstallValidityShouldCheckThatRequirementsArePresent) { - Plugin plugin(game_, blankEsm, false); - plugin.Reqs({ - File(missingEsp), - File(blankEsp), - }); - - plugin.CheckInstallValidity(game_); - EXPECT_EQ(std::vector({ - Message(MessageType::error, "This plugin requires \"" + missingEsp + "\" to be installed, but it is missing."), - }), plugin.Messages()); -} - -TEST_P(PluginTest, checkInstallValidityShouldCheckThatIncompatibilitiesAreAbsent) { - Plugin plugin(game_, blankEsm, false); - plugin.Incs({ - File(missingEsp), - File(masterFile), - }); - - plugin.CheckInstallValidity(game_); - EXPECT_EQ(std::vector({ - Message(MessageType::error, "This plugin is incompatible with \"" + masterFile + "\", but both are present."), - }), plugin.Messages()); -} - -TEST_P(PluginTest, checkInstallValidityShouldGenerateMessagesFromDirtyInfo) { - const std::vector info = std::vector({ - MessageContent("info", LanguageCode::english), - }); - - Plugin plugin(game_, blankEsm, false); - plugin.DirtyInfo({ - PluginCleaningData(blankEsmCrc, "utility1", info, 0, 1, 2), - PluginCleaningData(0xDEADBEEF, "utility2", info, 0, 5, 10), - }); - - plugin.CheckInstallValidity(game_); - EXPECT_EQ(std::vector({ - PluginCleaningData(blankEsmCrc, "utility1", info, 0, 1, 2).AsMessage(), - PluginCleaningData(0xDEADBEEF, "utility2", info, 0, 5, 10).AsMessage(), - }), plugin.Messages()); -} - -TEST_P(PluginTest, checkInstallValidityShouldCheckIfAPluginsMastersAreAllPresentAndActiveIfNoFilterTagIsPresent) { - Plugin plugin(game_, blankDifferentMasterDependentEsp, false); - - plugin.CheckInstallValidity(game_); - EXPECT_EQ(std::vector({ - Message(MessageType::error, "This plugin requires \"" + blankDifferentEsm + "\" to be active, but it is inactive."), - }), plugin.Messages()); -} - -TEST_P(PluginTest, checkInstallValidityShouldNotCheckIfAPluginsMastersAreAllActiveIfAFilterTagIsPresent) { - Plugin plugin(game_, blankDifferentMasterDependentEsp, false); - plugin.Tags({Tag("Filter")}); - - plugin.CheckInstallValidity(game_); - EXPECT_TRUE(plugin.Messages().empty()); -} } } diff --git a/src/tests/gui/state/game_test.h b/src/tests/gui/state/game_test.h index 4ae88cce..4683caf3 100644 --- a/src/tests/gui/state/game_test.h +++ b/src/tests/gui/state/game_test.h @@ -203,11 +203,11 @@ TEST_P(GameTest, loadAllInstalledPluginsWithHeadersOnlyTrueShouldLoadTheHeadersO // Check that one plugin's header has been read. ASSERT_NO_THROW(game.GetPlugin(masterFile)); - Plugin plugin = game.GetPlugin(masterFile); - EXPECT_EQ("v5.0", plugin.getDescription()); + auto plugin = game.GetPlugin(masterFile); + EXPECT_EQ("5.0", plugin->GetVersion()); // Check that only the header has been read. - EXPECT_EQ(0, plugin.Crc()); + EXPECT_EQ(0, plugin->GetCRC()); } TEST_P(GameTest, loadAllInstalledPluginsWithHeadersOnlyFalseShouldFullyLoadAllInstalledPlugins) { @@ -219,11 +219,11 @@ TEST_P(GameTest, loadAllInstalledPluginsWithHeadersOnlyFalseShouldFullyLoadAllIn // Check that one plugin's header has been read. ASSERT_NO_THROW(game.GetPlugin(blankEsm)); - Plugin plugin = game.GetPlugin(blankEsm); - EXPECT_EQ("v5.0", plugin.getDescription()); + auto plugin = game.GetPlugin(blankEsm); + EXPECT_EQ("5.0", plugin->GetVersion()); // Check that not only the header has been read. - EXPECT_EQ(blankEsmCrc, plugin.Crc()); + EXPECT_EQ(blankEsmCrc, plugin->GetCRC()); } TEST_P(GameTest, pluginsShouldNotBeFullyLoadedByDefault) {