From 397ea3e7ffcd5bffe9b3d478bba9d7e9a65da8fa Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 12 Aug 2013 15:27:00 +0100 Subject: [PATCH 1/4] Issue #45. Moved process creation code to helpers, other misc changes. --- src/backend/error.h | 2 + src/backend/game.cpp | 4 ++ src/backend/game.h | 1 + src/backend/globals.cpp | 1 - src/backend/globals.h | 1 - src/backend/graph.h | 2 +- src/backend/helpers.cpp | 86 ++++++++++++++++++++++++++++++++++++++++ src/backend/helpers.h | 3 ++ src/backend/network.cpp | 87 ----------------------------------------- 9 files changed, 97 insertions(+), 90 deletions(-) diff --git a/src/backend/error.h b/src/backend/error.h index b18bbb0a..5963b138 100644 --- a/src/backend/error.h +++ b/src/backend/error.h @@ -51,6 +51,8 @@ namespace boss { static const unsigned int no_game_detected = 10; static const unsigned int subversion_error = 11; static const unsigned int git_error = 12; + static const unsigned int windows_error = 13; + static const unsigned int sorting_error = 14; private: std::string _what; unsigned int _code; diff --git a/src/backend/game.cpp b/src/backend/game.cpp index f58e40c6..2858180d 100644 --- a/src/backend/game.cpp +++ b/src/backend/game.cpp @@ -217,6 +217,10 @@ namespace boss { return g_path_local / bossFolderName / "report.html"; } + fs::path Game::GraphPath() const { + return g_path_local / bossFolderName / "graph.svg"; + } + void Game::RefreshActivePluginsList() { lo_game_handle gh; char ** pluginArr; diff --git a/src/backend/game.h b/src/backend/game.h index 88565ca1..6c7d75bd 100644 --- a/src/backend/game.h +++ b/src/backend/game.h @@ -67,6 +67,7 @@ namespace boss { boost::filesystem::path MasterlistPath() const; boost::filesystem::path UserlistPath() const; boost::filesystem::path ReportPath() const; + boost::filesystem::path GraphPath() const; bool IsActive(const std::string& plugin) const; diff --git a/src/backend/globals.cpp b/src/backend/globals.cpp index 171cef07..7f8d530a 100644 --- a/src/backend/globals.cpp +++ b/src/backend/globals.cpp @@ -62,5 +62,4 @@ namespace boss { const boost::filesystem::path g_path_log = g_path_local / "BOSSDebugLog.txt"; const boost::filesystem::path g_path_l10n = "resources/l10n"; const boost::filesystem::path g_path_graphvis = "resources/graphvis/dot.exe"; - const boost::filesystem::path g_path_graph = g_path_local / "graph.svg"; } diff --git a/src/backend/globals.h b/src/backend/globals.h index 3d3cccb1..7768efa4 100644 --- a/src/backend/globals.h +++ b/src/backend/globals.h @@ -63,7 +63,6 @@ namespace boss { extern const boost::filesystem::path g_path_log; extern const boost::filesystem::path g_path_l10n; extern const boost::filesystem::path g_path_graphvis; - extern const boost::filesystem::path g_path_graph; } #endif diff --git a/src/backend/graph.h b/src/backend/graph.h index fe5a26f1..cacc5ec8 100644 --- a/src/backend/graph.h +++ b/src/backend/graph.h @@ -47,7 +47,7 @@ namespace boss { vertex_t vSource = boost::source(e, g); vertex_t vTarget = boost::target(e, g); - throw boss::error(boss::error::invalid_args, "Back edge detected between plugins \"" + g[vSource]->Name() + "\" and \"" + g[vTarget]->Name() + "\"."); + throw boss::error(boss::error::sorting_error, "Back edge detected between plugins \"" + g[vSource]->Name() + "\" and \"" + g[vTarget]->Name() + "\"."); } }; diff --git a/src/backend/helpers.cpp b/src/backend/helpers.cpp index 45dd8e49..400ba368 100644 --- a/src/backend/helpers.cpp +++ b/src/backend/helpers.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include @@ -50,6 +51,7 @@ # include "windows.h" # include "shlobj.h" #endif +#define BUFSIZE 4096 namespace boss { using namespace std; @@ -264,6 +266,90 @@ namespace boss { return g_lang_any; } + //Runs a command using the Win32 API. + bool RunCommand(const std::string& command, std::string& output) { + HANDLE consoleWrite = NULL; + HANDLE consoleRead = NULL; + + SECURITY_ATTRIBUTES saAttr; + + PROCESS_INFORMATION piProcInfo; + STARTUPINFO siStartInfo; + + CHAR chBuf[BUFSIZE]; + DWORD dwRead; + + DWORD exitCode; + + //Init attributes. + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + BOOST_LOG_TRIVIAL(trace) << "Creating a pipe for the process."; + + //Create I/O pipes. + if (!CreatePipe(&consoleRead, &consoleWrite, &saAttr, 0)) { + BOOST_LOG_TRIVIAL(error) << "Could not create pipe for process."; + throw error(error::windows_error, "Could not create pipe for process."); + } + + //Create a child process. + BOOST_LOG_TRIVIAL(trace) << "Creating a child process."; + ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); + + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdError = consoleWrite; + siStartInfo.hStdOutput = consoleWrite; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + siStartInfo.wShowWindow = SW_HIDE; + + const int utf16Len = MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, NULL, 0); + wchar_t * cmdLine = new wchar_t[utf16Len]; + MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, cmdLine, utf16Len); + + bool result = CreateProcess(NULL, + cmdLine, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + CREATE_NO_WINDOW, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &piProcInfo); // receives PROCESS_INFORMATION + + delete [] cmdLine; + + if (!result) { + BOOST_LOG_TRIVIAL(error) << "Could not create process."; + throw error(error::windows_error, "Could not create process."); + } + + BOOST_LOG_TRIVIAL(trace) << "Waiting for process to complete."; + + WaitForSingleObject(piProcInfo.hProcess, INFINITE); + + BOOST_LOG_TRIVIAL(trace) << "Getting the process exit code."; + + if (!GetExitCodeProcess(piProcInfo.hProcess, &exitCode)) { + BOOST_LOG_TRIVIAL(error) << "Could not get process exit code."; + throw error(error::windows_error, "Could not get process exit code."); + } + + BOOST_LOG_TRIVIAL(trace) << "Getting the process output."; + + if (!ReadFile(consoleRead, chBuf, BUFSIZE, &dwRead, NULL)) { + BOOST_LOG_TRIVIAL(error) << "Could not read process output."; + throw error(error::windows_error, "Could not read process output."); + } + + output = string(chBuf, dwRead); + + return exitCode == 0; + } + ////////////////////////////// // Version Class Functions diff --git a/src/backend/helpers.h b/src/backend/helpers.h index 33493fc4..0b9e86e8 100644 --- a/src/backend/helpers.h +++ b/src/backend/helpers.h @@ -69,6 +69,9 @@ namespace boss { std::string GetLangString(const unsigned int num); unsigned int GetLangNum(const std::string& str); + //Runs a command using the Win32 API. + bool RunCommand(const std::string& command, std::string& output); + //Version class for more robust version comparisons. class Version { private: diff --git a/src/backend/network.cpp b/src/backend/network.cpp index ed25716d..8c73b1cd 100644 --- a/src/backend/network.cpp +++ b/src/backend/network.cpp @@ -31,99 +31,12 @@ #include -#if _WIN32 || _WIN64 -# ifndef UNICODE -# define UNICODE -# endif -# ifndef _UNICODE -# define _UNICODE -# endif -# include "windows.h" -# include "shlobj.h" -#endif -#define BUFSIZE 4096 - using namespace std; namespace fs = boost::filesystem; namespace boss { - bool RunCommand(const std::string& command, std::string& output) { - HANDLE consoleWrite = NULL; - HANDLE consoleRead = NULL; - - SECURITY_ATTRIBUTES saAttr; - - PROCESS_INFORMATION piProcInfo; - STARTUPINFO siStartInfo; - - CHAR chBuf[BUFSIZE]; - DWORD dwRead; - - DWORD exitCode; - - //Init attributes. - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; - - //Create I/O pipes. - if (!CreatePipe(&consoleRead, &consoleWrite, &saAttr, 0)) { - BOOST_LOG_TRIVIAL(error) << "Could not create pipe for Subversion process."; - throw error(error::subversion_error, "Could not create pipe for Subversion process."); - } - - //Create a child process. - BOOST_LOG_TRIVIAL(trace) << "Creating a child process."; - ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); - - ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); - siStartInfo.cb = sizeof(STARTUPINFO); - siStartInfo.hStdError = consoleWrite; - siStartInfo.hStdOutput = consoleWrite; - siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - siStartInfo.wShowWindow = SW_HIDE; - - const int utf16Len = MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, NULL, 0); - wchar_t * cmdLine = new wchar_t[utf16Len]; - MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, cmdLine, utf16Len); - - bool result = CreateProcess(NULL, - cmdLine, // command line - NULL, // process security attributes - NULL, // primary thread security attributes - TRUE, // handles are inherited - 0, // creation flags - NULL, // use parent's environment - NULL, // use parent's current directory - &siStartInfo, // STARTUPINFO pointer - &piProcInfo); // receives PROCESS_INFORMATION - - delete [] cmdLine; - - if (!result) { - BOOST_LOG_TRIVIAL(error) << "Could not create Subversion process."; - throw error(error::subversion_error, "Could not create Subversion process."); - } - - WaitForSingleObject(piProcInfo.hProcess, INFINITE); - - if (!GetExitCodeProcess(piProcInfo.hProcess, &exitCode)) { - BOOST_LOG_TRIVIAL(error) << "Could not get Subversion process exit code."; - throw error(error::subversion_error, "Could not get Subversion process exit code."); - } - - if (!ReadFile(consoleRead, chBuf, BUFSIZE, &dwRead, NULL)) { - BOOST_LOG_TRIVIAL(error) << "Could not read Subversion process output."; - throw error(error::subversion_error, "Could not read Subversion process output."); - } - - output = string(chBuf, dwRead); - - return exitCode == 0; - } - //Gets revision + date string. string GetRevision(const std::string& buffer) { string revision, date; From 7ac75cd853fbdcad727af1c2ad5c0d873752f4e7 Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 12 Aug 2013 15:39:24 +0100 Subject: [PATCH 2/4] Issue #45. BOSS now creates an SVG image of the graph. --- src/gui/main.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 8a3a68a1..0344d943 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -613,7 +613,6 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { progDia->Pulse(); BOOST_LOG_TRIVIAL(trace) << "Building the plugin dependency graph..."; - bool cyclicDependenciesExist = false; //Use an adjacency list (don't know yet if list or matrix is the better choice), and use "listS" as the VertexList type. We need a possible multi-graph to catch some forms of cyclic dependency (a working graph would not be a multi-graph though), so use "listS". Want a directed graph where we can access in-edges, so use "bidirectionalS". Also provide the boss::Plugin class as the vertex property type. @@ -713,8 +712,20 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { //The .dot file can be converted to an SVG using Graphviz: the command is `dot -Tsvg output.dot -o output.svg`. if (fs::exists(g_path_graphvis)) { BOOST_LOG_TRIVIAL(trace) << "Outputting the graph."; - boss::SaveGraph(graph, "output.dot"); + fs::path temp = fs::path(_game.GraphPath().string() + ".temp"); + boss::SaveGraph(graph, temp); + string command = g_path_graphvis.string() + " -Tsvg \"" + temp.string() + "\" -o \"" + _game.GraphPath().string() + "\""; + string output; + + try { + system(command.c_str()); + + // if (RunCommand(command, output)) //This hangs for graphvis, for some reason. Works for svn though... + fs::remove(temp); + } catch(boss::error& e) { + messages.push_back(boss::Message(boss::g_message_error, (format(loc::translate("Failed to generate graph image. Details: %1%")) % e.what()).str())); + } } //Check for back-edges, then perform a topological sort. From 0dad6c305d914ffed92d3605b4c2fd29df1d7e2f Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 12 Aug 2013 17:41:11 +0100 Subject: [PATCH 3/4] Issue #45. Plugin graph is now displayed in the BOSS Report. --- src/backend/generators.h | 23 +++++++++++++++++++++-- src/gui/main.cpp | 3 ++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/backend/generators.h b/src/backend/generators.h index e3a6555b..bd9321bb 100644 --- a/src/backend/generators.h +++ b/src/backend/generators.h @@ -158,6 +158,12 @@ namespace boss { div.append_attribute("data-section").set_value("plugins"); div.text().set("Details"); + div = nav.append_child(); + div.set_name("div"); + div.append_attribute("class").set_value("button"); + div.append_attribute("data-section").set_value("graph"); + div.text().set("Graph"); + div = nav.append_child(); div.set_name("div"); div.append_attribute("class").set_value("button"); @@ -347,6 +353,7 @@ namespace boss { inline void AppendMain(pugi::xml_node& body, const std::string& oldDetails, const std::string& masterlistVersion, + const std::string& graphPath, bool masterlistUpdateEnabled, const std::list& messages, const std::list& plugins, @@ -370,6 +377,17 @@ namespace boss { pluginMessageNo = messageNo; AppendSummary(main, hasChanged, masterlistVersion, masterlistUpdateEnabled, messageNo, warnNo, errorNo, messages); + + //Append graph tag. + pugi::xml_node graph = main.append_child(); + graph.set_name("div"); + graph.append_attribute("id").set_value("graph"); + graph.append_attribute("class").set_value("hidden"); + + pugi::xml_node img = graph.append_child(); + img.set_name("img"); + img.append_attribute("src").set_value(ToFileURL(graphPath).c_str()); + img.append_attribute("alt").set_value("Plugin interactions graph."); } inline void AppendFilters(pugi::xml_node& body, int messageNo, int pluginNo) { @@ -474,7 +492,8 @@ namespace boss { const std::list& plugins, const std::string& oldDetails, const std::string& masterlistVersion, - const bool masterlistUpdateEnabled) { + const bool masterlistUpdateEnabled, + const std::string& graphPath) { pugi::xml_document doc; @@ -486,7 +505,7 @@ namespace boss { AppendNav(body); int messageNo=0; - AppendMain(body, oldDetails, masterlistVersion, masterlistUpdateEnabled, messages, plugins, messageNo); + AppendMain(body, oldDetails, masterlistVersion, graphPath, masterlistUpdateEnabled, messages, plugins, messageNo); AppendFilters(body, messageNo, plugins.size()); diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 0344d943..9f26c2d9 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -858,7 +858,8 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { plugins, oldDetails, revision, - _settings["Update Masterlist"].as()); + _settings["Update Masterlist"].as(), + _game.GraphPath().string()); } catch (boss::error& e) { wxMessageBox( FromUTF8(format(loc::translate("Error: %1%")) % e.what()), From 49e9902de1d57b34decedeb6341a0d8ac87c85da Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 12 Aug 2013 22:19:39 +0100 Subject: [PATCH 4/4] Disabled masterlist updating again. --- src/gui/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 9f26c2d9..abf7a114 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -461,8 +461,8 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { wxProgressDialog *progDia = new wxProgressDialog(translate("BOSS: Working..."),translate("BOSS working..."), 1000, this, wxPD_APP_MODAL|wxPD_AUTO_HIDE|wxPD_ELAPSED_TIME); - masterlist_updater_parser mup(_game, messages, mlist_plugins, mlist_messages, revision); - group.create_thread(mup); + // masterlist_updater_parser mup(_game, messages, mlist_plugins, mlist_messages, revision); + // 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;