mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Merge remote-tracking branch 'origin/master' into cef-implement
This commit is contained in:
+3
-2
@@ -49,12 +49,13 @@ set (LOOT_SRC "${CMAKE_SOURCE_DIR}/src/backend/metadata.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/game.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/helpers.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/globals.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/generators.cpp")
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/generators.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/sort.cpp")
|
||||
|
||||
set (LOOT_GUI_SRC ${LOOT_SRC}
|
||||
# Code the API doesn't need.
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/graph.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/network.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/backend/git.cpp"
|
||||
# Actual GUI code.
|
||||
"${CMAKE_SOURCE_DIR}/src/gui/main_win.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/gui/handler.cpp"
|
||||
|
||||
@@ -158,6 +158,21 @@ unsigned int c_error(const unsigned int code, const std::string& what) {
|
||||
return c_error(loot::error(code, what.c_str()));
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// Dummy Masterlist member functions
|
||||
////////////////////////////////////
|
||||
|
||||
// The API doesn't depend on libgit2, by not compiling ".git.cpp", so the member functions are defined
|
||||
// below as dummies.
|
||||
|
||||
namespace loot {
|
||||
void Masterlist::GetGitInfo(boost::filesystem::path& path) {}
|
||||
|
||||
void Masterlist::Update(Game& game, const unsigned int language) {
|
||||
this->MetadataList::Load(game.MasterlistPath());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
// Error Handling Functions
|
||||
|
||||
+162
-7
@@ -28,8 +28,11 @@
|
||||
#include "error.h"
|
||||
#include "metadata.h"
|
||||
#include "parsers.h"
|
||||
#include "streams.h"
|
||||
#include "generators.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -59,6 +62,128 @@ namespace loot {
|
||||
return games;
|
||||
}
|
||||
|
||||
size_t SelectGame(const YAML::Node& settings, const std::vector<Game>& games, const std::string& cmdLineGame) {
|
||||
string preferredGame(cmdLineGame);
|
||||
if (preferredGame.empty()) {
|
||||
// Get preferred game from settings.
|
||||
if (settings["Game"] && settings["Game"].as<string>() != "auto")
|
||||
preferredGame = settings["Game"].as<string>();
|
||||
else if (settings["Last Game"] && settings["Last Game"].as<string>() != "auto")
|
||||
preferredGame = settings["Last Game"].as<string>();
|
||||
}
|
||||
|
||||
// Get index of preferred game if there is one.
|
||||
for (size_t i = 0; i < games.size(); ++i) {
|
||||
if (preferredGame.empty() && games[i].IsInstalled())
|
||||
return i;
|
||||
else if (!preferredGame.empty() && preferredGame == games[i].FolderName() && games[i].IsInstalled())
|
||||
return i;
|
||||
}
|
||||
throw error(error::no_game_detected, "None of the supported games were detected.");
|
||||
}
|
||||
|
||||
// MetadataList member functions
|
||||
//------------------------------
|
||||
|
||||
void MetadataList::Load(const boost::filesystem::path& filepath) {
|
||||
plugins.clear();
|
||||
messages.clear();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Loading file: " << filepath;
|
||||
|
||||
loot::ifstream in(filepath);
|
||||
YAML::Node metadataList = YAML::Load(in);
|
||||
in.close();
|
||||
|
||||
if (metadataList["plugins"])
|
||||
plugins = metadataList["plugins"].as< list<Plugin> >();
|
||||
if (metadataList["globals"])
|
||||
messages = metadataList["globals"].as< list<Message> >();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "File loaded successfully.";
|
||||
}
|
||||
|
||||
void MetadataList::Save(const boost::filesystem::path& filepath) {
|
||||
YAML::Emitter yout;
|
||||
yout.SetIndent(2);
|
||||
yout << YAML::BeginMap
|
||||
<< YAML::Key << "plugins" << YAML::Value << plugins
|
||||
<< YAML::Key << "globals" << YAML::Value << messages
|
||||
<< YAML::EndMap;
|
||||
|
||||
loot::ofstream uout(filepath);
|
||||
uout << yout.c_str();
|
||||
uout.close();
|
||||
}
|
||||
|
||||
bool MetadataList::operator == (const MetadataList& rhs) const {
|
||||
if (this->plugins.size() != rhs.plugins.size() || this->messages.size() != rhs.messages.size()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Metadata edited for some plugin, new and old userlists differ in size.";
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
for (const auto& rhsPlugin : rhs.plugins) {
|
||||
const auto it = std::find(this->plugins.begin(), this->plugins.end(), rhsPlugin);
|
||||
|
||||
if (it == this->plugins.end()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Metadata added for plugin: " << it->Name();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!it->DiffMetadata(rhsPlugin).HasNameOnly()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Metadata edited for plugin: " << it->Name();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Messages are compared exactly by the '==' operator, so there's no need to do a more
|
||||
// fine-grained check.
|
||||
for (const auto& rhsMessage : rhs.messages) {
|
||||
const auto it = std::find(this->messages.begin(), this->messages.end(), rhsMessage);
|
||||
|
||||
if (it == this->messages.end()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Masterlist member functions
|
||||
//----------------------------
|
||||
|
||||
void Masterlist::Load(Game& game, const unsigned int language) {
|
||||
try {
|
||||
Update(game, language);
|
||||
}
|
||||
catch (error& e) {
|
||||
if (e.code() != error::ok) {
|
||||
// Error wasn't a parsing error. Need to try parsing masterlist if it exists.
|
||||
try {
|
||||
MetadataList::Load(game.MasterlistPath());
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Masterlist::GetRevision(const boost::filesystem::path& path) {
|
||||
if (revision.empty())
|
||||
GetGitInfo(path);
|
||||
|
||||
return revision;
|
||||
}
|
||||
|
||||
std::string Masterlist::GetDate(const boost::filesystem::path& path) {
|
||||
if (date.empty())
|
||||
GetGitInfo(path);
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
// Game member functions
|
||||
//----------------------
|
||||
|
||||
Game::Game() : id(Game::autodetect) {}
|
||||
|
||||
Game::Game(const unsigned int gameCode, const std::string& folder) : id(gameCode) {
|
||||
@@ -514,19 +639,49 @@ namespace loot {
|
||||
}
|
||||
|
||||
void Game::LoadPlugins(bool headersOnly) {
|
||||
//Add all plugins in data folder not already in the hashset to the hashset, and load them.
|
||||
for (fs::directory_iterator it(DataPath()); it != fs::directory_iterator(); ++it) {
|
||||
boost::thread_group group;
|
||||
uintmax_t meanFileSize = 0;
|
||||
unordered_map<std::string, uintmax_t> tempMap;
|
||||
std::vector<Plugin*> groupPlugins;
|
||||
//First calculate the mean plugin size. Store it temporarily in a map to reduce filesystem lookups and file size recalculation.
|
||||
for (fs::directory_iterator it(this->DataPath()); it != fs::directory_iterator(); ++it) {
|
||||
if (fs::is_regular_file(it->status()) && IsPlugin(it->path().string())) {
|
||||
const string filename = it->path().filename().string();
|
||||
|
||||
if (plugins.find(filename) == plugins.end())
|
||||
plugins.insert(std::pair<string, Plugin>(filename, Plugin(filename)));
|
||||
uintmax_t fileSize = fs::file_size(it->path());
|
||||
meanFileSize += fileSize;
|
||||
|
||||
tempMap.emplace(it->path().filename().string(), fileSize);
|
||||
}
|
||||
}
|
||||
meanFileSize /= tempMap.size(); //Rounding error, but not important.
|
||||
|
||||
for (auto &pluginPair: plugins) {
|
||||
pluginPair.second = Plugin(*this, pluginPair.second.Name(), headersOnly);
|
||||
//Now load plugins.
|
||||
for (const auto &pluginPair : tempMap) {
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Found plugin: " << pluginPair.first;
|
||||
|
||||
auto plugin = plugins.emplace(pluginPair.first, Plugin(pluginPair.first));
|
||||
|
||||
if (pluginPair.second > meanFileSize) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Creating individual loading thread for: " << pluginPair.first;
|
||||
group.create_thread([this, plugin, headersOnly]() {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Loading " << plugin.first->second.Name() << " individually.";
|
||||
plugin.first->second = Plugin(*this, plugin.first->first, headersOnly);
|
||||
});
|
||||
}
|
||||
else {
|
||||
groupPlugins.push_back(&plugin.first->second);
|
||||
}
|
||||
}
|
||||
group.create_thread([this, &groupPlugins, headersOnly]() {
|
||||
for (auto plugin : groupPlugins) {
|
||||
const std::string name = plugin->Name();
|
||||
BOOST_LOG_TRIVIAL(trace) << "Loading " << plugin->Name() << " as part of a group.";
|
||||
*plugin = Plugin(*this, name, headersOnly);
|
||||
}
|
||||
});
|
||||
|
||||
group.join_all();
|
||||
}
|
||||
|
||||
void Game::CreateLOOTGameFolder() {
|
||||
|
||||
+35
-2
@@ -40,6 +40,8 @@
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
namespace loot {
|
||||
|
||||
class Game;
|
||||
|
||||
/* Each Game object should store the config details specific to that game.
|
||||
It should also store the plugin and masterlist data for that game.
|
||||
@@ -51,6 +53,33 @@ namespace loot {
|
||||
data. Plugin data should be loaded as header-only and as full data.
|
||||
*/
|
||||
|
||||
class MetadataList {
|
||||
public:
|
||||
void Load(const boost::filesystem::path& filepath);
|
||||
void Save(const boost::filesystem::path& filepath);
|
||||
|
||||
bool operator == (const MetadataList& rhs) const; //Compares content.
|
||||
|
||||
std::list<Plugin> plugins;
|
||||
std::list<Message> messages;
|
||||
};
|
||||
|
||||
class Masterlist : public MetadataList {
|
||||
public:
|
||||
|
||||
void Load(Game& game, const unsigned int language); //Handles update with load fallback.
|
||||
void Update(Game& game, const unsigned int language);
|
||||
|
||||
std::string GetRevision(const boost::filesystem::path& path);
|
||||
std::string GetDate(const boost::filesystem::path& path);
|
||||
|
||||
private:
|
||||
void GetGitInfo(const boost::filesystem::path& path);
|
||||
|
||||
std::string revision;
|
||||
std::string date;
|
||||
};
|
||||
|
||||
class Game {
|
||||
public:
|
||||
//Game functions.
|
||||
@@ -82,7 +111,6 @@ namespace loot {
|
||||
boost::filesystem::path ReportDataPath() const;
|
||||
|
||||
//Game plugin functions.
|
||||
|
||||
bool IsActive(const std::string& plugin) const;
|
||||
|
||||
void GetLoadOrder(std::list<std::string>& loadOrder) const;
|
||||
@@ -92,12 +120,15 @@ namespace loot {
|
||||
void RedatePlugins(); //Change timestamps to match load order (Skyrim only).
|
||||
void LoadPlugins(bool headersOnly); //Loads all installed plugins.
|
||||
|
||||
void SortPrep(const unsigned int language, std::list<Message>& messages, std::function<void(const std::string&)> progressCallback);
|
||||
std::list<Plugin> Sort(const unsigned int language, std::list<Message>& messages, std::function<void(const std::string&)> progressCallback);
|
||||
|
||||
//Caches for condition results, active plugins and CRCs.
|
||||
std::unordered_map<std::string, bool> conditionCache; //Holds lowercased strings.
|
||||
std::unordered_map<std::string, uint32_t> crcCache; //Holds lowercased strings.
|
||||
|
||||
//Plugin data and metadata lists.
|
||||
MetadataList masterlist;
|
||||
Masterlist masterlist;
|
||||
MetadataList userlist;
|
||||
std::unordered_map<std::string, Plugin> plugins; //Map so that plugin data can be edited.
|
||||
|
||||
@@ -128,6 +159,8 @@ namespace loot {
|
||||
};
|
||||
|
||||
std::vector<Game> GetGames(const YAML::Node& settings);
|
||||
|
||||
size_t SelectGame(const YAML::Node& settings, const std::vector<Game>& games, const std::string& cmdLineGame);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
<http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "network.h"
|
||||
#include "error.h"
|
||||
#include "parsers.h"
|
||||
#include "streams.h"
|
||||
#include "helpers.h"
|
||||
#include "game.h"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/locale.hpp>
|
||||
@@ -92,11 +92,6 @@ namespace loot {
|
||||
std::string ui_message;
|
||||
};
|
||||
|
||||
int progress_cb(const char *str, int len, void *data) {
|
||||
BOOST_LOG_TRIVIAL(info) << string(str, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool are_files_equal(const void * buf1, size_t buf1_size, const void * buf2, size_t buf2_size) {
|
||||
if (buf1_size != buf2_size)
|
||||
return false;
|
||||
@@ -114,12 +109,17 @@ namespace loot {
|
||||
return git_repository_open_ext(NULL, path.string().c_str(), GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) == 0;
|
||||
}
|
||||
|
||||
std::pair<string, string> GetMasterlistRevision(const Game& game) {
|
||||
if (!fs::exists(game.MasterlistPath().parent_path() / ".git")) {
|
||||
return pair<string, string>("Unknown: Git repository missing", "Unknown: Git repository missing");
|
||||
void Masterlist::GetGitInfo(const boost::filesystem::path& path) {
|
||||
if (!fs::exists(path.parent_path() / ".git")) {
|
||||
revision = "Unknown: Git repository missing";
|
||||
date = "Unknown: Git repository missing";
|
||||
return;
|
||||
}
|
||||
else if (!fs::exists(path)) {
|
||||
revision = "N/A: No masterlist present";
|
||||
date = "N/A: No masterlist present";
|
||||
return;
|
||||
}
|
||||
else if (!fs::exists(game.MasterlistPath()))
|
||||
return pair<string, string>("N/A: No masterlist present", "N/A: No masterlist present");
|
||||
else {
|
||||
/* Compares HEAD to the working dir.
|
||||
1. Get an object for the masterlist in HEAD.
|
||||
@@ -128,9 +128,9 @@ namespace loot {
|
||||
4. Compare the file and blob buffers.
|
||||
*/
|
||||
git_handler git;
|
||||
git.ui_message = "An error occurred while trying to read the local masterlist's version. If this error happens again, try deleting the \".git\" folder in \"%LOCALAPPDATA%\\LOOT\\" + game.FolderName() + "\".";
|
||||
git.ui_message = "An error occurred while trying to read the local masterlist's version. If this error happens again, try deleting the \".git\" folder in " + path.parent_path().string() + ".";
|
||||
BOOST_LOG_TRIVIAL(debug) << "Existing repository found, attempting to open it.";
|
||||
git.call(git_repository_open(&git.repo, game.MasterlistPath().parent_path().string().c_str()));
|
||||
git.call(git_repository_open(&git.repo, path.parent_path().string().c_str()));
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Getting HEAD masterlist object.";
|
||||
git.call(git_revparse_single(&git.obj, git.repo, "HEAD:masterlist.yaml"));
|
||||
@@ -140,7 +140,7 @@ namespace loot {
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Opening masterlist in working directory.";
|
||||
std::string mlist;
|
||||
loot::ifstream ifile(game.MasterlistPath().string().c_str(), ios::binary);
|
||||
loot::ifstream ifile(path, ios::binary);
|
||||
if (ifile.fail())
|
||||
throw error(error::path_read_fail, "Couldn't open masterlist.");
|
||||
ifile.unsetf(ios::skipws); // No white space skipping!
|
||||
@@ -153,7 +153,6 @@ namespace loot {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Comparing files.";
|
||||
if (are_files_equal(git_blob_rawcontent(git.blob), git_blob_rawsize(git.blob), mlist.data(), mlist.length())) {
|
||||
|
||||
string revision, date;
|
||||
//Need to get the HEAD object, because the individual file has a different SHA.
|
||||
git_object_free(git.obj);
|
||||
git.obj = nullptr; //Just to be safe.
|
||||
@@ -176,15 +175,17 @@ namespace loot {
|
||||
out << boost::locale::as::ftime("%Y-%m-%d") << dateTime;
|
||||
date = out.str();
|
||||
|
||||
return pair<string, string>(revision, date);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
return pair<string, string>("Unknown: Masterlist edited", "Unknown: Masterlist edited");
|
||||
revision = "Unknown: Masterlist edited";
|
||||
date = "Unknown: Masterlist edited";
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> UpdateMasterlist(Game& game, std::list<Message>& parsingErrors, std::list<Plugin>& plugins, std::list<Message>& messages, const unsigned int language) {
|
||||
void Masterlist::Update(Game& game, const unsigned int language) {
|
||||
git_handler git;
|
||||
fs::path repo_path = game.MasterlistPath().parent_path();
|
||||
string repo_branch = game.RepoBranch();
|
||||
@@ -387,7 +388,7 @@ namespace loot {
|
||||
// and try again.
|
||||
|
||||
bool parsingFailed = false;
|
||||
string revision, date;
|
||||
std::string parsingError;
|
||||
git.ui_message = "An error occurred while trying to read information on the updated masterlist. If this error happens again, try deleting the \".git\" folder in \"%LOCALAPPDATA%\\LOOT\\" + game.FolderName() + "\".";
|
||||
do {
|
||||
// Get some descriptive info about what was checked out.
|
||||
@@ -428,14 +429,7 @@ namespace loot {
|
||||
//Now try parsing the masterlist.
|
||||
BOOST_LOG_TRIVIAL(debug) << "Testing masterlist parsing.";
|
||||
try {
|
||||
loot::ifstream in(game.MasterlistPath());
|
||||
YAML::Node mlist = YAML::Load(in);
|
||||
in.close();
|
||||
|
||||
if (mlist["globals"])
|
||||
messages = mlist["globals"].as< list<loot::Message> >();
|
||||
if (mlist["plugins"])
|
||||
plugins = mlist["plugins"].as< list<loot::Plugin> >();
|
||||
this->MetadataList::Load(game.MasterlistPath());
|
||||
|
||||
for (auto &plugin: plugins) {
|
||||
plugin.EvalAllConditions(game, language);
|
||||
@@ -466,10 +460,12 @@ namespace loot {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD.";
|
||||
git.call(git_checkout_head(git.repo, &checkout_opts));
|
||||
|
||||
parsingErrors.push_back(loot::Message(loot::Message::error, boost::locale::translate("Masterlist revision").str() + " " + string(revision) + ": " + e.what() + " " + boost::locale::translate("Rolled back to the previous revision.").str()));
|
||||
if (parsingError.empty())
|
||||
parsingError = boost::locale::translate("Masterlist revision").str() + " " + string(revision) + ": " + e.what() + " " + boost::locale::translate("Rolled back to the previous revision.").str();
|
||||
}
|
||||
} while (parsingFailed);
|
||||
|
||||
return pair<string, string>(revision, date);
|
||||
if (!parsingError.empty())
|
||||
throw error(error::ok, parsingError); //Throw an OK because the process still completed in a successful state.
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ namespace loot {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Sort(const PluginGraph& graph, std::list<Plugin>& plugins) {
|
||||
std::list<Plugin> Sort(const PluginGraph& graph) {
|
||||
|
||||
//Topological sort requires an index map, which std::list-based VertexList graphs don't have, so one needs to be built separately.
|
||||
|
||||
@@ -105,15 +105,19 @@ namespace loot {
|
||||
std::list<vertex_t> sortedVertices;
|
||||
boost::topological_sort(graph, std::front_inserter(sortedVertices), boost::vertex_index_map(v_index_map));
|
||||
|
||||
/* Sorting now evaluates conditions inside the graph, so existing plugins list is missing
|
||||
data present in the graph, so we need to swap the two lists. */
|
||||
BOOST_LOG_TRIVIAL(info) << "Calculated order: ";
|
||||
list<string> tempPlugins;
|
||||
list<Plugin> plugins;
|
||||
for (const auto &vertex: sortedVertices) {
|
||||
BOOST_LOG_TRIVIAL(info) << '\t' << graph[vertex].Name();
|
||||
tempPlugins.push_back(graph[vertex].Name());
|
||||
plugins.push_back(graph[vertex]);
|
||||
}
|
||||
return plugins;
|
||||
|
||||
|
||||
//Now sort exist plugins list according to order in tempPlugins.
|
||||
plugins.sort([tempPlugins](const Plugin& first, const Plugin& second){
|
||||
/*plugins.sort([tempPlugins](const Plugin& first, const Plugin& second){
|
||||
//Find both plugins, and compare distances from beginning.
|
||||
auto fIt = find(tempPlugins.begin(), tempPlugins.end(), first);
|
||||
auto sIt = find(tempPlugins.begin(), tempPlugins.end(), second);
|
||||
@@ -123,6 +127,7 @@ namespace loot {
|
||||
|
||||
return distance(tempPlugins.begin(), fIt) < distance(tempPlugins.begin(), sIt);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
void CheckForCycles(const PluginGraph& graph) {
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ namespace loot {
|
||||
|
||||
bool GetVertexByName(const PluginGraph& graph, const std::string& name, vertex_t& vertex);
|
||||
|
||||
void Sort(const PluginGraph& graph, std::list<Plugin>& plugins);
|
||||
std::list<Plugin> Sort(const PluginGraph& graph);
|
||||
|
||||
void CheckForCycles(const PluginGraph& graph);
|
||||
|
||||
|
||||
@@ -763,24 +763,6 @@ namespace loot {
|
||||
return boost::filesystem::exists(game.DataPath() / (name.substr(0, name.length() - 3) + "bsa"));
|
||||
}
|
||||
|
||||
void MetadataList::Load(boost::filesystem::path& filepath) {
|
||||
plugins.clear();
|
||||
messages.clear();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Loading file: " << filepath;
|
||||
|
||||
loot::ifstream in(filepath);
|
||||
YAML::Node metadataList = YAML::Load(in);
|
||||
in.close();
|
||||
|
||||
if (metadataList["plugins"])
|
||||
plugins = metadataList["plugins"].as< list<Plugin> >();
|
||||
if (metadataList["globals"])
|
||||
messages = metadataList["globals"].as< list<Message> >();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "File loaded successfully.";
|
||||
}
|
||||
|
||||
size_t plugin_hash::operator () (const Plugin& p) const {
|
||||
size_t seed = 0;
|
||||
boost::hash_combine(seed, p.Name());
|
||||
|
||||
@@ -243,14 +243,6 @@ namespace loot {
|
||||
size_t numOverrideRecords;
|
||||
};
|
||||
|
||||
class MetadataList {
|
||||
public:
|
||||
void Load(boost::filesystem::path& filepath);
|
||||
|
||||
std::list<Plugin> plugins;
|
||||
std::list<Message> messages;
|
||||
};
|
||||
|
||||
struct plugin_hash : std::unary_function<Plugin, size_t> {
|
||||
size_t operator () (const Plugin& p) const;
|
||||
};
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/* LOOT
|
||||
|
||||
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
|
||||
Fallout: New Vegas.
|
||||
|
||||
Copyright (C) 2012-2014 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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __LOOT_NETWORK__
|
||||
#define __LOOT_NETWORK__
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "game.h"
|
||||
#include "metadata.h"
|
||||
|
||||
namespace loot {
|
||||
|
||||
std::pair<std::string, std::string> UpdateMasterlist(Game& game, std::list<Message>& parsingErrors, std::list<Plugin>& plugins, std::list<Message>& messages, const unsigned int language);
|
||||
|
||||
std::pair<std::string, std::string> GetMasterlistRevision(const Game& game);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,203 @@
|
||||
/* LOOT
|
||||
|
||||
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
|
||||
Fallout: New Vegas.
|
||||
|
||||
Copyright (C) 2014 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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "game.h"
|
||||
#include "helpers.h"
|
||||
#include "graph.h"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/locale.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
using boost::format;
|
||||
|
||||
namespace loc = boost::locale;
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace loot {
|
||||
|
||||
void Game::SortPrep(const unsigned int language, std::list<Message>& messages, std::function<void(const std::string&)> progressCallback) {
|
||||
boost::thread_group group;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Using message language: " << Language(language).Name();
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// Load Plugins & Lists
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
progressCallback("Reading installed plugins...");
|
||||
|
||||
group.create_thread([this, language, &messages]() {
|
||||
try {
|
||||
this->masterlist.Load(*this, language);
|
||||
}
|
||||
catch (exception &e) {
|
||||
messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist parsing failed. Details: %1%")) % e.what()).str()));
|
||||
}
|
||||
});
|
||||
group.create_thread([this]() {
|
||||
this->LoadPlugins(false);
|
||||
});
|
||||
group.join_all();
|
||||
|
||||
//Now load userlist.
|
||||
if (fs::exists(this->UserlistPath())) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Parsing userlist at: " << this->UserlistPath();
|
||||
|
||||
try {
|
||||
this->userlist.Load(this->UserlistPath());
|
||||
}
|
||||
catch (exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Userlist parsing failed. Details: " << e.what();
|
||||
messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("Userlist parsing failed. Details: %1%")) % e.what()).str()));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// Evaluate Global Messages
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
progressCallback("Evaluating global messages...");
|
||||
|
||||
//Merge all global message lists.
|
||||
BOOST_LOG_TRIVIAL(debug) << "Merging all global message lists.";
|
||||
if (!this->masterlist.messages.empty())
|
||||
messages.insert(messages.end(), this->masterlist.messages.begin(), this->masterlist.messages.end());
|
||||
if (!this->userlist.messages.empty())
|
||||
messages.insert(messages.end(), this->userlist.messages.begin(), this->userlist.messages.end());
|
||||
|
||||
//Evaluate any conditions in the global messages.
|
||||
BOOST_LOG_TRIVIAL(debug) << "Evaluating global message conditions.";
|
||||
try {
|
||||
list<loot::Message>::iterator it = messages.begin();
|
||||
while (it != messages.end()) {
|
||||
if (!it->EvalCondition(*this, language))
|
||||
it = messages.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "A global message contains a condition that could not be evaluated. Details: " << e.what();
|
||||
messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("A global message contains a condition that could not be evaluated. Details: %1%")) % e.what()).str()));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// Slim down masterlist
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// Userlist data gets replaced every time sorting is looped, so there's no point evaluating it
|
||||
// outside the loop, but the masterlist can be slimmed down now.
|
||||
|
||||
progressCallback("Filtering masterlist...");
|
||||
|
||||
std::list<Plugin> tempMasterlistPlugins;
|
||||
for (const auto &plugin : this->plugins) {
|
||||
list<loot::Plugin>::iterator pos = std::find(this->masterlist.plugins.begin(), this->masterlist.plugins.end(), plugin.second);
|
||||
|
||||
if (pos != this->masterlist.plugins.end()) {
|
||||
// The plugin exists in the masterlist, store a copy of its metadata.
|
||||
tempMasterlistPlugins.push_back(*pos);
|
||||
}
|
||||
}
|
||||
// Now replace the current full masterlist plugin metadata list with the install-specific one.
|
||||
this->masterlist.plugins = tempMasterlistPlugins;
|
||||
}
|
||||
|
||||
|
||||
std::list<Plugin> Game::Sort(const unsigned int language, std::list<Message>& messages, std::function<void(const std::string&)> progressCallback) {
|
||||
//Create a plugin graph containing the plugin and masterlist data.
|
||||
loot::PluginGraph graph;
|
||||
|
||||
progressCallback("Building plugin graph...");
|
||||
BOOST_LOG_TRIVIAL(info) << "Merging masterlist, userlist into plugin list, evaluating conditions and checking for install validity.";
|
||||
for (const auto &plugin : this->plugins) {
|
||||
vertex_t v = boost::add_vertex(plugin.second, graph);
|
||||
list<loot::Plugin>::iterator pos;
|
||||
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.
|
||||
pos = std::find(this->masterlist.plugins.begin(), this->masterlist.plugins.end(), graph[v]);
|
||||
|
||||
if (pos != this->masterlist.plugins.end()) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data.";
|
||||
graph[v].MergeMetadata(*pos);
|
||||
}
|
||||
|
||||
//Check if there is a plugin entry in the userlist. This will also find matching regex entries.
|
||||
pos = std::find(this->userlist.plugins.begin(), this->userlist.plugins.end(), graph[v]);
|
||||
|
||||
if (pos != this->userlist.plugins.end() && pos->Enabled()) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Merging userlist data down to plugin list data.";
|
||||
graph[v].MergeMetadata(*pos);
|
||||
}
|
||||
|
||||
//Now that items are merged, evaluate any conditions they have.
|
||||
BOOST_LOG_TRIVIAL(trace) << "Evaluate conditions for merged plugin data.";
|
||||
try {
|
||||
graph[v].EvalAllConditions(*this, language);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "\"" << graph[v].Name() << "\" contains a condition that could not be evaluated. Details: " << e.what();
|
||||
messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("\"%1%\" contains a condition that could not be evaluated. Details: %2%")) % graph[v].Name() % e.what()).str()));
|
||||
}
|
||||
|
||||
//Also check install validity.
|
||||
BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to this plugin's data.";
|
||||
graph[v].CheckInstallValidity(*this);
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Building the plugin dependency graph...";
|
||||
|
||||
//Now add the interactions between plugins to the graph as edges.
|
||||
std::map<std::string, int> overriddenPriorities;
|
||||
BOOST_LOG_TRIVIAL(debug) << "Adding non-overlap edges.";
|
||||
loot::AddSpecificEdges(graph, overriddenPriorities);
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Adding priority edges.";
|
||||
loot::AddPriorityEdges(graph);
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Adding overlap edges.";
|
||||
loot::AddOverlapEdges(graph);
|
||||
|
||||
progressCallback("Checking for graph cycles...");
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Checking to see if the graph is cyclic.";
|
||||
loot::CheckForCycles(graph);
|
||||
|
||||
for (const auto &overriddenPriority : overriddenPriorities) {
|
||||
vertex_t vertex;
|
||||
if (loot::GetVertexByName(graph, overriddenPriority.first, vertex)) {
|
||||
graph[vertex].Priority(overriddenPriority.second);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Performing a topological sort.";
|
||||
progressCallback("Performing topological sort...");
|
||||
return loot::Sort(graph);
|
||||
}
|
||||
}
|
||||
+8
-15
@@ -1041,8 +1041,10 @@ void EditorPanel::ApplyCurrentEdits() {
|
||||
ApplyEdits(currentPlugin);
|
||||
}
|
||||
|
||||
const std::list<loot::Plugin>& EditorPanel::GetNewUserlist() const {
|
||||
return _editedPlugins;
|
||||
loot::MetadataList EditorPanel::GetNewUserlist() const {
|
||||
loot::MetadataList newUserlist;
|
||||
newUserlist.plugins = _editedPlugins;
|
||||
return newUserlist;
|
||||
}
|
||||
|
||||
loot::Plugin EditorPanel::GetMasterData(const wxString& plugin) const {
|
||||
@@ -1197,7 +1199,7 @@ void MiniEditor::OnResize(wxSizeEvent& event) {
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
const std::list<loot::Plugin>& MiniEditor::GetNewUserlist() const {
|
||||
loot::MetadataList MiniEditor::GetNewUserlist() const {
|
||||
return editorPanel->GetNewUserlist();
|
||||
}
|
||||
|
||||
@@ -1206,7 +1208,7 @@ const std::list<loot::Plugin>& MiniEditor::GetNewUserlist() const {
|
||||
// Full Editor Class
|
||||
///////////////////////////////////
|
||||
|
||||
FullEditor::FullEditor(wxWindow *parent, const wxString& title, wxPoint pos, wxSize size, const std::string userlistPath, const std::list<loot::Plugin>& basePlugins, std::list<loot::Plugin>& editedPlugins, const unsigned int language, const loot::Game& game, YAML::Node &settings) : wxFrame(parent, wxID_ANY, title, pos, size), _userlistPath(userlistPath), _settings(settings) {
|
||||
FullEditor::FullEditor(wxWindow *parent, const wxString& title, wxPoint pos, wxSize size, const boost::filesystem::path& userlistPath, const std::list<loot::Plugin>& basePlugins, std::list<loot::Plugin>& editedPlugins, const unsigned int language, const loot::Game& game, YAML::Node &settings) : wxFrame(parent, wxID_ANY, title, pos, size), _userlistPath(userlistPath), _settings(settings) {
|
||||
//Set up content.
|
||||
editorPanel = new EditorPanel(this, basePlugins, editedPlugins, language, game);
|
||||
applyBtn = new wxButton(this, BUTTON_Apply, translate("Save Changes"));
|
||||
@@ -1245,17 +1247,8 @@ void FullEditor::OnQuit(wxCommandEvent& event) {
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Saving metadata edits to userlist.";
|
||||
|
||||
//Save edits to userlist.
|
||||
YAML::Emitter yout;
|
||||
yout.SetIndent(2);
|
||||
yout << YAML::BeginMap
|
||||
<< YAML::Key << "plugins" << YAML::Value << editorPanel->GetNewUserlist()
|
||||
<< YAML::EndMap;
|
||||
|
||||
boost::filesystem::path p(_userlistPath);
|
||||
loot::ofstream out(p);
|
||||
out << yout.c_str();
|
||||
out.close();
|
||||
loot::MetadataList userlist = editorPanel->GetNewUserlist();
|
||||
userlist.Save(_userlistPath);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
+6
-4
@@ -26,7 +26,9 @@
|
||||
|
||||
#include "ids.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include "../backend/metadata.h"
|
||||
#include "../backend/game.h"
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
@@ -69,7 +71,7 @@ public:
|
||||
void SetSimpleView(bool on = true);
|
||||
void ApplyCurrentEdits();
|
||||
|
||||
const std::list<loot::Plugin>& GetNewUserlist() const;
|
||||
loot::MetadataList GetNewUserlist() const;
|
||||
|
||||
void OnPluginSelect(wxListEvent& event);
|
||||
void OnPluginListRightClick(wxListEvent& event);
|
||||
@@ -134,7 +136,7 @@ public:
|
||||
void OnApply(wxCommandEvent& event);
|
||||
void OnResize(wxSizeEvent& event);
|
||||
|
||||
const std::list<loot::Plugin>& GetNewUserlist() const;
|
||||
loot::MetadataList GetNewUserlist() const;
|
||||
private:
|
||||
EditorPanel * editorPanel;
|
||||
wxStaticText * descText;
|
||||
@@ -144,7 +146,7 @@ private:
|
||||
|
||||
class FullEditor : public wxFrame {
|
||||
public:
|
||||
FullEditor(wxWindow *parent, const wxString& title, wxPoint pos, wxSize size, const std::string userlistPath, const std::list<loot::Plugin>& basePlugins, std::list<loot::Plugin>& editedPlugins, const unsigned int language, const loot::Game& game, YAML::Node &settings);
|
||||
FullEditor(wxWindow *parent, const wxString& title, wxPoint pos, wxSize size, const boost::filesystem::path& userlistPath, const std::list<loot::Plugin>& basePlugins, std::list<loot::Plugin>& editedPlugins, const unsigned int language, const loot::Game& game, YAML::Node &settings);
|
||||
|
||||
void OnQuit(wxCommandEvent& event);
|
||||
void OnClose(wxCloseEvent &event);
|
||||
@@ -153,7 +155,7 @@ private:
|
||||
wxButton * applyBtn;
|
||||
wxButton * cancelBtn;
|
||||
|
||||
const std::string _userlistPath;
|
||||
const boost::filesystem::path _userlistPath;
|
||||
YAML::Node& _settings;
|
||||
};
|
||||
#endif
|
||||
|
||||
+70
-445
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -45,7 +45,7 @@ private:
|
||||
|
||||
class Launcher : public wxFrame {
|
||||
public:
|
||||
Launcher(const wxChar *title, YAML::Node& settings, loot::Game * inGame, std::vector<loot::Game>& games, wxPoint pos, wxSize size);
|
||||
Launcher(const wxChar *title, YAML::Node& settings, std::vector<loot::Game>& games, size_t currentGame, wxPoint pos, wxSize size);
|
||||
|
||||
void OnSortPlugins(wxCommandEvent& event);
|
||||
void OnEditMetadata(wxCommandEvent& event);
|
||||
@@ -65,9 +65,9 @@ private:
|
||||
wxMenuItem * RedatePluginsItem;
|
||||
wxButton * ViewButton;
|
||||
|
||||
loot::Game * _game;
|
||||
YAML::Node& _settings; //LOOT Settings.
|
||||
std::vector<loot::Game>& _games;
|
||||
size_t _currentGame;
|
||||
|
||||
void GetWindowSizePos(const YAML::Node& node, wxPoint& pos, wxSize& size);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user