diff --git a/src/backend/globals.h b/src/backend/globals.h index 64f5b215..5e523bb5 100644 --- a/src/backend/globals.h +++ b/src/backend/globals.h @@ -55,7 +55,6 @@ namespace boss { const boost::filesystem::path libespm_options_path = "resources/libespm.yaml"; const boost::filesystem::path svn_path = "resources/svn/svn.exe"; const boost::filesystem::path log_path = "BOSSDebugLog.txt"; - const boost::filesystem::path svn_log_path = "svn.log"; } #endif diff --git a/src/backend/network.cpp b/src/backend/network.cpp index d80ae5db..261f8335 100644 --- a/src/backend/network.cpp +++ b/src/backend/network.cpp @@ -25,7 +25,17 @@ #include "error.h" #include "parsers.h" -#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; @@ -33,22 +43,88 @@ namespace fs = boost::filesystem; namespace boss { - unsigned int UpdateMasterlist(const Game& game, std::vector& parsingErrors) { - string command; - //First check if the working copy is set up or not. - command = svn_path.string() + " info " + game.MasterlistPath().parent_path().string() + " > " + svn_log_path.string(); + 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; - if (system(command.c_str()) != 0) { + 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)) + throw error(ERROR_SUBVERSION_ERROR, "Could not create pipe for Subversion process."); + + //Create 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; + + 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) + throw error(ERROR_SUBVERSION_ERROR, "Could not create Subversion process."); + + WaitForSingleObject(piProcInfo.hProcess, INFINITE); + + if (!GetExitCodeProcess(piProcInfo.hProcess, &exitCode)) + throw error(ERROR_SUBVERSION_ERROR, "Could not get Subversion process exit code."); + + if (!ReadFile(consoleRead, chBuf, BUFSIZE, &dwRead, NULL)) + throw error(ERROR_SUBVERSION_ERROR, "Could not read Subversion process output."); + + output = string(chBuf, dwRead); + + return exitCode == 0; + } + + std::string UpdateMasterlist(const Game& game, std::vector& parsingErrors) { + + string command, output; + //First check if the working copy is set up or not. + command = svn_path.string() + " info " + game.MasterlistPath().parent_path().string(); + + if (!RunCommand(command, output)) { //Working copy not set up, perform a checkout. - command = svn_path.string() + " co --depth empty " + game.URL().substr(0, game.URL().rfind('/')) + " " + game.MasterlistPath().parent_path().string() + "/. > " + svn_log_path.string(); - if (system(command.c_str()) != 0) - throw error(ERROR_SUBVERSION_ERROR, "Subversion could not perform a checkout. See " + svn_log_path.string() + " in the BOSS folder for details."); + command = svn_path.string() + " co --depth empty " + game.URL().substr(0, game.URL().rfind('/')) + " " + game.MasterlistPath().parent_path().string() + "/."; + if (!RunCommand(command, output)) + throw error(ERROR_SUBVERSION_ERROR, "Subversion could not perform a checkout. Details: " + output); } //Now update masterlist. - command = svn_path.string() + " update " + game.MasterlistPath().string() + " > " + svn_log_path.string(); - if (system(command.c_str()) != 0) - throw error(ERROR_SUBVERSION_ERROR, "Subversion could not update the masterlist. See " + svn_log_path.string() + " in the BOSS folder for details."); + command = svn_path.string() + " update " + game.MasterlistPath().string(); + if (!RunCommand(command, output)) + throw error(ERROR_SUBVERSION_ERROR, "Subversion could not update the masterlist. Details: " + output); //Now test masterlist to see if it parses OK. bool good = false; @@ -59,31 +135,27 @@ namespace boss { } catch (YAML::Exception& e) { //Roll back one revision if there's an error. parsingErrors.push_back(e.what()); - command = svn_path.string() + " update --revision PREV " + game.MasterlistPath().string() + " > " + svn_log_path.string(); - if (system(command.c_str()) != 0) - throw error(ERROR_SUBVERSION_ERROR, "Subversion could not update the masterlist. See " + svn_log_path.string() + " in the BOSS folder for details."); + command = svn_path.string() + " update --revision PREV " + game.MasterlistPath().string(); + if (!RunCommand(command, output)) + throw error(ERROR_SUBVERSION_ERROR, "Subversion could not update the masterlist. Details: " + output); } } //Now get the masterlist revision. Can either create a pipe using the Win32 API (http://msdn.microsoft.com/en-us/library/ms682499.aspx), or output to a file, read it, then delete it. - command = svn_path.string() + " info " + game.MasterlistPath().parent_path().string() + " > " + svn_log_path.string(); - if (system(command.c_str()) != 0) - throw error(ERROR_SUBVERSION_ERROR, "Subversion could not read the masterlist revision number. See " + svn_log_path.string() + " in the BOSS folder for details."); + command = svn_path.string() + " info " + game.MasterlistPath().string(); + if (!RunCommand(command, output)) + throw error(ERROR_SUBVERSION_ERROR, "Subversion could not read the masterlist revision number. Details: " + output); - ifstream in(svn_log_path.string().c_str()); + size_t pos1 = output.rfind("Revision: "); + size_t pos2 = output.find('\n', pos1); - int revision = 0; - while (in.good()) { - string line; - getline(in, line); + string revision = output.substr(pos1+10, pos2-pos1-10); - if (line.substr(0, 10) == "Revision: ") - revision = atoi(line.substr(10).c_str()); - } - in.close(); + pos1 = output.find("Last Changed Date: ", pos2); + pos2 = output.find(' ', pos1+19); - fs::remove(svn_log_path); + string date = output.substr(pos1+19, pos2-pos1-19); - return revision; + return revision + " (" + date + ")"; } } diff --git a/src/backend/network.h b/src/backend/network.h index ed30be63..eb8c343f 100644 --- a/src/backend/network.h +++ b/src/backend/network.h @@ -57,6 +57,6 @@ namespace boss { ```svn info masterlist.txt``` returns a bunch of info: the line "Revision: XXXX" contains the revision number. */ - unsigned int UpdateMasterlist(const Game& game, std::vector& parsingErrors); + std::string UpdateMasterlist(const Game& game, std::vector& parsingErrors); } #endif diff --git a/src/gui/main.cpp b/src/gui/main.cpp index f07be18d..f240804f 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -323,8 +323,9 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { out << "Updating masterlist..." << endl; vector parsingErrors; + string revision; try { - UpdateMasterlist(_game, parsingErrors); + revision = UpdateMasterlist(_game, parsingErrors); } catch (boss::error& e) { //LOG_ERROR("Error: %s", e.what()); wxMessageBox( @@ -625,7 +626,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { messages, plugins, oldDetails, - "4030 (2020-13-13)", + revision, _settings["Update Masterlist"].as()); } catch (boss::error& e) { wxMessageBox(