X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Fsupport%2FSystemcall.cpp;h=3cb420bb518ebd1ccd106206e81c2dae48ed815e;hb=68957712dd4228a76b14611108fb61d926071e1f;hp=ec3f5a2258b1d5db8224f26c7ce7f03d28e65826;hpb=cd9b169d7697338bb70e800f4186e6aec6c38006;p=lyx.git diff --git a/src/support/Systemcall.cpp b/src/support/Systemcall.cpp index ec3f5a2258..3cb420bb51 100644 --- a/src/support/Systemcall.cpp +++ b/src/support/Systemcall.cpp @@ -4,9 +4,9 @@ * Licence details can be found in the file COPYING. * * \author Asger Alstrup - * - * Interface cleaned up by * \author Angus Leeming + * \author Enrico Forestieri + * \author Peter Kuemmel * * Full author contact details are available in file CREDITS. */ @@ -14,35 +14,47 @@ #include #include "support/debug.h" +#include "support/lstrings.h" #include "support/qstring_helpers.h" #include "support/Systemcall.h" +#include "support/SystemcallPrivate.h" #include "support/os.h" + #include #include #include +#include +#include +#include +#include #define USE_QPROCESS + +struct Sleep : QThread +{ + static void millisec(unsigned long ms) + { + QThread::usleep(ms * 1000); + } +}; + + + using namespace std; namespace lyx { namespace support { -static void killProcess(QProcess * p) -{ - p->closeReadChannel(QProcess::StandardOutput); - p->closeReadChannel(QProcess::StandardError); - p->close(); - delete p; -} // Reuse of instance -int Systemcall::startscript(Starttype how, string const & what) -{ #ifndef USE_QPROCESS +int Systemcall::startscript(Starttype how, string const & what, + bool /*process_events*/) +{ string command = what; if (how == DontWait) { @@ -57,49 +69,341 @@ int Systemcall::startscript(Starttype how, string const & what) } return ::system(command.c_str()); +} + #else - QString cmd = QString::fromLocal8Bit(what.c_str()); - QProcess * process = new QProcess; -#ifndef WIN32 - if (isatty(1)) - process->setStandardOutputFile(toqstr("/dev/stdout")); - if (isatty(2)) - process->setStandardErrorFile(toqstr("/dev/stderr")); -#endif - process->start(cmd); - if (!process->waitForStarted(3000)) { - LYXERR0("Qprocess " << cmd << " did not start!"); - LYXERR0("error " << process->error()); - LYXERR0("state " << process->state()); - LYXERR0("status " << process->exitStatus()); + +namespace { + +string const parsecmd(string const & cmd, string & outfile) +{ + bool inquote = false; + bool escaped = false; + + for (size_t i = 0; i < cmd.length(); ++i) { + char c = cmd[i]; + if (c == '"' && !escaped) + inquote = !inquote; + else if (c == '\\' && !escaped) + escaped = !escaped; + else if (c == '>' && !(inquote || escaped)) { + outfile = trim(cmd.substr(i + 1), " \""); + return trim(cmd.substr(0, i)); + } else + escaped = false; + } + outfile.erase(); + return cmd; +} + +} // namespace anon + + + +int Systemcall::startscript(Starttype how, string const & what, bool process_events) +{ + string outfile; + QString cmd = toqstr(parsecmd(what, outfile)); + SystemcallPrivate d(outfile); + + + d.startProcess(cmd); + if (!d.waitWhile(SystemcallPrivate::Starting, process_events, -1)) { + LYXERR0("Systemcall: '" << cmd << " did not start!"); + LYXERR0("error " << d.errorMessage()); return 10; } - if (how == DontWait) + + if (how == DontWait) { + QProcess* released = d.releaseProcess(); + (void) released; // TODO who deletes it? return 0; + } - if (!process->waitForFinished(180000)) { - LYXERR0("Qprocess " << cmd << " did not finished!"); - LYXERR0("error " << process->error()); - LYXERR0("state " << process->state()); - LYXERR0("status " << process->exitStatus()); + if (!d.waitWhile(SystemcallPrivate::Running, process_events, 180000)) { + LYXERR0("Systemcall: '" << cmd << "' did not finished!"); + LYXERR0("error " << d.errorMessage()); + LYXERR0("status " << d.exitStatusMessage()); return 20; } - int const exit_code = process->exitCode(); + + int const exit_code = d.exitCode(); if (exit_code) { - LYXERR0("Qprocess " << cmd << " finished!"); - LYXERR0("exitCode " << process->exitCode()); - LYXERR0("error " << process->error()); - LYXERR0("state " << process->state()); - LYXERR0("status " << process->exitStatus()); - } -#ifdef WIN32 - cout << fromqstr(QString::fromLocal8Bit(process->readAllStandardOutput().data())) << endl; - cerr << fromqstr(QString::fromLocal8Bit(process->readAllStandardError().data())) << endl; -#endif - killProcess(process); + LYXERR0("Systemcall: '" << cmd << "' finished with exit code " << exit_code); + } + return exit_code; -#endif } + +SystemcallPrivate::SystemcallPrivate(const std::string& of) : + proc_(new QProcess), outindex_(0), errindex_(0), + outfile(of), showout_(false), showerr_(false), process_events(false) +{ + if (!outfile.empty()) { + // Check whether we have to simply throw away the output. + if (outfile != os::nulldev()) + proc_->setStandardOutputFile(toqstr(outfile)); + } else if (os::is_terminal(os::STDOUT)) + showout(); + if (os::is_terminal(os::STDERR)) + showerr(); + + connect(proc_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut())); + connect(proc_, SIGNAL(readyReadStandardError()), SLOT(stdErr())); + connect(proc_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError))); + connect(proc_, SIGNAL(started()), this, SLOT(processStarted())); + connect(proc_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus))); +} + + + +void SystemcallPrivate::startProcess(const QString& cmd) +{ + if (proc_) { + state = SystemcallPrivate::Starting; + proc_->start(cmd); + } +} + + +void SystemcallPrivate::processEvents() +{ + if(process_events) { + //static int count = 0; qDebug() << count++ << ": waitAndProcessEvents"; + QCoreApplication::processEvents(QEventLoop::AllEvents); + } +} + + +void SystemcallPrivate::waitAndProcessEvents() +{ + Sleep::millisec(100); + processEvents(); +} + + +bool SystemcallPrivate::waitWhile(State waitwhile, bool proc_events, int timeout) +{ + if (!proc_) + return false; + + process_events = proc_events; + + // Block GUI while waiting, + // relay on QProcess' wait functions + if (!process_events) { + if (waitwhile == Starting) + return proc_->waitForStarted(timeout); + if (waitwhile == Running) + return proc_->waitForFinished(timeout); + return false; + } + + // process events while waiting, no timeout + if (timeout == -1) { + while (state == waitwhile && state != Error) { + waitAndProcessEvents(); + } + return state != Error; + } + + // process events while waiting whith timeout + QTime timer; + timer.start(); + while (state == waitwhile && state != Error && timer.elapsed() < timeout) { + waitAndProcessEvents(); + } + return (state != Error) && (timer.elapsed() < timeout); +} + + +SystemcallPrivate::~SystemcallPrivate() +{ + flush(); + + if (outindex_) { + outdata_[outindex_] = '\0'; + outindex_ = 0; + cout << outdata_; + } + cout.flush(); + if (errindex_) { + errdata_[errindex_] = '\0'; + errindex_ = 0; + cerr << errdata_; + } + cerr.flush(); + + killProcess(); +} + + +void SystemcallPrivate::flush() +{ + if (proc_) { + // If the output has been redirected, we write it all at once. + // Even if we are not running in a terminal, the output could go + // to some log file, for example ~/.xsession-errors on *nix. + if (!os::is_terminal(os::STDOUT) && outfile.empty()) + cout << fromqstr(QString::fromLocal8Bit( + proc_->readAllStandardOutput().data())); + if (!os::is_terminal(os::STDERR)) + cerr << fromqstr(QString::fromLocal8Bit( + proc_->readAllStandardError().data())); + } +} + + +void SystemcallPrivate::stdOut() +{ + if (proc_ && showout_) { + char c; + proc_->setReadChannel(QProcess::StandardOutput); + while (proc_->getChar(&c)) { + outdata_[outindex_++] = c; + if (c == '\n' || outindex_ + 1 == bufsize_) { + outdata_[outindex_] = '\0'; + outindex_ = 0; + cout << outdata_; + } + } + } + processEvents(); +} + + +void SystemcallPrivate::stdErr() +{ + if (proc_ && showerr_) { + char c; + proc_->setReadChannel(QProcess::StandardError); + while (proc_->getChar(&c)) { + errdata_[errindex_++] = c; + if (c == '\n' || errindex_ + 1 == bufsize_) { + errdata_[errindex_] = '\0'; + errindex_ = 0; + cerr << errdata_; + } + } + } + processEvents(); +} + + +void SystemcallPrivate::processStarted() +{ + state = Running; + // why do we get two started signals? + //disconnect(proc_, SIGNAL(started()), this, SLOT(processStarted())); +} + + +void SystemcallPrivate::processFinished(int, QProcess::ExitStatus) +{ + state = Finished; +} + + +void SystemcallPrivate::processError(QProcess::ProcessError) +{ + state = Error; +} + + +QString SystemcallPrivate::errorMessage() const +{ + if (!proc_) + return "No QProcess available"; + + QString message; + switch (proc_->error()) { + case QProcess::FailedToStart: + message = "The process failed to start. Either the invoked program is missing, " + "or you may have insufficient permissions to invoke the program."; + break; + case QProcess::Crashed: + message = "The process crashed some time after starting successfully."; + break; + case QProcess::Timedout: + message = "The process timed out. It might be restarted automatically."; + break; + case QProcess::WriteError: + message = "An error occurred when attempting to write to the process-> For example, " + "the process may not be running, or it may have closed its input channel."; + break; + case QProcess::ReadError: + message = "An error occurred when attempting to read from the process-> For example, " + "the process may not be running."; + break; + case QProcess::UnknownError: + default: + message = "An unknown error occured."; + break; + } + return message; +} + + +QString SystemcallPrivate::exitStatusMessage() const +{ + if (!proc_) + return "No QProcess available"; + + QString message; + switch (proc_->exitStatus()) { + case QProcess::NormalExit: + message = "The process exited normally."; + break; + case QProcess::CrashExit: + message = "The process crashed."; + break; + default: + message = "Unknown exit state."; + break; + } + return message; +} + + +int SystemcallPrivate::exitCode() +{ + if (!proc_) + return -1; + + return proc_->exitCode(); +} + + +QProcess* SystemcallPrivate::releaseProcess() +{ + QProcess* released = proc_; + proc_ = 0; + return released; +} + + +void SystemcallPrivate::killProcess() +{ + killProcess(proc_); +} + + +void SystemcallPrivate::killProcess(QProcess * p) +{ + if (p) { + p->disconnect(); + p->closeReadChannel(QProcess::StandardOutput); + p->closeReadChannel(QProcess::StandardError); + p->close(); + delete p; + } +} + + + +#include "moc_SystemcallPrivate.cpp" +#endif + } // namespace support } // namespace lyx