From 2f5d51071a9e4525ae915f5247b3307f0890a1ec Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Tue, 26 Aug 2014 23:56:14 +0100 Subject: [PATCH] Getting game data no longer blocks UI. Progress dialogs added for initialisation, game change and window refocus refresh. Part of #246. --- resources/report/js/script.js | 13 +- src/gui/handler.cpp | 302 +++++++++++++++++----------------- src/gui/handler.h | 2 +- 3 files changed, 163 insertions(+), 154 deletions(-) diff --git a/resources/report/js/script.js b/resources/report/js/script.js index 8aade6f1..804932de 100644 --- a/resources/report/js/script.js +++ b/resources/report/js/script.js @@ -561,6 +561,8 @@ function changeGame(evt) { loot.games[index].masterlist = loot.game.masterlist; loot.games[index].plugins = loot.game.plugins; + updateProgressDialog('Loading game data...'); + openProgressDialog(); /* Now send off a CEF query with the folder name of the new game. */ var request = JSON.stringify({ @@ -594,7 +596,7 @@ function changeGame(evt) { var gameInfo = JSON.parse(result, jsonToPlugin); } catch (e) { console.log(e); - console.log('getGameData response: ' + result); + console.log('changeGame response: ' + result); } /* This may not be the first time loading this game this instance of @@ -656,6 +658,8 @@ function changeGame(evt) { /* Reapply previously active filters. */ applyFilters(); + + closeProgressDialog(); }).catch(processCefError); } function openReadme(evt) { @@ -1249,6 +1253,8 @@ function initVars() { parallelPromises.push(loot.query('getGameData')); } + updateProgressDialog('Initialising user interface...'); + openProgressDialog(); Promise.all(parallelPromises).then(function(results) { try { loot.gameTypes = JSON.parse(results[0]); @@ -1298,12 +1304,15 @@ function initVars() { document.getElementById('settingsButton').click(); } + closeProgressDialog(); }).catch(processCefError); }).catch(processCefError); } function onFocus() { /* Send a query for updated load order and plugin header info. */ + updateProgressDialog('Refreshing data...'); + openProgressDialog(); loot.query('getGameData').then(function(result){ /* Parse the data sent from C++. */ try { @@ -1368,6 +1377,8 @@ function onFocus() { /* Reapply filters. */ applyFilters(); + + closeProgressDialog(); }).catch(processCefError); } function checkFocus(){ diff --git a/src/gui/handler.cpp b/src/gui/handler.cpp index 6c0bb338..53b2bc83 100644 --- a/src/gui/handler.cpp +++ b/src/gui/handler.cpp @@ -126,18 +126,7 @@ namespace loot { #ifdef _WIN32 SetWindowText(handle, ToWinWide("LOOT: " + g_app_state.CurrentGame().Name()).c_str()); #endif - try { - callback->Success(GetGameData()); - } - catch (error &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to get game data. " << e.what(); - callback->Failure(e.code(), e.what()); - } - catch (exception &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to get game data. " << e.what(); - callback->Failure(-1, e.what()); - } - return true; + return CefPostTask(TID_FILE, base::Bind(&Handler::GetGameData, base::Unretained(this), callback)); } else if (request == "cancelFind") { browser->GetHost()->StopFinding(true); @@ -220,8 +209,7 @@ namespace loot { #ifdef _WIN32 SetWindowText(handle, ToWinWide("LOOT: " + g_app_state.CurrentGame().Name()).c_str()); #endif - - callback->Success(GetGameData()); + CefPostTask(TID_FILE, base::Bind(&Handler::GetGameData, base::Unretained(this), callback)); } catch (loot::error &e) { BOOST_LOG_TRIVIAL(error) << "Failed to change game. Details: " << e.what(); @@ -561,160 +549,170 @@ namespace loot { return "[]"; } - std::string Handler::GetGameData() { - /* GetGameData() can be called for initialising the UI for a game for the first time - in a session, or it can be called when changing to a game that has previously been - active. In the first case, all data should be loaded, but in the second, only load - order and plugin header info should be re-loaded. - Determine which case it is by checking to see if the game's plugins object is empty. - */ - BOOST_LOG_TRIVIAL(info) << "Getting data specific to LOOT's active game."; - // Get masterlist revision info and parse if it exists. Also get plugin headers info and parse userlist if it exists. + void Handler::GetGameData(CefRefPtr callback) { + try { + /* GetGameData() can be called for initialising the UI for a game for the first time + in a session, or it can be called when changing to a game that has previously been + active. In the first case, all data should be loaded, but in the second, only load + order and plugin header info should be re-loaded. + Determine which case it is by checking to see if the game's plugins object is empty. + */ + BOOST_LOG_TRIVIAL(info) << "Getting data specific to LOOT's active game."; + // Get masterlist revision info and parse if it exists. Also get plugin headers info and parse userlist if it exists. - bool isFirstLoad = g_app_state.CurrentGame().plugins.empty(); - g_app_state.CurrentGame().LoadPlugins(true); + bool isFirstLoad = g_app_state.CurrentGame().plugins.empty(); + g_app_state.CurrentGame().LoadPlugins(true); - //Sort plugins into their load order. - list installed; - list loadOrder; - g_app_state.CurrentGame().GetLoadOrder(loadOrder); - for (const auto &pluginName : loadOrder) { - const auto pos = g_app_state.CurrentGame().plugins.find(boost::locale::to_lower(pluginName)); + //Sort plugins into their load order. + list installed; + list loadOrder; + g_app_state.CurrentGame().GetLoadOrder(loadOrder); + for (const auto &pluginName : loadOrder) { + const auto pos = g_app_state.CurrentGame().plugins.find(boost::locale::to_lower(pluginName)); - if (pos != g_app_state.CurrentGame().plugins.end()) - installed.push_back(pos->second); - } - - if (isFirstLoad) { - //Parse masterlist, don't update it. - if (fs::exists(g_app_state.CurrentGame().MasterlistPath())) { - BOOST_LOG_TRIVIAL(debug) << "Parsing masterlist."; - try { - g_app_state.CurrentGame().masterlist.MetadataList::Load(g_app_state.CurrentGame().MasterlistPath()); - } - catch (exception &e) { - g_app_state.CurrentGame().masterlist.messages.push_back(Message(Message::error, string("An error occurred while parsing the masterlist: ") + e.what())); - } + if (pos != g_app_state.CurrentGame().plugins.end()) + installed.push_back(pos->second); } - //Parse userlist. - if (fs::exists(g_app_state.CurrentGame().UserlistPath())) { - BOOST_LOG_TRIVIAL(debug) << "Parsing userlist."; - try { - g_app_state.CurrentGame().userlist.Load(g_app_state.CurrentGame().UserlistPath()); - } - catch (exception &e) { - g_app_state.CurrentGame().userlist.messages.push_back(Message(Message::error, string("An error occurred while parsing the userlist: ") + e.what())); - } - } - } - - // Now convert to a single object that can be turned into a JSON string - //--------------------------------------------------------------------- - - // The data structure is to be set as 'loot.game'. - YAML::Node gameNode; - - // ID the game using its folder value. - gameNode["folder"] = g_app_state.CurrentGame().FolderName(); - - if (isFirstLoad) { - // Store the masterlist revision and date. - gameNode["masterlist"]["revision"] = g_app_state.CurrentGame().masterlist.GetRevision(g_app_state.CurrentGame().MasterlistPath()); - gameNode["masterlist"]["date"] = g_app_state.CurrentGame().masterlist.GetDate(g_app_state.CurrentGame().MasterlistPath()); - } - - // Now store plugin data. - for (const auto& plugin : installed) { - /* Each plugin has members while hold its raw masterlist and userlist data for - the editor, and also processed data for the main display. - */ - YAML::Node pluginNode; - // Find the masterlist metadata for this plugin. Treat Bash Tags from the plugin - // description as part of it. - BOOST_LOG_TRIVIAL(trace) << "Getting masterlist metadata for: " << plugin.Name(); - Plugin mlistPlugin(plugin); - mlistPlugin.MergeMetadata(g_app_state.CurrentGame().masterlist.FindPlugin(plugin)); - - // Now do the same again for any userlist data. - BOOST_LOG_TRIVIAL(trace) << "Getting userlist metadata for: " << plugin.Name(); - Plugin ulistPlugin(g_app_state.CurrentGame().userlist.FindPlugin(plugin)); - - pluginNode["__type"] = "Plugin"; // For conversion back into a JS typed object. - pluginNode["name"] = plugin.Name(); - pluginNode["isActive"] = g_app_state.CurrentGame().IsActive(plugin.Name()); - pluginNode["isDummy"] = false; // Set to false for now because we just don't know yet. - pluginNode["loadsBSA"] = plugin.LoadsBSA(g_app_state.CurrentGame()); - pluginNode["crc"] = IntToHexString(plugin.Crc()); - pluginNode["version"] = plugin.Version(); - if (isFirstLoad) { - if (!mlistPlugin.HasNameOnly()) { - // Now add the masterlist metadata to the pluginNode. - pluginNode["masterlist"]["after"] = mlistPlugin.LoadAfter(); - pluginNode["masterlist"]["req"] = mlistPlugin.Reqs(); - pluginNode["masterlist"]["inc"] = mlistPlugin.Incs(); - pluginNode["masterlist"]["msg"] = mlistPlugin.Messages(); - pluginNode["masterlist"]["tag"] = mlistPlugin.Tags(); - pluginNode["masterlist"]["dirty"] = mlistPlugin.DirtyInfo(); + //Parse masterlist, don't update it. + if (fs::exists(g_app_state.CurrentGame().MasterlistPath())) { + BOOST_LOG_TRIVIAL(debug) << "Parsing masterlist."; + try { + g_app_state.CurrentGame().masterlist.MetadataList::Load(g_app_state.CurrentGame().MasterlistPath()); + } + catch (exception &e) { + g_app_state.CurrentGame().masterlist.messages.push_back(Message(Message::error, string("An error occurred while parsing the masterlist: ") + e.what())); + } } - if (!ulistPlugin.HasNameOnly()) { - // Now add the userlist metadata to the pluginNode. - pluginNode["userlist"]["enabled"] = ulistPlugin.Enabled(); - pluginNode["userlist"]["after"] = ulistPlugin.LoadAfter(); - pluginNode["userlist"]["req"] = ulistPlugin.Reqs(); - pluginNode["userlist"]["inc"] = ulistPlugin.Incs(); - pluginNode["userlist"]["msg"] = ulistPlugin.Messages(); - pluginNode["userlist"]["tag"] = ulistPlugin.Tags(); - pluginNode["userlist"]["dirty"] = ulistPlugin.DirtyInfo(); + //Parse userlist. + if (fs::exists(g_app_state.CurrentGame().UserlistPath())) { + BOOST_LOG_TRIVIAL(debug) << "Parsing userlist."; + try { + g_app_state.CurrentGame().userlist.Load(g_app_state.CurrentGame().UserlistPath()); + } + catch (exception &e) { + g_app_state.CurrentGame().userlist.messages.push_back(Message(Message::error, string("An error occurred while parsing the userlist: ") + e.what())); + } } } - // Now merge masterlist and userlist metadata and evaluate, - // putting any resulting metadata into the base of the pluginNode. - YAML::Node derivedNode = GenerateDerivedMetadata(plugin, mlistPlugin, ulistPlugin); + // Now convert to a single object that can be turned into a JSON string + //--------------------------------------------------------------------- - for (auto it = derivedNode.begin(); it != derivedNode.end(); ++it) { - const string key = it->first.as(); - pluginNode[key] = it->second; + // The data structure is to be set as 'loot.game'. + YAML::Node gameNode; + + // ID the game using its folder value. + gameNode["folder"] = g_app_state.CurrentGame().FolderName(); + + if (isFirstLoad) { + // Store the masterlist revision and date. + gameNode["masterlist"]["revision"] = g_app_state.CurrentGame().masterlist.GetRevision(g_app_state.CurrentGame().MasterlistPath()); + gameNode["masterlist"]["date"] = g_app_state.CurrentGame().masterlist.GetDate(g_app_state.CurrentGame().MasterlistPath()); } - gameNode["plugins"].push_back(pluginNode); + // Now store plugin data. + for (const auto& plugin : installed) { + /* Each plugin has members while hold its raw masterlist and userlist data for + the editor, and also processed data for the main display. + */ + YAML::Node pluginNode; + // Find the masterlist metadata for this plugin. Treat Bash Tags from the plugin + // description as part of it. + BOOST_LOG_TRIVIAL(trace) << "Getting masterlist metadata for: " << plugin.Name(); + Plugin mlistPlugin(plugin); + mlistPlugin.MergeMetadata(g_app_state.CurrentGame().masterlist.FindPlugin(plugin)); + + // Now do the same again for any userlist data. + BOOST_LOG_TRIVIAL(trace) << "Getting userlist metadata for: " << plugin.Name(); + Plugin ulistPlugin(g_app_state.CurrentGame().userlist.FindPlugin(plugin)); + + pluginNode["__type"] = "Plugin"; // For conversion back into a JS typed object. + pluginNode["name"] = plugin.Name(); + pluginNode["isActive"] = g_app_state.CurrentGame().IsActive(plugin.Name()); + pluginNode["isDummy"] = false; // Set to false for now because we just don't know yet. + pluginNode["loadsBSA"] = plugin.LoadsBSA(g_app_state.CurrentGame()); + pluginNode["crc"] = IntToHexString(plugin.Crc()); + pluginNode["version"] = plugin.Version(); + + if (isFirstLoad) { + if (!mlistPlugin.HasNameOnly()) { + // Now add the masterlist metadata to the pluginNode. + pluginNode["masterlist"]["after"] = mlistPlugin.LoadAfter(); + pluginNode["masterlist"]["req"] = mlistPlugin.Reqs(); + pluginNode["masterlist"]["inc"] = mlistPlugin.Incs(); + pluginNode["masterlist"]["msg"] = mlistPlugin.Messages(); + pluginNode["masterlist"]["tag"] = mlistPlugin.Tags(); + pluginNode["masterlist"]["dirty"] = mlistPlugin.DirtyInfo(); + } + + if (!ulistPlugin.HasNameOnly()) { + // Now add the userlist metadata to the pluginNode. + pluginNode["userlist"]["enabled"] = ulistPlugin.Enabled(); + pluginNode["userlist"]["after"] = ulistPlugin.LoadAfter(); + pluginNode["userlist"]["req"] = ulistPlugin.Reqs(); + pluginNode["userlist"]["inc"] = ulistPlugin.Incs(); + pluginNode["userlist"]["msg"] = ulistPlugin.Messages(); + pluginNode["userlist"]["tag"] = ulistPlugin.Tags(); + pluginNode["userlist"]["dirty"] = ulistPlugin.DirtyInfo(); + } + } + + // Now merge masterlist and userlist metadata and evaluate, + // putting any resulting metadata into the base of the pluginNode. + YAML::Node derivedNode = GenerateDerivedMetadata(plugin, mlistPlugin, ulistPlugin); + + for (auto it = derivedNode.begin(); it != derivedNode.end(); ++it) { + const string key = it->first.as(); + pluginNode[key] = it->second; + } + + gameNode["plugins"].push_back(pluginNode); + } + + if (isFirstLoad) { + //Set language. + unsigned int language; + if (g_app_state.GetSettings()["language"]) + language = Language(g_app_state.GetSettings()["language"].as()).Code(); + else + language = Language::any; + BOOST_LOG_TRIVIAL(info) << "Using message language: " << Language(language).Name(); + + //Evaluate any conditions in the global messages. + BOOST_LOG_TRIVIAL(debug) << "Evaluating global message conditions."; + list messages = g_app_state.CurrentGame().masterlist.messages; + messages.insert(messages.end(), g_app_state.CurrentGame().userlist.messages.begin(), g_app_state.CurrentGame().userlist.messages.end()); + try { + list::iterator it = messages.begin(); + while (it != messages.end()) { + if (!it->EvalCondition(g_app_state.CurrentGame(), 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(Message(Message::error, (format(loc::translate("A global message contains a condition that could not be evaluated. Details: %1%")) % e.what()).str())); + } + + // Now store global messages from masterlist. + gameNode["globalMessages"] = messages; + } + + callback->Success(JSON::stringify(gameNode)); } - - if (isFirstLoad) { - //Set language. - unsigned int language; - if (g_app_state.GetSettings()["language"]) - language = Language(g_app_state.GetSettings()["language"].as()).Code(); - else - language = Language::any; - BOOST_LOG_TRIVIAL(info) << "Using message language: " << Language(language).Name(); - - //Evaluate any conditions in the global messages. - BOOST_LOG_TRIVIAL(debug) << "Evaluating global message conditions."; - list messages = g_app_state.CurrentGame().masterlist.messages; - messages.insert(messages.end(), g_app_state.CurrentGame().userlist.messages.begin(), g_app_state.CurrentGame().userlist.messages.end()); - try { - list::iterator it = messages.begin(); - while (it != messages.end()) { - if (!it->EvalCondition(g_app_state.CurrentGame(), 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(Message(Message::error, (format(loc::translate("A global message contains a condition that could not be evaluated. Details: %1%")) % e.what()).str())); - } - - // Now store global messages from masterlist. - gameNode["globalMessages"] = messages; + catch (loot::error &e) { + BOOST_LOG_TRIVIAL(error) << "Failed to change game. Details: " << e.what(); + callback->Failure(e.code(), string("Failed to change game. Details: ") + e.what()); + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Failed to change game. Details: " << e.what(); + callback->Failure(-1, string("Failed to change game. Details: ") + e.what()); } - - return JSON::stringify(gameNode); } void Handler::UpdateMasterlist(CefRefPtr callback) { diff --git a/src/gui/handler.h b/src/gui/handler.h index bfa8499d..d4d72978 100644 --- a/src/gui/handler.h +++ b/src/gui/handler.h @@ -56,7 +56,7 @@ namespace loot { std::string GetLanguages(); std::string GetGameTypes(); std::string GetInstalledGames(); - std::string GetGameData(); + void GetGameData(CefRefPtr callback); void UpdateMasterlist(CefRefPtr callback); std::string ClearAllMetadata(); void SortPlugins(CefRefPtr callback);