X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Fsupport%2FSystemcall.cpp;h=741feb233843834c95a596e06e2f361edfea39f7;hb=c48091f33a773732fa6c789927e5833e44108d9d;hp=499994b34c57ef4f11c023b9c511b24699f1087e;hpb=a0bdf5e6667ca2998a20861d943916254ae8af0e;p=lyx.git diff --git a/src/support/Systemcall.cpp b/src/support/Systemcall.cpp index 499994b34c..741feb2338 100644 --- a/src/support/Systemcall.cpp +++ b/src/support/Systemcall.cpp @@ -14,12 +14,16 @@ #include #include "support/debug.h" +#include "support/environment.h" +#include "support/filetools.h" #include "support/lstrings.h" #include "support/qstring_helpers.h" #include "support/Systemcall.h" #include "support/SystemcallPrivate.h" #include "support/os.h" +#include "support/ProgressInterface.h" +#include "LyXRC.h" #include #include @@ -28,7 +32,7 @@ #include #include #include - +#include #define USE_QPROCESS @@ -43,29 +47,78 @@ struct Sleep : QThread + using namespace std; namespace lyx { namespace support { -static void killProcess(QProcess * p) + +class ProgressDummy : public ProgressInterface { - p->disconnect(); - p->closeReadChannel(QProcess::StandardOutput); - p->closeReadChannel(QProcess::StandardError); - p->close(); - delete p; +public: + ProgressDummy() {} + + void processStarted(QString const &) {} + void processFinished(QString const &) {} + void appendMessage(QString const &) {} + void appendError(QString const &) {} + void clearMessages() {} + void lyxerrFlush() {} + + void lyxerrConnect() {} + void lyxerrDisconnect() {} + + void warning(QString const &, QString const &) {} + void toggleWarning(QString const &, QString const &, QString const &) {} + void error(QString const &, QString const &) {} + void information(QString const &, QString const &) {} +}; + + +static ProgressInterface* progress_instance = 0; + +void ProgressInterface::setInstance(ProgressInterface* p) +{ + progress_instance = p; } +ProgressInterface* ProgressInterface::instance() +{ + if (!progress_instance) { + static ProgressDummy dummy; + return &dummy; + } + return progress_instance; +} + // Reuse of instance #ifndef USE_QPROCESS -int Systemcall::startscript(Starttype how, string const & what) +int Systemcall::startscript(Starttype how, string const & what, + std::string const & path, bool /*process_events*/) { - string command = what; + string command; + string const texinputs = os::latex_path_list( + replaceCurdirPath(path, lyxrc.texinputs_prefix)); + string const sep = string(1, os::path_separator(os::TEXENGINE)); + string const env = getEnv("TEXINPUTS"); + + switch (os::shell()) { + case os::UNIX: + command = path.empty() || lyxrc.texinputs_prefix.empty() ? what + : "env TEXINPUTS='." + sep + texinputs + + sep + env + "' " + what; + break; + case os::CMD_EXE: + command = path.empty() || lyxrc.texinputs_prefix.empty() ? what + : "set TEXINPUTS=." + sep + texinputs + + sep + env + " & " + what; + break; + } if (how == DontWait) { switch (os::shell()) { @@ -85,22 +138,83 @@ int Systemcall::startscript(Starttype how, string const & what) namespace { -string const parsecmd(string const & cmd, string & outfile) +/* + * This is a parser that (mostly) mimics the behavior of a posix shell but + * its output is tailored for being processed by QProcess. + * + * The escape character is the backslash. + * A backslash that is not quoted preserves the literal value of the following + * character, with the exception of a double-quote '"'. If a double-quote + * follows a backslash, it will be replaced by three consecutive double-quotes + * (this is how the QProcess parser recognizes a '"' as a simple character + * instead of a quoting character). Thus, for example: + * \\ -> \ + * \a -> a + * \" -> """ + * + * Single-quotes. + * Characters enclosed in single-quotes ('') have their literal value preserved. + * A single-quote cannot occur within single-quotes. Indeed, a backslash cannot + * be used to escape a single-quote in a single-quoted string. In other words, + * anything enclosed in single-quotes is passed as is, but the single-quotes + * themselves are eliminated. Thus, for example: + * '\' -> \ + * '\\' -> \\ + * '\a' -> \a + * 'a\"b' -> a\"b + * + * Double-quotes. + * Characters enclosed in double-quotes ("") have their literal value preserved, + * with the exception of the backslash. The backslash retains its special + * meaning as an escape character only when followed by a double-quote. + * Contrarily to the behavior of a posix shell, the double-quotes themselves + * are *not* eliminated. Thus, for example: + * "\\" -> "\\" + * "\a" -> "\a" + * "a\"b" -> "a"""b" + */ +string const parsecmd(string const & inputcmd, string & outfile) { - bool inquote = false; + bool in_single_quote = false; + bool in_double_quote = 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) + string cmd; + + for (size_t i = 0; i < inputcmd.length(); ++i) { + char c = inputcmd[i]; + if (c == '\'') { + if (in_double_quote || escaped) { + if (in_double_quote && escaped) + cmd += '\\'; + cmd += c; + } else + in_single_quote = !in_single_quote; + escaped = false; + continue; + } + if (in_single_quote) { + cmd += c; + continue; + } + if (c == '"') { + if (escaped) { + cmd += "\"\"\""; + escaped = false; + } else { + cmd += c; + in_double_quote = !in_double_quote; + } + } else if (c == '\\' && !escaped) { escaped = !escaped; - else if (c == '>' && !(inquote || escaped)) { - outfile = trim(cmd.substr(i + 1), " \""); - return trim(cmd.substr(0, i)); - } else + } else if (c == '>' && !(in_double_quote || escaped)) { + outfile = trim(inputcmd.substr(i + 1), " \""); + return trim(cmd); + } else { + if (escaped && in_double_quote) + cmd += '\\'; + cmd += c; escaped = false; + } } outfile.erase(); return cmd; @@ -110,99 +224,121 @@ string const parsecmd(string const & cmd, string & outfile) -int Systemcall::startscript(Starttype how, string const & what) +int Systemcall::startscript(Starttype how, string const & what, + string const & path, bool process_events) { string outfile; QString cmd = toqstr(parsecmd(what, outfile)); - QProcess * process = new QProcess; - SystemcallPrivate d(process); - if (!outfile.empty()) { - // Check whether we have to simply throw away the output. - if (outfile != os::nulldev()) - process->setStandardOutputFile(toqstr(outfile)); - } else if (os::is_terminal(os::STDOUT)) - d.showout(); - if (os::is_terminal(os::STDERR)) - d.showerr(); - - - bool processEvents = false; - d.startProcess(cmd); - if (!d.waitWhile(SystemcallPrivate::Starting, processEvents, 3000)) { - LYXERR0("QProcess " << cmd << " did not start!"); + + SystemcallPrivate d(outfile); + + + d.startProcess(cmd, path); + if (!d.waitWhile(SystemcallPrivate::Starting, process_events, -1)) { + LYXERR0("Systemcall: '" << cmd << "' did not start!"); LYXERR0("error " << d.errorMessage()); return 10; } if (how == DontWait) { - // TODO delete process later + QProcess* released = d.releaseProcess(); + (void) released; // TODO who deletes it? return 0; } - if (!d.waitWhile(SystemcallPrivate::Running, processEvents, 180000)) { - LYXERR0("QProcess " << cmd << " did not finished!"); + if (!d.waitWhile(SystemcallPrivate::Running, process_events, + os::timeout_min() * 60 * 1000)) { + LYXERR0("Systemcall: '" << cmd << "' did not finish!"); 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("error " << exit_code << ": " << d.errorMessage()); + LYXERR0("Systemcall: '" << cmd << "' finished with exit code " << exit_code); } - // 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( - process->readAllStandardOutput().data())); - if (!os::is_terminal(os::STDERR)) - cerr << fromqstr(QString::fromLocal8Bit( - process->readAllStandardError().data())); - - killProcess(process); - return exit_code; } -SystemcallPrivate::SystemcallPrivate(QProcess * proc) : proc_(proc), outindex_(0), - errindex_(0), showout_(false), showerr_(false) +SystemcallPrivate::SystemcallPrivate(const std::string& of) : + process_(new QProcess), + out_index_(0), + err_index_(0), + out_file_(of), + texinputs_(getEnv("TEXINPUTS")), + process_events_(false) { - 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))); + if (!out_file_.empty()) { + // Check whether we have to simply throw away the output. + if (out_file_ != os::nulldev()) + process_->setStandardOutputFile(toqstr(out_file_)); + } + + connect(process_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut())); + connect(process_, SIGNAL(readyReadStandardError()), SLOT(stdErr())); + connect(process_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError))); + connect(process_, SIGNAL(started()), this, SLOT(processStarted())); + connect(process_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus))); } -void SystemcallPrivate::startProcess(const QString& cmd) +void SystemcallPrivate::startProcess(QString const & cmd, string const & path) { - state = SystemcallPrivate::Starting; - proc_->start(cmd); + cmd_ = cmd; + if (process_) { + if (!path.empty() && !lyxrc.texinputs_prefix.empty()) { + string const texinputs = os::latex_path_list( + replaceCurdirPath(path, lyxrc.texinputs_prefix)); + string const sep = string(1, + os::path_separator(os::TEXENGINE)); + string const prefix = "." + sep + texinputs + sep; + if (prefixIs(texinputs_, prefix)) + texinputs_.clear(); + else + setEnv("TEXINPUTS", prefix + texinputs_); + } + state = SystemcallPrivate::Starting; + if (os::shell() == os::CMD_EXE) + process_->start(QLatin1String("cmd /d /c ") + cmd_); + else + process_->start(cmd_); + } +} + + +void SystemcallPrivate::processEvents() +{ + if(process_events_) { + QCoreApplication::processEvents(/*QEventLoop::ExcludeUserInputEvents*/); + } } void SystemcallPrivate::waitAndProcessEvents() { Sleep::millisec(100); - QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + processEvents(); } -bool SystemcallPrivate::waitWhile(State waitwhile, bool processEvents, int timeout) +bool SystemcallPrivate::waitWhile(State waitwhile, bool process_events, int timeout) { + if (!process_) + return false; + + process_events_ = process_events; + // Block GUI while waiting, // relay on QProcess' wait functions - if (!processEvents) { + if (!process_events_) { if (waitwhile == Starting) - return proc_->waitForStarted(timeout); + return process_->waitForStarted(timeout); if (waitwhile == Running) - return proc_->waitForFinished(timeout); + return process_->waitForFinished(timeout); return false; } @@ -226,32 +362,38 @@ bool SystemcallPrivate::waitWhile(State waitwhile, bool processEvents, int timeo SystemcallPrivate::~SystemcallPrivate() { - if (outindex_) { - outdata_[outindex_] = '\0'; - outindex_ = 0; - cout << outdata_; + if (!texinputs_.empty()) + setEnv("TEXINPUTS", texinputs_); + + if (out_index_) { + out_data_[out_index_] = '\0'; + out_index_ = 0; + cout << out_data_; } cout.flush(); - if (errindex_) { - errdata_[errindex_] = '\0'; - errindex_ = 0; - cerr << errdata_; + if (err_index_) { + err_data_[err_index_] = '\0'; + err_index_ = 0; + cerr << err_data_; } cerr.flush(); + + killProcess(); } void SystemcallPrivate::stdOut() { - if (showout_) { + if (process_) { 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_; + process_->setReadChannel(QProcess::StandardOutput); + while (process_->getChar(&c)) { + out_data_[out_index_++] = c; + if (c == '\n' || out_index_ + 1 == buffer_size_) { + out_data_[out_index_] = '\0'; + out_index_ = 0; + ProgressInterface::instance()->appendMessage(QString::fromLocal8Bit(out_data_)); + cout << out_data_; } } } @@ -260,31 +402,54 @@ void SystemcallPrivate::stdOut() void SystemcallPrivate::stdErr() { - if (showerr_) { + if (process_) { 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_; + process_->setReadChannel(QProcess::StandardError); + while (process_->getChar(&c)) { + err_data_[err_index_++] = c; + if (c == '\n' || err_index_ + 1 == buffer_size_) { + err_data_[err_index_] = '\0'; + err_index_ = 0; + ProgressInterface::instance()->appendError(QString::fromLocal8Bit(err_data_)); + cerr << err_data_; } } } } -void SystemcallPrivate::processError(QProcess::ProcessError err) +void SystemcallPrivate::processStarted() +{ + if (state != Running) { + state = Running; + ProgressInterface::instance()->processStarted(cmd_); + } +} + + +void SystemcallPrivate::processFinished(int, QProcess::ExitStatus) +{ + if (state != Finished) { + state = Finished; + ProgressInterface::instance()->processFinished(cmd_); + } +} + + +void SystemcallPrivate::processError(QProcess::ProcessError) { state = Error; + ProgressInterface::instance()->appendError(errorMessage()); } QString SystemcallPrivate::errorMessage() const { + if (!process_) + return "No QProcess available"; + QString message; - switch (proc_->error()) { + switch (process_->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."; @@ -312,24 +477,13 @@ QString SystemcallPrivate::errorMessage() const } -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 status) -{ - state = Finished; -} - - QString SystemcallPrivate::exitStatusMessage() const { + if (!process_) + return "No QProcess available"; + QString message; - switch (proc_->exitStatus()) { + switch (process_->exitStatus()) { case QProcess::NormalExit: message = "The process exited normally."; break; @@ -344,6 +498,42 @@ QString SystemcallPrivate::exitStatusMessage() const } +int SystemcallPrivate::exitCode() +{ + if (!process_) + return -1; + + return process_->exitCode(); +} + + +QProcess* SystemcallPrivate::releaseProcess() +{ + QProcess* released = process_; + process_ = 0; + return released; +} + + +void SystemcallPrivate::killProcess() +{ + killProcess(process_); +} + + +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