X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Fsupport%2FSystemcall.cpp;h=ca804096603eb81b7f35732fe6bac5e6ac8820ab;hb=bd2e7480b17a8fa8bc8ed5ec60eb4f02d75a0f1e;hp=dda23d66bce1d83369ca2d168a30bfbfda04fbf4;hpb=952a31a43ef55e86f11dfe58842ccc588a5a47c5;p=lyx.git diff --git a/src/support/Systemcall.cpp b/src/support/Systemcall.cpp index dda23d66bc..ca80409660 100644 --- a/src/support/Systemcall.cpp +++ b/src/support/Systemcall.cpp @@ -14,6 +14,8 @@ #include #include "support/debug.h" +#include "support/filetools.h" +#include "support/gettext.h" #include "support/lstrings.h" #include "support/qstring_helpers.h" #include "support/Systemcall.h" @@ -21,6 +23,7 @@ #include "support/os.h" #include "support/ProgressInterface.h" +#include "LyXRC.h" #include #include @@ -70,6 +73,8 @@ public: void toggleWarning(QString const &, QString const &, QString const &) {} void error(QString const &, QString const &) {} void information(QString const &, QString const &) {} + int prompt(docstring const &, docstring const &, int default_but, int, + docstring const &, docstring const &) { return default_but; } }; @@ -96,9 +101,15 @@ ProgressInterface* ProgressInterface::instance() // Reuse of instance #ifndef USE_QPROCESS int Systemcall::startscript(Starttype how, string const & what, - bool /*process_events*/) + std::string const & path, bool /*process_events*/) { - string command = what; + string const python_call = "python -tt"; + string command = to_filesystem8bit(from_utf8(latexEnvCmdPrefix(path))); + + if (prefixIs(what, python_call)) + command += os::python() + what.substr(python_call.length()); + else + command += what; if (how == DontWait) { switch (os::shell()) { @@ -109,7 +120,8 @@ int Systemcall::startscript(Starttype how, string const & what, command = "start /min " + command; break; } - } + } else if (os::shell() == os::CMD_EXE) + command = subst(command, "cmd /d /c ", ""); return ::system(command.c_str()); } @@ -119,8 +131,9 @@ int Systemcall::startscript(Starttype how, string const & what, namespace { /* - * This is a parser that (mostly) mimics the behavior of a posix shell but - * its output is tailored for being processed by QProcess. + * This is a parser that (mostly) mimics the behavior of a posix shell as + * regards quoting, but its output is tailored for being processed by QProcess. + * Note that shell metacharacters are not parsed. * * The escape character is the backslash. * A backslash that is not quoted preserves the literal value of the following @@ -153,66 +166,92 @@ namespace { * "\a" -> "\a" * "a\"b" -> "a"""b" */ -string const parsecmd(string const & inputcmd, string & outfile) +string const parsecmd(string const & incmd, string & infile, string & outfile, + string & errfile) { bool in_single_quote = false; bool in_double_quote = false; bool escaped = false; - string cmd; + string const python_call = "python -tt"; + vector outcmd(4); + size_t start = 0; + + if (prefixIs(incmd, python_call)) { + outcmd[0] = os::python(); + start = python_call.length(); + } - for (size_t i = 0; i < inputcmd.length(); ++i) { - char c = inputcmd[i]; + for (size_t i = start, o = 0; i < incmd.length(); ++i) { + char c = incmd[i]; if (c == '\'') { if (in_double_quote || escaped) { if (in_double_quote && escaped) - cmd += '\\'; - cmd += c; + outcmd[o] += '\\'; + outcmd[o] += c; } else in_single_quote = !in_single_quote; escaped = false; continue; } if (in_single_quote) { - cmd += c; + outcmd[o] += c; continue; } if (c == '"') { if (escaped) { - cmd += "\"\"\""; + // Don't triple double-quotes for redirection + // files as these won't be parsed by QProcess + outcmd[o] += string(o ? "\"" : "\"\"\""); escaped = false; } else { - cmd += c; + outcmd[o] += c; in_double_quote = !in_double_quote; } } else if (c == '\\' && !escaped) { escaped = !escaped; } else if (c == '>' && !(in_double_quote || escaped)) { - outfile = trim(inputcmd.substr(i + 1), " \""); - return trim(cmd); + if (suffixIs(outcmd[o], " 2")) { + outcmd[o] = rtrim(outcmd[o], "2"); + o = 2; + } else { + if (suffixIs(outcmd[o], " 1")) + outcmd[o] = rtrim(outcmd[o], "1"); + o = 1; + } + } else if (c == '<' && !(in_double_quote || escaped)) { + o = 3; } else { if (escaped && in_double_quote) - cmd += '\\'; - cmd += c; + outcmd[o] += '\\'; + outcmd[o] += c; escaped = false; } } - outfile.erase(); - return cmd; + infile = trim(outcmd[3], " \""); + outfile = trim(outcmd[1], " \""); + errfile = trim(outcmd[2], " \""); + return trim(outcmd[0]); } } // namespace anon -int Systemcall::startscript(Starttype how, string const & what, bool process_events) +int Systemcall::startscript(Starttype how, string const & what, + string const & path, bool process_events) { + LYXERR(Debug::INFO,"Running: " << what); + + string infile; string outfile; - QString cmd = toqstr(parsecmd(what, outfile)); + string errfile; + QString const cmd = QString::fromLocal8Bit( + parsecmd(what, infile, outfile, errfile).c_str()); - SystemcallPrivate d(outfile); + SystemcallPrivate d(infile, outfile, errfile); - d.startProcess(cmd); + d.startProcess(cmd, path); if (!d.waitWhile(SystemcallPrivate::Starting, process_events, -1)) { LYXERR0("Systemcall: '" << cmd << "' did not start!"); LYXERR0("error " << d.errorMessage()); @@ -225,8 +264,9 @@ int Systemcall::startscript(Starttype how, string const & what, bool process_eve return 0; } - if (!d.waitWhile(SystemcallPrivate::Running, process_events, 180000)) { - LYXERR0("Systemcall: '" << 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; @@ -241,17 +281,64 @@ int Systemcall::startscript(Starttype how, string const & what, bool process_eve } -SystemcallPrivate::SystemcallPrivate(const std::string& of) : - process_(new QProcess), - out_index_(0), - err_index_(0), - out_file_(of), - process_events_(false) +SystemcallPrivate::SystemcallPrivate(std::string const & sf, + std::string const & of, + std::string const & ef) : + process_(new QProcess), + out_index_(0), + err_index_(0), + in_file_(sf), + out_file_(of), + err_file_(ef), + process_events_(false) { + if (!in_file_.empty()) + process_->setStandardInputFile(QString::fromLocal8Bit(in_file_.c_str())); if (!out_file_.empty()) { - // Check whether we have to simply throw away the output. - if (out_file_ != os::nulldev()) - process_->setStandardOutputFile(toqstr(out_file_)); + if (out_file_[0] == '&') { + if (subst(out_file_, " ", "") == "&2" + && err_file_[0] != '&') { + out_file_ = err_file_; + process_->setProcessChannelMode( + QProcess::MergedChannels); + } else { + if (err_file_[0] == '&') { + // Leave alone things such as + // "1>&2 2>&1". Should not be harmful, + // but let's give anyway a warning. + LYXERR0("Unsupported stdout/stderr redirect."); + err_file_.erase(); + } else { + LYXERR0("Ambiguous stdout redirect: " + << out_file_); + } + out_file_ = os::nulldev(); + } + } + // Check whether we have to set the output file. + if (out_file_ != os::nulldev()) { + process_->setStandardOutputFile(QString::fromLocal8Bit( + out_file_.c_str())); + } + } + if (!err_file_.empty()) { + if (err_file_[0] == '&') { + if (subst(err_file_, " ", "") == "&1" + && out_file_[0] != '&') { + process_->setProcessChannelMode( + QProcess::MergedChannels); + } else { + LYXERR0("Ambiguous stderr redirect: " + << err_file_); + } + // In MergedChannels mode stderr goes to stdout. + err_file_ = os::nulldev(); + } + // Check whether we have to set the error file. + if (err_file_ != os::nulldev()) { + process_->setStandardErrorFile(QString::fromLocal8Bit( + err_file_.c_str())); + } } connect(process_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut())); @@ -262,20 +349,19 @@ SystemcallPrivate::SystemcallPrivate(const std::string& of) : } - -void SystemcallPrivate::startProcess(const QString& cmd) +void SystemcallPrivate::startProcess(QString const & cmd, string const & path) { cmd_ = cmd; if (process_) { state = SystemcallPrivate::Starting; - process_->start(cmd_); + process_->start(toqstr(latexEnvCmdPrefix(path)) + cmd_); } } void SystemcallPrivate::processEvents() { - if(process_events_) { + if (process_events_) { QCoreApplication::processEvents(/*QEventLoop::ExcludeUserInputEvents*/); } } @@ -288,11 +374,26 @@ void SystemcallPrivate::waitAndProcessEvents() } +namespace { + +bool queryStopCommand(QString const & cmd) +{ + docstring text = bformat(_( + "The command\n%1$s\nhas not yet completed.\n\n" + "Do you want to stop it?"), qstring_to_ucs4(cmd)); + return ProgressInterface::instance()->prompt(_("Stop command?"), text, + 1, 1, _("&Stop it"), _("Let it &run")) == 0; +} + +} + + bool SystemcallPrivate::waitWhile(State waitwhile, bool process_events, int timeout) { if (!process_) return false; + bool timedout = false; process_events_ = process_events; // Block GUI while waiting, @@ -300,8 +401,24 @@ bool SystemcallPrivate::waitWhile(State waitwhile, bool process_events, int time if (!process_events_) { if (waitwhile == Starting) return process_->waitForStarted(timeout); - if (waitwhile == Running) - return process_->waitForFinished(timeout); + if (waitwhile == Running) { + int bump = 2; + while (!timedout) { + if (process_->waitForFinished(timeout)) + return true; + bool stop = queryStopCommand(cmd_); + // The command may have finished in the meantime + if (process_->state() == QProcess::NotRunning) + return true; + if (stop) { + timedout = true; + process_->kill(); + } else { + timeout *= bump; + bump = 3; + } + } + } return false; } @@ -313,13 +430,24 @@ bool SystemcallPrivate::waitWhile(State waitwhile, bool process_events, int time return state != Error; } - // process events while waiting whith timeout + // process events while waiting with timeout QTime timer; timer.start(); - while (state == waitwhile && state != Error && timer.elapsed() < timeout) { + while (state == waitwhile && state != Error && !timedout) { waitAndProcessEvents(); + if (timer.elapsed() > timeout) { + bool stop = queryStopCommand(cmd_); + // The command may have finished in the meantime + if (process_->state() == QProcess::NotRunning) + break; + if (stop) { + timedout = true; + process_->kill(); + } else + timeout *= 3; + } } - return (state != Error) && (timer.elapsed() < timeout); + return (state != Error) && !timedout; }