opennx/AsyncProcess.cpp
2025-08-08 20:34:09 +02:00

332 lines
10 KiB
C++

// $Id: AsyncProcess.cpp 603 2011-02-21 19:18:11Z felfert $
//
// Copyright (C) 2006 The OpenNX Team
// Author: Fritz Elfert
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU Library General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(__GNUG__) && !defined(__APPLE__)
#pragma implementation "AsyncProcess.h"
#endif
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include <wx/filename.h>
#include <wx/txtstrm.h>
#include "AsyncProcess.h"
#include "trace.h"
ENABLE_TRACE;
#if 0
// trying to get rid of file descriptors which are left
// open by redirected wxExecute ...
#define private public
#define protected public
#include <wx/file.h>
#include <wx/wfstream.h>
#undef private
#undef protected
#endif
DEFINE_LOCAL_EVENT_TYPE(wxEVT_PROCESS_STDOUT);
DEFINE_LOCAL_EVENT_TYPE(wxEVT_PROCESS_STDERR);
DEFINE_LOCAL_EVENT_TYPE(wxEVT_PROCESS_EXIT);
IMPLEMENT_DYNAMIC_CLASS(AsyncProcess, wxProcess)
AsyncProcess::AsyncProcess()
: wxProcess(),
wxThreadHelper(),
m_pEvtHandler(NULL)
{
Redirect();
}
AsyncProcess::AsyncProcess(const wxString& cmd, const wxString &wdir, const wxString &special, wxEvtHandler *h /* = NULL */)
: wxProcess(),
wxThreadHelper(),
m_pEvtHandler(h),
m_sCmd(cmd),
m_sDir(wdir),
m_sSpecial(special)
{
Redirect();
}
AsyncProcess::AsyncProcess(const wxString& cmd, const wxString &wdir, wxEvtHandler *h /* = NULL */)
: wxProcess(),
wxThreadHelper(),
m_pEvtHandler(h),
m_sCmd(cmd),
m_sDir(wdir)
{
Redirect();
}
AsyncProcess::AsyncProcess(const wxString& cmd, wxEvtHandler *h /* = NULL */)
: wxProcess(),
wxThreadHelper(),
m_pEvtHandler(h),
m_sCmd(cmd),
m_sDir(::wxGetCwd())
{
Redirect();
}
AsyncProcess::~AsyncProcess()
{
myLogTrace(MYTRACETAG, wxT("~AsyncProcess"));
if (m_thread && m_thread->IsRunning()) {
m_thread->Delete();
while (m_thread->IsRunning())
wxThread::Sleep(100);
m_thread = NULL;
}
myLogTrace(MYTRACETAG, wxT("~AsyncProcess exit"));
m_pEvtHandler = NULL;
}
wxThread::ExitCode
AsyncProcess::Entry()
{
myLogTrace(MYTRACETAG, wxT("IoThread starting"));
while (!m_thread->TestDestroy()) {
while (IsInputAvailable()) {
char c = GetInputStream()->GetC();
if (m_pEvtHandler) {
m_cOutWatch.Start();
switch (c) {
case '\r':
break;
case '\n':
{
wxCommandEvent event(wxEVT_PROCESS_STDOUT, wxID_ANY);
event.SetString(m_sOutBuf);
event.SetClientData(this);
m_pEvtHandler->AddPendingEvent(event);
m_sOutBuf.Empty();
}
break;
default:
if (c >= ' ')
m_sOutBuf += c;
break;
}
}
}
if (m_pEvtHandler) {
// If no LF received within a second, send buffer anyway
//if ((m_sSpecial.Len() && m_sOutBuf.StartsWith(m_sSpecial)) || ((m_cOutWatch.Time() > 100) && (!m_sOutBuf.IsEmpty()))) {
//
// +Djelf: there is no need to wait, all data should already be in
// the buffer, with the exception of a long list of
// sessions, more than a TCP/IP packet
if (!m_sOutBuf.IsEmpty()) {
myLogTrace(MYTRACETAG, wxT("IoThread outwatch timed out"));
wxCommandEvent event(wxEVT_PROCESS_STDOUT, wxID_ANY);
event.SetString(m_sOutBuf);
event.SetClientData(this);
m_pEvtHandler->AddPendingEvent(event);
m_sOutBuf.Empty();
}
}
while (IsErrorAvailable()) {
char c = GetErrorStream()->GetC();
if (m_pEvtHandler) {
m_cErrWatch.Start();
switch (c) {
case '\r':
break;
case '\n':
{
wxCommandEvent event(wxEVT_PROCESS_STDERR, wxID_ANY);
event.SetString(m_sErrBuf);
event.SetClientData(this);
m_pEvtHandler->AddPendingEvent(event);
m_sErrBuf.Empty();
}
break;
default:
if (c >= ' ')
m_sErrBuf += c;
break;
}
}
}
if (m_pEvtHandler) {
// If no LF received within a second, send buffer anyway
//if ((m_sSpecial.Len() && m_sErrBuf.StartsWith(m_sSpecial)) ||
// ((m_cErrWatch.Time() > 100) && (!m_sErrBuf.IsEmpty()))) {
//
// +Djelf: there is no need to wait, all data should already be in
// the buffer, with the exception of a long list of
// sessions, more than a TCP/IP packet
if (!m_sErrBuf.IsEmpty()) {
myLogTrace(MYTRACETAG, wxT("IoThread errwatch timed out"));
wxCommandEvent event(wxEVT_PROCESS_STDERR, wxID_ANY);
event.SetString(m_sErrBuf);
event.SetClientData(this);
m_pEvtHandler->AddPendingEvent(event);
m_sErrBuf.Empty();
}
}
wxThread::Sleep(10);
}
myLogTrace(MYTRACETAG, wxT("IoThread exiting"));
return 0;
}
bool
AsyncProcess::IsRunning() {
bool ret = ((m_pid > 0) && Exists(m_pid));
return ret;
}
bool
AsyncProcess::Print(const wxString &s, bool doLog)
{
wxOutputStream *os = GetOutputStream();
if (os) {
if (doLog)
myLogTrace(MYTRACETAG, wxT("Sending: '%s'"), VMB(s));
else
myLogTrace(MYTRACETAG, wxT("Sending (hidden): '************'"));
wxString sbuf = s + (s.IsEmpty() ? wxT("NullCommand\n") : wxT("\n")) ;
const wxWX2MBbuf buf = wxConvCurrent->cWX2MB(sbuf);
os->Write(buf, strlen(buf));
return true;
}
return false;
}
void
AsyncProcess::OnTerminate(int pid, int status)
{
myLogTrace(MYTRACETAG, wxT("Process %u terminated with exit code %d."), pid, status);
wxLog::FlushActive();
m_iStatus = status;
if (m_thread && m_thread->IsRunning()) {
while ((m_cOutWatch.Time() < 2000) || (m_cErrWatch.Time() < 2000))
wxThread::Sleep(100);
m_thread->Delete();
while (m_thread->IsRunning())
wxThread::Sleep(100);
myLogTrace(MYTRACETAG, wxT("OnTerminate(): IoThread has ended."));
m_thread = NULL;
}
if (m_pEvtHandler) {
wxCommandEvent event(wxEVT_PROCESS_EXIT, wxID_ANY);
event.SetInt(m_iStatus);
event.SetClientData(this);
m_pEvtHandler->AddPendingEvent(event);
}
}
bool
AsyncProcess::Start()
{
bool ret = false;
if (!m_sCmd.IsEmpty()) {
wxString cwd = ::wxGetCwd();
myLogTrace(MYTRACETAG, wxT("Starting '%s'"), VMB(m_sCmd));
if (!m_sDir.IsEmpty())
wxFileName::SetCwd(m_sDir);
m_sOutBuf.Empty();
m_sErrBuf.Empty();
m_pid = ::wxExecute(m_sCmd, wxEXEC_ASYNC, this);
myLogTrace(MYTRACETAG, wxT("wxExecute returned %d"), (int)m_pid);
ret = (m_pid > 0);
if (!m_sDir.IsEmpty())
wxFileName::SetCwd(cwd);
if (ret) {
myLogTrace(MYTRACETAG, wxT(">Create()"));
if (IsRunning())
CreateThread();
myLogTrace(MYTRACETAG, wxT("<Create()"));
if (IsRunning())
m_thread->Run();
myLogTrace(MYTRACETAG, wxT("Run()"));
}
}
return ret;
}
void
AsyncProcess::Detach()
{
myLogTrace(MYTRACETAG, wxT("Detach() called"));
m_pEvtHandler = NULL;
#if 0
int out_fd = reinterpret_cast<wxFileOutputStream*>(m_outputStream)->m_file->m_fd;
int err_fd = reinterpret_cast<wxFileInputStream*>(m_errorStream)->m_file->m_fd;
int inp_fd = reinterpret_cast<wxFileInputStream*>(m_inputStream)->m_file->m_fd;
myLogTrace(MYTRACETAG, wxT("inp=%d out=%d err=%d"), inp_fd, out_fd, err_fd);
#endif
wxProcess::Detach();
if (m_thread && m_thread->IsRunning()) {
m_thread->Delete();
myLogTrace(MYTRACETAG, wxT("Detatch(): waiting for IoThread"));
while (m_thread->IsRunning())
wxThread::Sleep(100);
myLogTrace(MYTRACETAG, wxT("Detatch(): IoThread has ended"));
m_thread = NULL;
}
#if 0
close(inp_fd);
close(out_fd);
close(err_fd);
#endif
}
bool
AsyncProcess::Kill()
{
myLogTrace(MYTRACETAG, wxT("Kill() called"));
bool ret = false;
if (IsRunning()) {
myLogTrace(MYTRACETAG, wxT("Kill(): actually send sig"));
switch (wxProcess::Kill(GetPid(), wxSIGKILL)) {
case wxKILL_OK:
Detach();
case wxKILL_NO_PROCESS:
ret = true;
break;
case wxKILL_BAD_SIGNAL:
case wxKILL_ACCESS_DENIED:
case wxKILL_ERROR:
break;
}
}
return ret;
}