diff --git a/src/backend/game.cpp b/src/backend/game.cpp index e7d58455..9b981b42 100644 --- a/src/backend/game.cpp +++ b/src/backend/game.cpp @@ -28,7 +28,6 @@ #include "error.h" #include "metadata.h" #include "parsers.h" -#include "streams.h" #include @@ -60,63 +59,6 @@ namespace loot { return games; } - // MetadataList member functions - //------------------------------ - - 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 >(); - if (metadataList["globals"]) - messages = metadataList["globals"].as< list >(); - - BOOST_LOG_TRIVIAL(debug) << "File loaded successfully."; - } - - // 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(boost::filesystem::path& path) { - if (revision.empty()) - GetGitInfo(path); - - return revision; - } - - std::string Masterlist::GetDate(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) { diff --git a/src/backend/game.h b/src/backend/game.h index 89729cfc..4cc0d1d3 100644 --- a/src/backend/game.h +++ b/src/backend/game.h @@ -40,8 +40,6 @@ #include 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. @@ -53,82 +51,6 @@ namespace loot { data. Plugin data should be loaded as header-only and as full data. */ - class MetadataList { - public: - void Load(boost::filesystem::path& filepath); - - std::list plugins; - std::list messages; - std::unordered_map conditionCache; //Holds lowercased strings. - std::unordered_map crcCache; //Holds lowercased strings. - }; - - 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(boost::filesystem::path& path); - std::string GetDate(boost::filesystem::path& path); - - private: - void GetGitInfo(boost::filesystem::path& path); - - std::string revision; - std::string date; - }; - /* - class PluginCache { - - - bool IsActive(const std::string& plugin) const; - - void GetLoadOrder(std::list& loadOrder) const; - void SetLoadOrder(const std::list& loadOrder) const; //Modifies game load order, even though const. - - void RefreshActivePluginsList(); - void RedatePlugins(); //Change timestamps to match load order (Skyrim only). - void LoadPlugins(bool headersOnly); //Loads all installed plugins. - std::unordered_map plugins; //Map so that plugin data can be edited. - - std::unordered_set activePlugins; //Holds lowercased strings. - }; - */ - - // A couple of plugin loader classes for handling plugin loading in separate threads. - class PluginLoader { - public: - PluginLoader(Plugin& plugin, Game& game) : _plugin(plugin), _game(game) { - } - - void operator () () { - _plugin = Plugin(_game, _plugin.Name(), false); - } - - Plugin& _plugin; - Game& _game; - std::string _filename; - bool _b; - }; - - class PluginsLoader { - public: - PluginsLoader(std::list& plugins, Game& game) : _plugins(plugins), _game(game) {} - - void operator () () { - for (auto &plugin : _plugins) { - if (skipPlugins.find(plugin.Name()) == skipPlugins.end()) { - plugin = Plugin(_game, plugin.Name(), false); - } - } - } - - std::list& _plugins; - Game& _game; - std::set skipPlugins; - }; - class Game { public: //Game functions. @@ -160,8 +82,8 @@ namespace loot { boost::filesystem::path UserlistPath() const; boost::filesystem::path ReportDataPath() const; -//TO BE REMOVED //Game plugin functions. + bool IsActive(const std::string& plugin) const; void GetLoadOrder(std::list& loadOrder) const; @@ -174,12 +96,11 @@ namespace loot { //Caches for condition results, active plugins and CRCs. std::unordered_map conditionCache; //Holds lowercased strings. std::unordered_map crcCache; //Holds lowercased strings. -//END TO BE REMOVED //Plugin data and metadata lists. - Masterlist masterlist; + MetadataList masterlist; MetadataList userlist; - std::unordered_map plugins; //Map so that plugin data can be edited. TO BE REMOVED + std::unordered_map plugins; //Map so that plugin data can be edited. espm::Settings espm_settings; @@ -201,7 +122,7 @@ namespace loot { boost::filesystem::path gamePath; //Path to the game's folder. - std::unordered_set activePlugins; //Holds lowercased strings. TO BE REMOVED + std::unordered_set activePlugins; //Holds lowercased strings. //Creates directory in LOOT folder for LOOT's game-specific files. void CreateLOOTGameFolder(); diff --git a/src/backend/metadata.cpp b/src/backend/metadata.cpp index c6538d2d..94d22e60 100644 --- a/src/backend/metadata.cpp +++ b/src/backend/metadata.cpp @@ -763,6 +763,24 @@ 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 >(); + if (metadataList["globals"]) + messages = metadataList["globals"].as< list >(); + + 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()); diff --git a/src/backend/metadata.h b/src/backend/metadata.h index 9633c40d..f7d76591 100644 --- a/src/backend/metadata.h +++ b/src/backend/metadata.h @@ -243,6 +243,14 @@ namespace loot { size_t numOverrideRecords; }; + class MetadataList { + public: + void Load(boost::filesystem::path& filepath); + + std::list plugins; + std::list messages; + }; + struct plugin_hash : std::unary_function { size_t operator () (const Plugin& p) const; }; diff --git a/src/backend/network.cpp b/src/backend/network.cpp index e28b0ff3..f3d33d1e 100644 --- a/src/backend/network.cpp +++ b/src/backend/network.cpp @@ -22,11 +22,11 @@ . */ +#include "network.h" #include "error.h" #include "parsers.h" #include "streams.h" #include "helpers.h" -#include "game.h" #include #include @@ -92,6 +92,11 @@ 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; @@ -109,17 +114,12 @@ namespace loot { return git_repository_open_ext(NULL, path.string().c_str(), GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) == 0; } - void Masterlist::GetGitInfo(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; + std::pair GetMasterlistRevision(const Game& game) { + if (!fs::exists(game.MasterlistPath().parent_path() / ".git")) { + return pair("Unknown: Git repository missing", "Unknown: Git repository missing"); } + else if (!fs::exists(game.MasterlistPath())) + return pair("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 " + path.parent_path().string() + "."; + 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() + "\"."; BOOST_LOG_TRIVIAL(debug) << "Existing repository found, attempting to open it."; - git.call(git_repository_open(&git.repo, path.parent_path().string().c_str())); + git.call(git_repository_open(&git.repo, game.MasterlistPath().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(path, ios::binary); + loot::ifstream ifile(game.MasterlistPath().string().c_str(), ios::binary); if (ifile.fail()) throw error(error::path_read_fail, "Couldn't open masterlist."); ifile.unsetf(ios::skipws); // No white space skipping! @@ -153,6 +153,7 @@ 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. @@ -175,17 +176,15 @@ namespace loot { out << boost::locale::as::ftime("%Y-%m-%d") << dateTime; date = out.str(); - return; + return pair(revision, date); } else { - revision = "Unknown: Masterlist edited"; - date = "Unknown: Masterlist edited"; - return; + return pair("Unknown: Masterlist edited", "Unknown: Masterlist edited"); } } } - void Masterlist::Update(Game& game, const unsigned int language) { + std::pair UpdateMasterlist(Game& game, std::list& parsingErrors, std::list& plugins, std::list& messages, const unsigned int language) { git_handler git; fs::path repo_path = game.MasterlistPath().parent_path(); string repo_branch = game.RepoBranch(); @@ -388,7 +387,7 @@ namespace loot { // and try again. bool parsingFailed = false; - std::string parsingError; + string revision, date; 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. @@ -429,7 +428,14 @@ namespace loot { //Now try parsing the masterlist. BOOST_LOG_TRIVIAL(debug) << "Testing masterlist parsing."; try { - this->MetadataList::Load(game.MasterlistPath()); + loot::ifstream in(game.MasterlistPath()); + YAML::Node mlist = YAML::Load(in); + in.close(); + + if (mlist["globals"]) + messages = mlist["globals"].as< list >(); + if (mlist["plugins"]) + plugins = mlist["plugins"].as< list >(); for (auto &plugin: plugins) { plugin.EvalAllConditions(game, language); @@ -460,12 +466,10 @@ namespace loot { BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD."; git.call(git_checkout_head(git.repo, &checkout_opts)); - if (parsingError.empty()) - parsingError = boost::locale::translate("Masterlist revision").str() + " " + string(revision) + ": " + e.what() + " " + boost::locale::translate("Rolled back to the previous revision.").str(); + 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())); } } while (parsingFailed); - if (!parsingError.empty()) - throw error(error::ok, parsingError); //Throw an OK because the process still completed in a successful state. + return pair(revision, date); } } diff --git a/src/backend/network.h b/src/backend/network.h new file mode 100644 index 00000000..51922343 --- /dev/null +++ b/src/backend/network.h @@ -0,0 +1,40 @@ +/* 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 + . +*/ + +#ifndef __LOOT_NETWORK__ +#define __LOOT_NETWORK__ + +#include +#include + +#include "game.h" +#include "metadata.h" + +namespace loot { + + std::pair UpdateMasterlist(Game& game, std::list& parsingErrors, std::list& plugins, std::list& messages, const unsigned int language); + + std::pair GetMasterlistRevision(const Game& game); +} +#endif diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 69162cf4..64d33e1f 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -33,6 +33,7 @@ #include "../backend/error.h" #include "../backend/helpers.h" #include "../backend/generators.h" +#include "../backend/network.h" #include "../backend/streams.h" #include "../backend/graph.h" @@ -70,6 +71,123 @@ using boost::format; namespace fs = boost::filesystem; namespace loc = boost::locale; + +struct plugin_loader { + plugin_loader(loot::Plugin& plugin, loot::Game& game) : _plugin(plugin), _game(game) { + } + + void operator () () { + _plugin = loot::Plugin(_game, _plugin.Name(), false); + } + + loot::Plugin& _plugin; + loot::Game& _game; + string _filename; + bool _b; +}; + +struct plugin_list_loader { + plugin_list_loader(list& plugins, loot::Game& game) : _plugins(plugins), _game(game) {} + + void operator () () { + for (auto &plugin : _plugins) { + if (skipPlugins.find(plugin.Name()) == skipPlugins.end()) { + plugin = loot::Plugin(_game, plugin.Name(), false); + } + } + } + + list& _plugins; + loot::Game& _game; + set skipPlugins; +}; + +struct masterlist_updater_parser { + masterlist_updater_parser(bool doUpdate, loot::Game& game, list& errors, list& plugins, list& messages, string& revision, string& date, const unsigned int language) : _doUpdate(doUpdate), _game(game), _errors(errors), _plugins(plugins), _messages(messages), _revision(revision), _date(date), _language(language) {} + + void operator () () { + + if (_doUpdate) { + BOOST_LOG_TRIVIAL(debug) << "Updating masterlist"; + try { + pair ret = UpdateMasterlist(_game, _errors, _plugins, _messages, _language); + _revision = ret.first; + _date = ret.second; + } catch (std::exception& e) { + _plugins.clear(); + _messages.clear(); + BOOST_LOG_TRIVIAL(error) << "Masterlist update failed. Details: " << e.what(); + _errors.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist update failed. Details: %1%")) % e.what()).str())); + //Try getting masterlist revision anyway. + try { + pair ret = GetMasterlistRevision(_game); + _revision = ret.first; + _date = ret.second; + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Masterlist revision check failed. Details: " << e.what(); + _errors.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist revision check failed. Details: %1%")) % e.what()).str())); + } + } + } + else { + BOOST_LOG_TRIVIAL(debug) << "Getting masterlist revision"; + try { + pair ret = GetMasterlistRevision(_game); + _revision = ret.first; + _date = ret.second; + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Masterlist revision check failed. Details: " << e.what(); + _errors.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist revision check failed. Details: %1%")) % e.what()).str())); + } + } + + if (_plugins.empty() && _messages.empty() && fs::exists(_game.MasterlistPath())) { + + BOOST_LOG_TRIVIAL(debug) << "Parsing masterlist..."; + try { + loot::ifstream in(_game.MasterlistPath()); + YAML::Node mlist = YAML::Load(in); + in.close(); + + if (mlist["globals"]) + _messages = mlist["globals"].as< list >(); + if (mlist["plugins"]) + _plugins = mlist["plugins"].as< list >(); + } catch (YAML::Exception& e) { + BOOST_LOG_TRIVIAL(error) << "Masterlist parsing failed. Details: " << e.what(); + _errors.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist parsing failed. Details: %1%")) % e.what()).str())); + } + BOOST_LOG_TRIVIAL(debug) << "Finished parsing masterlist."; + + } + + if (_revision.empty()) { + if (fs::exists(_game.MasterlistPath())) + _revision = loc::translate("Unknown"); + else + _revision = loc::translate("No masterlist"); + } + + if (_date.empty()) { + if (fs::exists(_game.MasterlistPath())) + _date = loc::translate("Unknown"); + else + _date = loc::translate("No masterlist"); + } + } + + bool _doUpdate; + loot::Game& _game; + list& _errors; + list& _plugins; + list& _messages; + string& _revision; + string& _date; + unsigned int _language; +}; + bool LOOT::OnInit() { //Check if GUI is already running. @@ -628,6 +746,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { list mlist_plugins, ulist_plugins; list plugins; boost::thread_group group; + string revision, date; wxProgressDialog *progDia = new wxProgressDialog(translate("LOOT: Working..."),translate("LOOT working..."), 1000, this, wxPD_APP_MODAL|wxPD_AUTO_HIDE|wxPD_ELAPSED_TIME); @@ -645,14 +764,8 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { BOOST_LOG_TRIVIAL(info) << "Using message language: " << Language(lang).Name(); bool doUpdate = _settings["Update Masterlist"] && _settings["Update Masterlist"].as(); - group.create_thread([this, lang, &messages]() { - try { - this->_game->masterlist.Load(*this->_game, lang); - } - catch (exception &e) { - messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist parsing failed. Details: %1%")) % e.what()).str())); - } - }); + masterlist_updater_parser mup(doUpdate, *_game, messages, mlist_plugins, mlist_messages, revision, date, lang); + group.create_thread(mup); //First calculate the mean plugin size. Store it temporarily in a map to reduce filesystem lookups and file size recalculation. size_t meanFileSize = 0; @@ -669,7 +782,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { meanFileSize /= tempMap.size(); //Now load plugins. - PluginsLoader pll(plugins, *_game); + plugin_list_loader pll(plugins, *_game); for (const auto &pluginPair: tempMap) { BOOST_LOG_TRIVIAL(info) << "Found plugin: " << pluginPair.first; @@ -678,7 +791,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { if (pluginPair.second > meanFileSize) { pll.skipPlugins.insert(pluginPair.first); - PluginLoader pl(plugins.back(), *_game); + plugin_loader pl(plugins.back(), *_game); group.create_thread(pl); } @@ -962,8 +1075,8 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { GenerateReportData(*_game, messages, plugins, - _game->masterlist.GetRevision(_game->MasterlistPath()), - _game->masterlist.GetDate(_game->MasterlistPath()), + revision, + date, doUpdate); } catch (std::exception& e) { wxMessageBox(