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.
This commit is contained in:
Oliver Hamlet
2017-02-06 18:02:18 +00:00
parent 83f0caa68e
commit 2efa47f03f
27 changed files with 598 additions and 396 deletions
+3 -1
View File
@@ -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"
+52
View File
@@ -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<DatabaseInterface> 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<std::string>& 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<const PluginInterface> 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<std::shared_ptr<const PluginInterface>> GetLoadedPlugins() = 0;
/**
* @}
* @name Sorting
+3 -4
View File
@@ -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<Message> messages_;
std::set<Tag> tags_;
private:
std::string name_;
bool enabled_; //Default to true.
bool enabled_;
Priority localPriority_;
Priority globalPriority_;
std::set<File> loadAfter_;
std::set<File> requirements_;
std::set<File> incompatibilities_;
std::vector<Message> messages_;
std::set<Tag> tags_;
std::set<PluginCleaningData> dirtyInfo_;
std::set<PluginCleaningData> cleanInfo_;
std::set<Location> locations_;
+61
View File
@@ -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
<https://www.gnu.org/licenses/>.
*/
#ifndef LOOT_PLUGIN_INTERFACE
#define LOOT_PLUGIN_INTERFACE
#include <cstdint>
#include <string>
#include <vector>
#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<std::string> GetMasters() const = 0;
virtual std::vector<Message> GetStatusMessages() const = 0;
virtual std::set<Tag> 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<loot::PluginInterface> {
size_t operator() (const loot::PluginInterface& plugin) const {
return hash<string>()(plugin.GetLowercasedName());
}
};
}
#endif
+21 -21
View File
@@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 "
+23 -8
View File
@@ -42,6 +42,28 @@ std::shared_ptr<DatabaseInterface> Game::GetDatabase() {
return database_;
}
bool Game::IsValidPlugin(const std::string& plugin) {
return Plugin::IsValid(plugin, game_);
}
void Game::LoadPlugins(const std::vector<std::string>& plugins, bool loadHeadersOnly) {
game_.LoadPlugins(plugins, masterFile_, loadHeadersOnly);
}
std::shared_ptr<const PluginInterface> Game::GetPlugin(const std::string& pluginName) {
return std::static_pointer_cast<const PluginInterface>(game_.GetPlugin(pluginName));
}
std::set<std::shared_ptr<const PluginInterface>> Game::GetLoadedPlugins() {
auto pointers = game_.GetPlugins();
std::set<std::shared_ptr<const PluginInterface>> interfacePointers;
for (auto& plugin : game_.GetPlugins()) {
interfacePointers.insert(std::static_pointer_cast<const PluginInterface>(plugin));
}
return interfacePointers;
}
void Game::IdentifyMainMasterFile(const std::string& masterFile) {
masterFile_ = masterFile;
}
@@ -51,14 +73,7 @@ std::vector<std::string> Game::SortPlugins(const std::vector<std::string>& plugi
//Sort plugins into their load order.
PluginSorter sorter;
auto list = sorter.Sort(game_, LanguageCode::english);
std::vector<std::string> 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) {
+8
View File
@@ -38,6 +38,14 @@ public:
std::shared_ptr<DatabaseInterface> GetDatabase();
bool IsValidPlugin(const std::string& plugin);
void LoadPlugins(const std::vector<std::string>& plugins, bool loadHeadersOnly);
std::shared_ptr<const PluginInterface> GetPlugin(const std::string& pluginName);
std::set<std::shared_ptr<const PluginInterface>> GetLoadedPlugins();
void IdentifyMainMasterFile(const std::string& masterFile);
std::vector<std::string> SortPlugins(const std::vector<std::string>& plugins);
+1 -1
View File
@@ -155,7 +155,7 @@ void Game::LoadPlugins(const std::vector<std::string>& 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);
}
+11 -8
View File
@@ -84,18 +84,18 @@ std::pair<bool, bool> GameCache::GetCachedCondition(const std::string& condition
return pair<bool, bool>(false, false);
}
std::set<Plugin> GameCache::GetPlugins() const {
std::set<Plugin> output;
std::set<std::shared_ptr<const Plugin>> GameCache::GetPlugins() const {
std::set<std::shared_ptr<const Plugin>> output;
std::transform(begin(plugins_),
end(plugins_),
std::inserter<std::set<Plugin>>(output, begin(output)),
[](const pair<string, Plugin>& pluginPair) {
std::inserter<std::set<std::shared_ptr<const Plugin>>>(output, begin(output)),
[](const pair<string, std::shared_ptr<const Plugin>>& pluginPair) {
return pluginPair.second;
});
return output;
}
const Plugin& GameCache::GetPlugin(const std::string & pluginName) const {
std::shared_ptr<const Plugin> 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<mutex> 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<Plugin>(std::move(plugin)));
}
std::vector<Message> GameCache::GetMessages() const {
+3 -3
View File
@@ -48,8 +48,8 @@ public:
std::pair<bool, bool> GetCachedCondition(const std::string& condition) const;
void CacheCondition(const std::string& condition, bool result);
std::set<Plugin> GetPlugins() const;
const Plugin& GetPlugin(const std::string& pluginName) const;
std::set<std::shared_ptr<const Plugin>> GetPlugins() const;
std::shared_ptr<const Plugin> GetPlugin(const std::string& pluginName) const;
void AddPlugin(const Plugin&& plugin);
std::vector<Message> GetMessages() const;
@@ -68,7 +68,7 @@ private:
Masterlist masterlist_;
MetadataList userlist_;
std::unordered_map<std::string, bool> conditions_;
std::unordered_map<std::string, Plugin> plugins_;
std::unordered_map<std::string, std::shared_ptr<const Plugin>> plugins_;
std::vector<Message> messages_;
std::vector<std::string> loadOrder_;
unsigned short loadOrderSortCount_;
+4 -4
View File
@@ -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);
}
+61 -71
View File
@@ -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<std::string> Plugin::GetMasters() const {
return getMasters();
}
std::vector<Message> Plugin::GetStatusMessages() const {
return messages_;
}
std::set<Tag> 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<const Plugin&>(plugin);
//Basically std::set_intersection except with an early exit instead of an append to results.
set<FormId> formIds(getFormIds());
set<FormId> otherFormIds(plugin.getFormIds());
set<FormId> 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<FormId> Plugin::OverlapFormIDs(const Plugin& plugin) const {
set<FormId> formIds(getFormIds());
set<FormId> otherFormIds(plugin.getFormIds());
@@ -162,10 +203,6 @@ std::set<FormId> 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;
+17 -21
View File
@@ -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<std::string> GetMasters() const;
std::vector<Message> GetStatusMessages() const;
std::set<Tag> 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<libespm::FormId> 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<Tag> tags_;
std::vector<Message> messages_;
//Useful caches.
size_t numOverrideRecords_;
};
}
namespace std {
template<>
struct hash<loot::Plugin> {
size_t operator() (const loot::Plugin& plugin) const {
return hash<string>()(boost::locale::to_lower(plugin.Name()));
}
};
}
#endif
+38 -50
View File
@@ -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<PluginGraph>::vertex_iterator vertex_it;
typedef boost::graph_traits<PluginGraph>::edge_descriptor edge_t;
typedef boost::graph_traits<PluginGraph>::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<Plugin> PluginSorter::Sort(Game& game, const LanguageCode language) {
std::vector<std::string> PluginSorter::Sort(Game& game, const LanguageCode language) {
// Clear existing data.
graph_.clear();
indexMap_.clear();
@@ -114,7 +117,7 @@ std::vector<Plugin> 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<Plugin>();
return vector<std::string>();
// Get the existing load order.
oldLoadOrder_ = game.GetLoadOrder();
@@ -150,16 +153,16 @@ std::vector<Plugin> 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<Plugin> plugins;
vector<std::string> 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<Message> 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<PluginGraph&>(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<PluginGraph&>(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 {
+20 -2
View File
@@ -34,13 +34,31 @@
#include "backend/plugin/plugin.h"
namespace loot {
typedef boost::adjacency_list<boost::listS, boost::listS, boost::directedS, Plugin> 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<boost::listS, boost::listS, boost::directedS, PluginSortingData> PluginGraph;
typedef boost::graph_traits<PluginGraph>::vertex_descriptor vertex_t;
typedef boost::associative_property_map<std::map<vertex_t, size_t>> vertex_map_t;
class PluginSorter {
public:
std::vector<Plugin> Sort(Game& game, const LanguageCode language);
std::vector<std::string> Sort(Game& game, const LanguageCode language);
private:
bool GetVertexByName(const std::string& name, vertex_t& vertex) const;
void CheckForCycles() const;
+4 -3
View File
@@ -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));
}
@@ -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<const Plugin> plugin,
std::shared_ptr<const Plugin> 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;
+21 -22
View File
@@ -55,7 +55,7 @@ public:
loadMetadataLists();
//Sort plugins into their load order.
std::vector<Plugin> installed;
std::vector<std::shared_ptr<const Plugin>> installed;
std::vector<std::string> 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<const Plugin> 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<std::string>();
@@ -170,7 +169,7 @@ private:
return pluginNode;
}
std::string generateJsonResponse(std::vector<Plugin> plugins) {
std::string generateJsonResponse(std::vector<std::shared_ptr<const Plugin>> plugins) {
YAML::Node gameNode;
// ID the game using its folder value.
+80 -20
View File
@@ -25,6 +25,8 @@ along with LOOT. If not, see
#ifndef LOOT_GUI_QUERY_METADATA_QUERY
#define LOOT_GUI_QUERY_METADATA_QUERY
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <boost/locale.hpp>
#include <boost/log/trivial.hpp>
@@ -50,25 +52,40 @@ protected:
return toSimpleMessages(messages, state_.getLanguage().GetCode());
}
YAML::Node generateDerivedMetadata(const Plugin& file,
PluginMetadata getNonUserMetadata(std::shared_ptr<const Plugin> 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<const Plugin> 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<const Plugin> 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<Message> CheckInstallValidity(std::shared_ptr<const Plugin> plugin, const PluginMetadata& metadata) {
BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to " << plugin->GetName() << "'s data.";
std::vector<Message> 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_;
};
}
+14 -25
View File
@@ -49,12 +49,7 @@ public:
state_.getCurrentGame().LoadAllInstalledPlugins(false);
//Sort plugins into their load order.
std::vector<Plugin> plugins = sortPlugins();
sortedPluginNames.resize(plugins.size());
std::transform(begin(plugins), end(plugins), begin(sortedPluginNames), [](const Plugin& plugin) {
return plugin.Name();
});
std::vector<std::string> plugins = sortPlugins();
if ((state_.getCurrentGame().Type() == GameType::tes5
|| state_.getCurrentGame().Type() == GameType::fo4
@@ -71,9 +66,9 @@ public:
}
private:
std::vector<Plugin> sortPlugins() {
std::vector<std::string> sortPlugins() {
sendProgressUpdate(frame_, boost::locale::translate("Sorting load order..."));
std::vector<Plugin> plugins;
std::vector<std::string> plugins;
try {
PluginSorter sorter;
plugins = sorter.Sort(state_.getCurrentGame(), state_.getLanguage().GetCode());
@@ -89,41 +84,35 @@ private:
return plugins;
}
void applyUnchangedLoadOrder(const std::vector<Plugin>& plugins) {
void applyUnchangedLoadOrder(const std::vector<std::string>& 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<std::string> 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<const Plugin> 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<Plugin>& plugins) {
std::string generateJsonResponse(const std::vector<std::string>& 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));
}

Some files were not shown because too many files have changed in this diff Show More