mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
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:
+3
-1
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user