Added new POpen with windows support since windows builtin _popen pops up a window on each call which is not acceptable. Removed a bunch of warnings as well.

This commit is contained in:
Jonas Drewsen 2013-02-08 21:59:13 +01:00
Родитель d6a5621966
Коммит 2942f8a04b
17 изменённых файлов: 409 добавлений и 122 удалений

Просмотреть файл

@ -33,6 +33,18 @@ void ConvertUnityPathName( const char* utf8, wchar_t* outBuffer, int outBufferSi
ConvertSeparatorsToWindows( outBuffer );
}
string PluginPath()
{
HMODULE hModule = GetModuleHandleW(NULL);
if (hModule == NULL)
return "";
CHAR path[MAX_PATH];
if (GetModuleFileNameA(hModule, path, MAX_PATH) == 0)
return "";
return path;
}
const size_t kDefaultPathBufferSize = 1024;
bool EnsureDirectory(const string& path)
@ -78,14 +90,14 @@ bool IsDirectory(const string& path)
{
wchar_t widePath[kDefaultPathBufferSize];
ConvertUnityPathName(path.c_str(), widePath, kDefaultPathBufferSize);
return PathIsDirectoryW(widePath);
return PathIsDirectoryW(widePath) == TRUE;
}
bool PathExists(const std::string& path)
{
wchar_t widePath[kDefaultPathBufferSize];
ConvertUnityPathName(path.c_str(), widePath, kDefaultPathBufferSize);
return PathFileExistsW(widePath);
return PathFileExistsW(widePath) == TRUE;
}
static bool RemoveReadOnlyW(LPCWSTR path)

Просмотреть файл

@ -13,4 +13,5 @@ bool PathExists(const std::string& path);
#if WIN32
#include "windows.h"
void ConvertUnityPathName( const char* utf8, wchar_t* outBuffer, int outBufferSize );
std::string PluginPath();
#endif

Просмотреть файл

@ -23,11 +23,16 @@ LogWriter& Endl(LogWriter& w)
LogStream::LogStream(const std::string& path, LogLevel level)
: m_Stream(path.c_str(), std::ios_base::ate),
m_OnWriter(*this, true),
m_OffWriter(*this, false)
m_OnWriter(Self(), true),
m_OffWriter(Self(), false)
{
}
LogStream& LogStream::Self(void)
{
// Work around C4355
return *this;
}
LogStream::~LogStream()
{

Просмотреть файл

@ -55,6 +55,8 @@ class LogStream
public:
LogStream(const std::string& path, LogLevel level = LOG_NOTICE);
~LogStream();
LogStream& Self(void);
void SetLogLevel(LogLevel l);
LogLevel GetLogLevel() const;

297
Common/POpen.cpp Normal file
Просмотреть файл

@ -0,0 +1,297 @@
#include "Utility.h"
using namespace std;
#if defined(_WINDOWS)
#include "string.h"
#include "FileSystem.h"
#define BUFSIZE 4096
POpen::POpen(const string& cmd) : m_Command(cmd)
, m_ChildStd_IN_Rd(NULL)
, m_ChildStd_IN_Wr(NULL)
, m_ChildStd_OUT_Rd(NULL)
, m_ChildStd_OUT_Wr(NULL)
, m_BufSize(0)
{
//
// Setup pipes
//
SECURITY_ATTRIBUTES saAttr;
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
Enforce<PluginException>(CreatePipe(&m_ChildStd_OUT_Rd, &m_ChildStd_OUT_Wr, &saAttr, 0) == TRUE, string("Error creating STDOUT for '") + cmd + "' " + ErrorCodeToMsg(GetLastError()));
// Ensure the read handle to the pipe for STDOUT is not inherited.
Enforce<PluginException>(SetHandleInformation(m_ChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) == TRUE, string("Error STDOUT no inherit '") + cmd + "' " + ErrorCodeToMsg(GetLastError()));
// Create a pipe for the child process's STDIN.
Enforce<PluginException>(CreatePipe(&m_ChildStd_IN_Rd, &m_ChildStd_IN_Wr, &saAttr, 0) == TRUE, string("Error creating STDIN for '") + cmd + "' " + ErrorCodeToMsg(GetLastError()));
// Ensure the write handle to the pipe for STDIN is not inherited.
Enforce<PluginException>(SetHandleInformation(m_ChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0) == TRUE, string("Error STDIN no inherit '") + cmd + "'" + ErrorCodeToMsg(GetLastError()));
//
// Create child process
//
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory( &m_ProcInfo, sizeof(PROCESS_INFORMATION) );
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = m_ChildStd_OUT_Wr;
siStartInfo.hStdOutput = m_ChildStd_OUT_Wr;
siStartInfo.hStdInput = m_ChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
siStartInfo.wShowWindow = SW_HIDE;
// Create the child process.
bSuccess = CreateProcess(NULL,
TEXT(const_cast<char *>(m_Command.c_str())), // 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
&m_ProcInfo); // receives PROCESS_INFORMATION
// If an error occurs, exit the application.
Enforce<PluginException>(bSuccess == TRUE, string("Could not start '") + cmd + "'" + ErrorCodeToMsg(GetLastError()));
// Close the client pipe ends here or we will keep the handles open even though the
// client has terminated. This is turn would block reads/writes and we would get EOL.
CloseHandle(m_ChildStd_OUT_Wr);
CloseHandle(m_ChildStd_IN_Rd);
}
POpen::~POpen()
{
string msg = "";
if (m_ChildStd_IN_Rd != NULL && !CloseHandle(m_ChildStd_IN_Rd))
msg += ErrorCodeToMsg(GetLastError()) + " - ";
if (m_ChildStd_IN_Wr != NULL && !CloseHandle(m_ChildStd_IN_Wr))
msg += ErrorCodeToMsg(GetLastError()) + " - ";
if (m_ChildStd_OUT_Rd != NULL && !CloseHandle(m_ChildStd_OUT_Rd))
msg += ErrorCodeToMsg(GetLastError()) + " - ";
if (m_ChildStd_OUT_Wr != NULL && !CloseHandle(m_ChildStd_OUT_Wr))
msg += ErrorCodeToMsg(GetLastError()) + " - ";
if (m_ProcInfo.hProcess != NULL && !CloseHandle(m_ProcInfo.hProcess))
msg += ErrorCodeToMsg(GetLastError()) + " - ";
if (m_ProcInfo.hThread != NULL && !CloseHandle(m_ProcInfo.hThread))
msg += ErrorCodeToMsg(GetLastError()) + " - ";
// msg could be printed or something at some point
}
bool POpen::ReadLine(string& result)
{
static int aa = 0;
if (m_BufSize == BUFSIZE+1)
return false;
DWORD dwRead;
for (;;)
{
DWORD i = 0;
for ( ; i < m_BufSize; ++i)
if (m_Buf[i] == '\n')
break;
//if (aa++ == 23)
// throw PluginException(string("got '") + string(m_Buf, m_BufSize) + "'" + ToString(m_BufSize));
// throw PluginException(ToString(m_BufSize) + " " + ToString(i) + ": " + string(m_Buf, dwRead));
if (i != m_BufSize)
{
// Found a newline
bool stripCR = i > 0 && m_Buf[i-1] == '\r';
result.assign(m_Buf, stripCR ? i-1 : i);
if (i+1 < m_BufSize)
{
// +1 to skip \n
m_BufSize -= i + 1;
memmove(m_Buf, m_Buf + i + 1, m_BufSize);
}
else
{
m_BufSize = 0;
}
return true;
}
// Need more data
DWORD bufferLeft = BUFSIZE - m_BufSize - 1;
Enforce<PluginException>(bufferLeft > 0, string("Buffer overflow in ReadLine for '") + m_Command + "'");
if (!ReadFile( m_ChildStd_OUT_Rd, m_Buf + m_BufSize, bufferLeft, &dwRead, NULL))
{
DWORD err = GetLastError();
if (err != ERROR_BROKEN_PIPE)
throw PluginException(string("Error during read from '") + m_Command + "': " + ErrorCodeToMsg(GetLastError()));
dwRead = 0;
}
if (dwRead == 0)
{
// End if pipe
if (m_BufSize != 0)
{
result.assign(m_Buf, m_BufSize);
m_BufSize = BUFSIZE+1; // signal no more data
return true;
}
return false;
}
// look for newline
m_BufSize += dwRead;
}
}
const size_t kDefaultPathBufferSize = 1024;
void POpen::ReadIntoFile(const std::string& path)
{
wchar_t widePath[kDefaultPathBufferSize];
ConvertUnityPathName(path.c_str(), widePath, kDefaultPathBufferSize);
HANDLE fh = CreateFileW(
widePath,
GENERIC_WRITE | GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
Enforce<PluginException>(fh != INVALID_HANDLE_VALUE, string("Invalid result file handle for ReadIntoFile during '") + m_Command + "' " + ErrorCodeToMsg(GetLastError()));
DWORD dwRead, dwWritten;
for (;;)
{
if (!ReadFile( m_ChildStd_OUT_Rd, m_Buf, BUFSIZE, &dwRead, NULL))
{
CloseHandle(fh);
throw PluginException(string("Error during read from '") + m_Command + "': " + ErrorCodeToMsg(GetLastError()));
}
if( dwRead == 0 ) break; // end of pipe
if (!WriteFile(fh, m_Buf, dwRead, &dwWritten, NULL))
{
CloseHandle(fh);
throw PluginException(string("Error during read from '") + m_Command + "': " + ErrorCodeToMsg(GetLastError()));
}
}
CloseHandle(fh);
}
#else // posix
POpen::POpen(const string& cmd) : m_Command(cmd)
{
m_Handle = popen(cmd.c_str(), "r");
Enforce<PluginException>(m_Handle, string("Error starting '") + cmd + "'");
}
POpen::~POpen()
{
if (m_Handle)
pclose(m_Handle);
}
bool POpen::ReadLine(string& result)
{
Enforce<PluginException>(m_Handle, string("Null handle when reading from command pipe: ") + m_Command);
const size_t BUFSIZE = 8192;
static char buf[BUFSIZE];
if (feof(m_Handle)) return false;
char* res = fgets(buf, BUFSIZE, m_Handle);
if (!res)
{
if (feof(m_Handle))
return false; // no more data
throw PluginException(string("Error reading command pipe :") +
strerror(ferror(m_Handle))
+ " - " + m_Command);
}
result = res;
string::reverse_iterator i = result.rbegin();
if (!result.empty() && *i == '\n')
result.resize(result.size()-1);
return true;
}
void POpen::ReadIntoFile(const std::string& path)
{
Enforce<PluginException>(m_Handle, string("Null handle when reading into filefrom command pipe: ") + m_Command);
const size_t BUFSIZE = 8192;
static char buf[BUFSIZE];
FILE* fh = fopen(path.c_str(), "w");
if (feof(m_Handle))
{
fclose(fh);
return;
}
size_t bytes = fread(buf, 1, BUFSIZE, m_Handle);
while (bytes == BUFSIZE)
{
if (fwrite(buf, 1, bytes, fh) != bytes)
{
// Error writing to disk
fclose(fh);
throw PluginException(string("Error writing process output into file: ") + path + " for command " + m_Command);
}
bytes = fread(buf, BUFSIZE, 1, m_Handle);
}
if (feof(m_Handle))
{
if (bytes && fwrite(buf, 1, bytes, fh) != bytes)
{
// Error writing to disk
fclose(fh);
throw PluginException(string("Error writing process end output into file: ") + path);
}
fclose(fh);
}
else
{
stringstream os;
os << "Error writing process output to file: ";
os << path << " code " << ferror(fh);
os << " for command " << m_Command << std::endl;
fclose(fh);
throw PluginException(os.str());
}
}
#endif // end defined(_WINDOWS) or posix

31
Common/POpen.h Normal file
Просмотреть файл

@ -0,0 +1,31 @@
#pragma once
#if defined(_WINDOWS)
#include <Windows.h>
#endif
// Run a command line read result.
// This is done blocking which is ok since Unity will timeout
class POpen
{
public:
POpen(const std::string& cmd);
~POpen();
bool ReadLine(std::string& result);
void ReadIntoFile(const std::string& path);
private:
std::string m_Command;
#if defined(_WINDOWS)
HANDLE m_ChildStd_IN_Rd;
HANDLE m_ChildStd_IN_Wr;
HANDLE m_ChildStd_OUT_Rd;
HANDLE m_ChildStd_OUT_Wr;
PROCESS_INFORMATION m_ProcInfo;
CHAR m_Buf[4096];
DWORD m_BufSize;
#else // posix
FILE* m_Handle;
#endif
};
typedef std::auto_ptr<POpen> APOpen;

Просмотреть файл

@ -61,7 +61,7 @@ unityplugin::LogStream& UnityConnection::Log()
bool UnityConnection::IsConnected() const
{
return m_UnityPipe;
return m_UnityPipe != NULL;
}
UnityPipe& UnityConnection::Pipe()

Просмотреть файл

@ -142,100 +142,6 @@ bool IsReadOnly(const std::string& path)
}
*/
POpen::POpen(const std::string& cmd) : m_Command(cmd)
{
#if defined(_WINDOWS)
m_Handle = _popen(cmd.c_str(), "r");
#else
m_Handle = popen(cmd.c_str(), "r");
#endif
Enforce<PluginException>(m_Handle, string("Error starting '") + cmd + "'");
}
POpen::~POpen()
{
if (m_Handle)
#if defined(_WINDOWS)
_pclose(m_Handle);
#else
pclose(m_Handle);
#endif
}
bool POpen::ReadLine(std::string& result)
{
Enforce<PluginException>(m_Handle, string("Null handle when reading from command pipe: ") + m_Command);
const size_t BUFSIZE = 8192;
static char buf[BUFSIZE];
if (feof(m_Handle)) return false;
char* res = fgets(buf, BUFSIZE, m_Handle);
if (!res)
{
if (feof(m_Handle))
return false; // no more data
throw PluginException(string("Error reading command pipe :") +
strerror(ferror(m_Handle))
+ " - " + m_Command);
}
result = res;
string::reverse_iterator i = result.rbegin();
if (!result.empty() && *i == '\n')
result.resize(result.size()-1);
return true;
}
void POpen::ReadIntoFile(const std::string& path)
{
Enforce<PluginException>(m_Handle, string("Null handle when reading into filefrom command pipe: ") + m_Command);
const size_t BUFSIZE = 8192;
static char buf[BUFSIZE];
FILE* fh = fopen(path.c_str(), "w");
if (feof(m_Handle))
{
fclose(fh);
return;
}
size_t bytes = fread(buf, 1, BUFSIZE, m_Handle);
while (bytes == BUFSIZE)
{
if (fwrite(buf, 1, bytes, fh) != bytes)
{
// Error writing to disk
fclose(fh);
throw PluginException(string("Error writing process output into file: ") + path + " for command " + m_Command);
}
bytes = fread(buf, BUFSIZE, 1, m_Handle);
}
if (feof(m_Handle))
{
if (bytes && fwrite(buf, 1, bytes, fh) != bytes)
{
// Error writing to disk
fclose(fh);
throw PluginException(string("Error writing process end output into file: ") + path);
}
fclose(fh);
}
else
{
stringstream os;
os << "Error writing process output to file: ";
os << path << " code " << ferror(fh);
os << " for command " << m_Command << std::endl;
fclose(fh);
throw PluginException(os.str());
}
}
#if defined(_WINDOWS)
#include <stdio.h>
string ErrorCodeToMsg( DWORD code )

Просмотреть файл

@ -4,6 +4,7 @@
#include <vector>
#include <memory>
#include <sstream>
#include "POpen.h"
std::string IntToString (int i);
size_t Tokenize(std::vector<std::string>& result, const std::string& str,
@ -50,24 +51,7 @@ std::string ToString(const T& v1, const T& v2, const T& v3)
return ss.str();
}
// Run a command line read result.
// This is done blocking which is ok since Unity will timeout
class POpen
{
public:
POpen(const std::string& cmd);
~POpen();
bool ReadLine(std::string& result);
void ReadIntoFile(const std::string& path);
private:
std::string m_Command;
FILE* m_Handle;
};
typedef std::auto_ptr<POpen> APOpen;
#if defined(_WINDOWS)
#include <Windows.h>
std::string ErrorCodeToMsg( DWORD code );
#endif

Просмотреть файл

@ -16,6 +16,7 @@
<ClCompile Include="..\Common\CommandLine.cpp" />
<ClCompile Include="..\Common\FileSystem.cpp" />
<ClCompile Include="..\Common\Log.cpp" />
<ClCompile Include="..\Common\POpen.cpp" />
<ClCompile Include="..\Common\Status.cpp" />
<ClCompile Include="..\Common\Task.cpp" />
<ClCompile Include="..\Common\UnityConnection.cpp" />
@ -72,6 +73,7 @@
<ClInclude Include="..\Common\Dispatch.h" />
<ClInclude Include="..\Common\FileSystem.h" />
<ClInclude Include="..\Common\Log.h" />
<ClInclude Include="..\Common\POpen.h" />
<ClInclude Include="..\Common\Status.h" />
<ClInclude Include="..\Common\Task.h" />
<ClInclude Include="..\Common\UnityConnection.h" />

Просмотреть файл

@ -140,6 +140,9 @@
<ClCompile Include="..\Common\UnityConnection.cpp">
<Filter>Common</Filter>
</ClCompile>
<ClCompile Include="..\Common\POpen.cpp">
<Filter>Common</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\Common\Changes.h">
@ -232,5 +235,8 @@
<ClInclude Include="..\Common\Commands\Submit.h">
<Filter>Common\Commands</Filter>
</ClInclude>
<ClInclude Include="..\Common\POpen.h">
<Filter>Common</Filter>
</ClInclude>
</ItemGroup>
</Project>

Просмотреть файл

@ -46,7 +46,7 @@ public:
Pipe().Log() << "Ensure editable source " << paths << unityplugin::Endl;
string err;
bool editable = src.GetState() & (kCheckedOutLocal | kAddedLocal | kLockedLocal);
bool editable = (src.GetState() & (kCheckedOutLocal | kAddedLocal | kLockedLocal)) != 0;
if (!editable)
{

Просмотреть файл

@ -16,6 +16,7 @@ public:
resp.addTrait("svnPassword", "Password", "Subversion password", "", ConfigResponse::TF_Required | ConfigResponse::TF_Password);
resp.addTrait("svnRepos", "Repository", "Subversion Repository", "", ConfigResponse::TF_Required);
resp.addTrait("svnOptions", "Options", "Subversion extra options", "", ConfigResponse::TF_None);
resp.addTrait("svnExecutable", "Executable", "Path to the svn.exe executable", task.GetSvnExecutable(), ConfigResponse::TF_None);
}
else if (req.key == "pluginVersions")
{
@ -46,6 +47,11 @@ public:
req.conn.Log() << "Set options to " << req.values[0] << unityplugin::Endl;
task.SetOptions(Join(req.values, " "));
}
else if (req.key == "svnExcutable")
{
req.conn.Log() << "Set executable path to " << req.values[0] << unityplugin::Endl;
task.SetSvnExecutable(Join(req.values, " "));
}
else
{
std::string msg = "Unknown config field set on subversion plugin: ";

Просмотреть файл

@ -10,6 +10,7 @@ using namespace std;
SvnTask::SvnTask() : m_Task(NULL)
{
SetSvnExecutable(""); // Set default svn executable
}
SvnTask::~SvnTask()
@ -57,6 +58,32 @@ const std::string& SvnTask::GetOptions() const
return m_OptionsConfig;
}
void SvnTask::SetSvnExecutable(const std::string& e)
{
if (!e.empty())
{
if (PathExists(e))
m_SvnPath = e;
else
m_SvnPath.clear();
}
if (e.empty())
{
#if defined(_WINDOWS)
m_SvnPath = PluginPath();
m_SvnPath = m_SvnPath.substr(0, m_SvnPath.rfind('\\', m_SvnPath.rfind('\\') - 1)) + "\\svn\\svn.exe";
#else // posix
m_SvnPath = "/usr/bin/svn";
#endif
}
}
const std::string& SvnTask::GetSvnExecutable() const
{
return m_SvnPath;
}
std::string SvnTask::GetCredentials() const
{
string c;
@ -91,7 +118,6 @@ int SvnTask::Run()
UnityCommand cmd;
CommandArgs args;
m_SvnPath = "/usr/bin/svn";
try
{
@ -311,7 +337,6 @@ void SvnTask::GetStatusWithChangelists(const VersionedAssetList& assets,
// status for different areas. Following at are space separated
// fields in order: working revision, last commited revision, last commited author
// The last field is the file path and can include spaces
if (StartsWith(line, "--- Changelist"))
{
const int prefixLen = 15; // "-- Changelist '" length
@ -398,14 +423,14 @@ void SvnTask::GetLog(SvnLogResult& result, const std::string& from, const std::s
while (ppipe->ReadLine(line))
{
Enforce<SvnException>(StartsWith(line, "--------------"), "Invalid log header top");
Enforce<SvnException>(StartsWith(line, "--------------"), string("Invalid log header top: ") + line);
// Skip first line of "------"
if (!ppipe->ReadLine(line))
break;
Enforce<SvnException>(line.length() >= MIN_HEADER_LINE_LENGTH && line[0] == 'r',
"Invalid log header");
string("Invalid log header: ") + line);
toks.clear();
size_t size = Tokenize(toks, line, "|");

Просмотреть файл

@ -20,6 +20,8 @@ public:
const std::string& GetPassword() const;
void SetOptions(const std::string& p);
const std::string& GetOptions() const;
void SetSvnExecutable(const std::string& e);
const std::string& GetSvnExecutable() const;
void SetAssetsPath(const std::string& p);
const std::string& GetAssetsPath() const;

Просмотреть файл

@ -92,6 +92,7 @@ _SCL_SECURE_NO_DEPRECATE
<ClCompile Include="..\Common\CommandLine.cpp" />
<ClCompile Include="..\Common\FileSystem.cpp" />
<ClCompile Include="..\Common\Log.cpp" />
<ClCompile Include="..\Common\POpen.cpp" />
<ClCompile Include="..\Common\Status.cpp" />
<ClCompile Include="..\Common\Task.cpp" />
<ClCompile Include="..\Common\UnityConnection.cpp" />
@ -120,6 +121,7 @@ _SCL_SECURE_NO_DEPRECATE
<ClInclude Include="..\Common\Dispatch.h" />
<ClInclude Include="..\Common\FileSystem.h" />
<ClInclude Include="..\Common\Log.h" />
<ClInclude Include="..\Common\POpen.h" />
<ClInclude Include="..\Common\Status.h" />
<ClInclude Include="..\Common\Task.h" />
<ClInclude Include="..\Common\UnityConnection.h" />

Просмотреть файл

@ -51,6 +51,9 @@
<ClCompile Include="Source\SvnTask.cpp">
<Filter>SvnPlugin</Filter>
</ClCompile>
<ClCompile Include="..\Common\POpen.cpp">
<Filter>Common</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\Common\Changes.h">
@ -188,5 +191,8 @@
<ClInclude Include="Source\SvnUnlockCommand.h">
<Filter>SvnPlugin</Filter>
</ClInclude>
<ClInclude Include="..\Common\POpen.h">
<Filter>Common</Filter>
</ClInclude>
</ItemGroup>
</Project>