From 2efa47f03f4cbfed1d6fc597f37542532ad7fb5e Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 29 Jan 2017 21:44:04 +0000 Subject: [PATCH] Add plugin data access functions to API Plugin now inherits from PluginInterface, and Plugin objects are now immutable and cached in shared_ptr objects. The Plugin class no longer inherits from PluginMetadata, so file data and metadata are kept separate and only brought together temporarily when sorting. Sorting no longer evaluates conditions and checks install validity, so such actions need to be performed before sorting (the validity checking isn't, as the data gets reloaded just before sorting) or after it. It should now be possible to reimplement the GUI using the API, though there are probably still a few holes in the API. --- CMakeLists.txt | 4 +- include/loot/game_interface.h | 52 +++++++ include/loot/metadata/plugin_metadata.h | 7 +- include/loot/plugin_interface.h | 61 ++++++++ resources/l10n/template.pot | 42 +++--- src/api/game.cpp | 31 ++-- src/api/game.h | 8 ++ src/backend/game/game.cpp | 2 +- src/backend/game/game_cache.cpp | 19 +-- src/backend/game/game_cache.h | 6 +- src/backend/metadata/condition_evaluator.cpp | 8 +- src/backend/plugin/plugin.cpp | 132 ++++++++---------- src/backend/plugin/plugin.h | 38 +++-- src/backend/plugin/plugin_sorter.cpp | 88 +++++------- src/backend/plugin/plugin_sorter.h | 22 ++- src/gui/query/editor_closed_query.h | 7 +- src/gui/query/get_conflicting_plugins_query.h | 15 +- src/gui/query/get_game_data_query.h | 43 +++--- src/gui/query/metadata_query.h | 100 ++++++++++--- src/gui/query/sort_plugins_query.h | 39 ++---- src/gui/query/update_masterlist_query.h | 27 ++-- src/tests/api/game_interface_test.h | 86 +++++++++++- src/tests/backend/game/game_cache_test.h | 17 +-- src/tests/backend/game/game_test.h | 12 +- src/tests/backend/plugin/plugin_sorter_test.h | 22 +-- src/tests/backend/plugin/plugin_test.h | 94 +++---------- src/tests/gui/state/game_test.h | 12 +- 27 files changed, 598 insertions(+), 396 deletions(-) create mode 100644 include/loot/plugin_interface.h 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) {