mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Merged "graph-visualisation" into "master".
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<Message>& messages,
|
||||
const std::list<Plugin>& 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<Plugin>& 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());
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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() + "\".");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/crc.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <alphanum.hpp>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -31,99 +31,12 @@
|
||||
|
||||
#include <git2.h>
|
||||
|
||||
#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;
|
||||
|
||||
+17
-5
@@ -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;
|
||||
@@ -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.
|
||||
@@ -847,7 +858,8 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
|
||||
plugins,
|
||||
oldDetails,
|
||||
revision,
|
||||
_settings["Update Masterlist"].as<bool>());
|
||||
_settings["Update Masterlist"].as<bool>(),
|
||||
_game.GraphPath().string());
|
||||
} catch (boss::error& e) {
|
||||
wxMessageBox(
|
||||
FromUTF8(format(loc::translate("Error: %1%")) % e.what()),
|
||||
|
||||
Reference in New Issue
Block a user