File indexing completed on 2024-04-14 15:17:36

0001 /****************************************************************************************
0002     Copyright (C) 2003 by Jeroen Wijnhout (Jeroen.Wijnhout@kdemail.net)
0003                   2008-2018 by Michel Ludwig (michel.ludwig@kdemail.net)
0004  ****************************************************************************************/
0005 
0006 /***************************************************************************
0007  *                                                                         *
0008  *   This program is free software; you can redistribute it and/or modify  *
0009  *   it under the terms of the GNU General Public License as published by  *
0010  *   the Free Software Foundation; either version 2 of the License, or     *
0011  *   (at your option) any later version.                                   *
0012  *                                                                         *
0013  ***************************************************************************/
0014 
0015 #include "kilelauncher.h"
0016 
0017 #include <config.h>
0018 
0019 #include "kileconfig.h"
0020 #include "kileinfo.h"
0021 #include "kiletool.h"
0022 #include "kiletoolmanager.h"
0023 #include "kiletool_enums.h"
0024 #include "kileviewmanager.h"
0025 #include "livepreview.h"
0026 
0027 #include <QStackedWidget>
0028 #include <QFileInfo>
0029 
0030 #include "kiledebug.h"
0031 #include <KIO/DesktopExecParser>
0032 #include <KProcess>
0033 #include <KLocalizedString>
0034 #include <KShell>
0035 
0036 #include <KParts/Part>
0037 #include <KParts/PartManager>
0038 
0039 namespace KileTool {
0040 
0041 Launcher::Launcher() :
0042     m_tool(Q_NULLPTR)
0043 {
0044 }
0045 
0046 Launcher::~ Launcher()
0047 {
0048     KILE_DEBUG_MAIN << "DELETING launcher";
0049 }
0050 
0051 ProcessLauncher::ProcessLauncher() :
0052     m_changeTo(true)
0053 {
0054     KILE_DEBUG_MAIN << "==KileTool::ProcessLauncher::ProcessLauncher()==============";
0055 
0056     m_proc = new KProcess(this);
0057 
0058     m_proc->setOutputChannelMode(KProcess::MergedChannels);
0059     m_proc->setReadChannel(QProcess::StandardOutput);
0060 
0061     connect(m_proc, SIGNAL(readyReadStandardOutput()), this, SLOT(slotProcessOutput()));
0062     connect(m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotProcessExited(int,QProcess::ExitStatus)));
0063     connect(m_proc, SIGNAL(error(QProcess::ProcessError)), this, SLOT(slotProcessError(QProcess::ProcessError)));
0064 }
0065 
0066 ProcessLauncher::~ProcessLauncher()
0067 {
0068     KILE_DEBUG_MAIN << "DELETING ProcessLauncher";
0069 
0070     if(m_proc) {
0071         // we don't want it to emit any signals as we are being deleted
0072         m_proc->disconnect();
0073         kill(false);
0074         delete m_proc;
0075     }
0076 }
0077 
0078 void ProcessLauncher::setWorkingDirectory(const QString &wd)
0079 {
0080     m_wd = wd;
0081 }
0082 
0083 void ProcessLauncher::changeToWorkingDirectory(bool change)
0084 {
0085     m_changeTo = change;
0086 }
0087 
0088 void ProcessLauncher::setCommand(const QString& cmd)
0089 {
0090     m_cmd = cmd;
0091 }
0092 
0093 void ProcessLauncher::setOptions(const QString& opt)
0094 {
0095     m_options = opt;
0096 }
0097 
0098 bool ProcessLauncher::launch()
0099 {
0100     if(tool() == Q_NULLPTR) {
0101         qWarning() << "tool() is Q_NULLPTR which is a BUG";
0102         return false;
0103     }
0104     if(m_proc == Q_NULLPTR) {
0105         qWarning() << "m_proc is Q_NULLPTR which is a BUG";
0106         return false;
0107     }
0108 
0109     QString msg;
0110     QString out = "*****\n*****     " + tool()->name() + i18n(" output: \n");
0111 
0112     if(m_cmd.isEmpty()) {
0113         m_cmd = tool()->readEntry("command");
0114         KILE_DEBUG_MAIN << "readEntry('command'): " << m_cmd;
0115     }
0116 
0117     if(m_options.isEmpty()) {
0118         m_options = tool()->readEntry("options");
0119         KILE_DEBUG_MAIN << "readEntry('option'):" << m_options;
0120     }
0121 
0122     if(m_changeTo && (!m_wd.isEmpty())) {
0123         m_proc->setWorkingDirectory(m_wd);
0124         KILE_DEBUG_MAIN << "changed to " << m_wd;
0125         out += QString("*****     cd \"") + m_wd + QString("\"\n");
0126     }
0127 
0128     tool()->translate(m_cmd);
0129     tool()->translate(m_options, true); // quote the substituted strings using 'KShell::quoteArg'
0130     // (see bug 314109)
0131     KILE_DEBUG_MAIN << "after translate: m_cmd=" << m_cmd << ", m_options=" << m_options;
0132 
0133     if(m_cmd.isEmpty()) {
0134         return false;
0135     }
0136 
0137     KShell::Errors err;
0138     QStringList arguments = KShell::splitArgs(m_options, KShell::AbortOnMeta | KShell::TildeExpand, &err);
0139     if(err == KShell::BadQuoting || err == KShell::FoundMeta) {
0140         return false;
0141     }
0142 
0143     // we cannot use 'KProcess::setShellCommand' here as that method uses 'KStandardDirs::findExe'
0144     // which doesn't respect the path preferences given by the user, i.e. 'KStandardDirs::findExe' is happy
0145     // to return the first executable it finds (for example, in '/usr/bin' although the user maybe didn't
0146     // want to use that directory)
0147     // BUG: 204397
0148     m_proc->setProgram(m_cmd, arguments);
0149 
0150     KILE_DEBUG_MAIN << "sent " << m_cmd << ' ' << arguments;
0151 
0152     out += QString("*****     ") + m_cmd + ' ' + arguments.join(" ") + '\n';
0153 
0154     QString src = tool()->source(false);
0155     QString trgt = tool()->target();
0156     if(src == trgt) {
0157         msg = src;
0158     }
0159     else {
0160         msg = src + " => " + trgt;
0161     }
0162 
0163     msg += " (" + m_cmd + ')';
0164 
0165     emit(message(Info, msg));
0166 
0167     QString teXInputPaths = tool()->teXInputPaths();
0168     QString bibInputPaths = tool()->bibInputPaths();
0169     QString bstInputPaths = tool()->bstInputPaths();
0170 
0171     // QuickView tools need a special TEXINPUTS environment variable
0172     if(tool()->isQuickie()) {
0173         teXInputPaths = KileConfig::previewTeXPaths();
0174         bibInputPaths = KileConfig::previewBibInputPaths();
0175     }
0176 
0177     KILE_DEBUG_MAIN << "$PATH=" << tool()->manager()->info()->expandEnvironmentVars("$PATH");
0178     KILE_DEBUG_MAIN << "$TEXINPUTS=" << tool()->manager()->info()->expandEnvironmentVars(teXInputPaths + LIST_SEPARATOR + "$TEXINPUTS");
0179     KILE_DEBUG_MAIN << "$BIBINPUTS=" << tool()->manager()->info()->expandEnvironmentVars(bibInputPaths + LIST_SEPARATOR + "$BIBINPUTS");
0180     KILE_DEBUG_MAIN << "$BSTINPUTS=" << tool()->manager()->info()->expandEnvironmentVars(bstInputPaths + LIST_SEPARATOR + "$BSTINPUTS");
0181     KILE_DEBUG_MAIN << "Tool name is "<< tool()->name();
0182 
0183     m_proc->setEnv("PATH", tool()->manager()->info()->expandEnvironmentVars("$PATH"));
0184 
0185     if(!teXInputPaths.isEmpty()) {
0186         m_proc->setEnv("TEXINPUTS", tool()->manager()->info()->expandEnvironmentVars(teXInputPaths + LIST_SEPARATOR + "$TEXINPUTS"));
0187     }
0188     if(!bibInputPaths.isEmpty()) {
0189         m_proc->setEnv("BIBINPUTS", tool()->manager()->info()->expandEnvironmentVars(bibInputPaths + LIST_SEPARATOR + "$BIBINPUTS"));
0190     }
0191     if(!bstInputPaths.isEmpty()) {
0192         m_proc->setEnv("BSTINPUTS", tool()->manager()->info()->expandEnvironmentVars(bstInputPaths + LIST_SEPARATOR + "$BSTINPUTS"));
0193     }
0194 
0195     out += "*****\n";
0196     emit(output(out));
0197 
0198     if(tool()->manager()->shouldBlock()) {
0199         KILE_DEBUG_MAIN << "About to execute: " << m_proc->program();
0200         m_proc->execute();
0201     }
0202     else {
0203         KILE_DEBUG_MAIN << "About to start: " << m_proc->program();
0204         m_proc->start();
0205     }
0206     return true;
0207 }
0208 
0209 void ProcessLauncher::kill(bool emitSignals)
0210 {
0211     KILE_DEBUG_MAIN << "==KileTool::ProcessLauncher::kill()==============";
0212     if(m_proc && m_proc->state() == QProcess::Running) {
0213         KILE_DEBUG_MAIN << "\tkilling";
0214         m_proc->kill();
0215         m_proc->waitForFinished(-1);
0216     }
0217     else {
0218         KILE_DEBUG_MAIN << "\tno process or process not running";
0219         if(emitSignals) {
0220             emit(message(Error, i18n("terminated")));
0221             emit(done(AbnormalExit));
0222         }
0223     }
0224 }
0225 
0226 // FIXME: this should be done in the 'launch()' method itself
0227 bool ProcessLauncher::selfCheck()
0228 {
0229     emit(message(Error, i18n("Launching failed, diagnostics:")));
0230 
0231     KShell::Errors err;
0232     QStringList arguments = KShell::splitArgs(m_options, KShell::AbortOnMeta | KShell::TildeExpand, &err);
0233     if(err == KShell::BadQuoting) {
0234         emit(message(Error, i18n("An error occurred while parsing the options given to the tool.")));
0235         return false;
0236     }
0237     else if(err == KShell::FoundMeta) {
0238         emit(message(Error, i18n("Shell meta characters that cannot be handled are present in the options given to the tool.")));
0239         return false;
0240     }
0241 
0242 
0243     QString exe = KIO::DesktopExecParser::executablePath(tool()->readEntry("command"));
0244     QString path = QStandardPaths::findExecutable(exe);
0245 
0246     if(path.isEmpty()) {
0247         emit(message(Error, i18n("There is no executable named \"%1\" in your path.", exe)));
0248         return false;
0249     }
0250     else {
0251         QFileInfo fi(path);
0252         if(!fi.isExecutable()) {
0253             emit(message(Error, i18n("You do not have permission to run %1.", path)));
0254             return false;
0255         }
0256     }
0257 
0258     emit(message(Info, i18n("Diagnostics could not find any obvious problems.")));
0259     return true;
0260 }
0261 
0262 void ProcessLauncher::slotProcessOutput()
0263 {
0264     QByteArray buf = m_proc->readAllStandardOutput();
0265     emit output(QString::fromLocal8Bit(buf, buf.size()));
0266 }
0267 
0268 void ProcessLauncher::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
0269 {
0270     KILE_DEBUG_MAIN << "==KileTool::ProcessLauncher::slotProcessExited=============";
0271     KILE_DEBUG_MAIN << "\t" << tool()->name();
0272 
0273     if(m_proc) {
0274         if(exitStatus == QProcess::NormalExit) {
0275             KILE_DEBUG_MAIN << "\tnormal exit";
0276             int type = Info;
0277             if(exitCode != 0) {
0278                 type = Error;
0279                 emit(message(type, i18n("finished with exit code %1", exitCode)));
0280             }
0281 
0282             if (type == Info) {
0283                 emit(done(Success));
0284             }
0285             else {
0286                 emit(done(Failed));
0287             }
0288         }
0289         else {
0290             KILE_DEBUG_MAIN << "\tabnormal exit";
0291             emit(message(Error, i18n("finished abruptly")));
0292             emit(done(AbnormalExit));
0293         }
0294     }
0295     else {
0296         qWarning() << "\tNO PROCESS, emitting done";
0297         emit(done(Success));
0298     }
0299 }
0300 
0301 void ProcessLauncher::slotProcessError(QProcess::ProcessError error)
0302 {
0303     KILE_DEBUG_MAIN << "error =" << error << "tool = " << tool()->name();
0304     QString errorString;
0305     switch(error) {
0306     case QProcess::FailedToStart:
0307         errorString = i18n("failed to start");
0308         break;
0309     case QProcess::Crashed:
0310         errorString = i18n("crashed");
0311         break;
0312     default:
0313         errorString = i18n("failed (error code %1)", error);
0314         break;
0315     }
0316     emit(message(Error, errorString));
0317     emit(done(AbnormalExit));
0318 }
0319 
0320 KonsoleLauncher::KonsoleLauncher() : ProcessLauncher()
0321 {
0322 }
0323 
0324 bool KonsoleLauncher::launch()
0325 {
0326     QString cmd = tool()->readEntry("command");
0327     QString noclose = (tool()->readEntry("close") == "no") ? "--noclose" : "";
0328     setCommand("konsole");
0329     setOptions(noclose + " -e " + cmd + ' ' + tool()->readEntry("options"));
0330     if(QStandardPaths::findExecutable(KIO::DesktopExecParser::executablePath(cmd)).isEmpty()) {
0331         return false;
0332     }
0333 
0334     return ProcessLauncher::launch();
0335 }
0336 
0337 DocumentViewerLauncher::DocumentViewerLauncher()
0338 {
0339 }
0340 
0341 DocumentViewerLauncher::~DocumentViewerLauncher()
0342 {
0343     KILE_DEBUG_MAIN << "DELETING DocumentViewerLauncher";
0344 }
0345 
0346 bool DocumentViewerLauncher::selfCheck()
0347 {
0348     return true;  //no additional self-checks, all of them are done in launch()
0349 }
0350 
0351 bool DocumentViewerLauncher::launch()
0352 {
0353     if(!tool()->manager()->viewManager()->viewerPart()) {
0354         emit(message(Error, i18n("The document viewer is not available")));
0355         return false;
0356     }
0357     if(tool()->manager()->livePreviewManager() && tool()->manager()->livePreviewManager()->isLivePreviewActive()) {
0358         emit(message(Error, i18n("Please disable the live preview before launching this tool")));
0359         return false;
0360     }
0361     const QString fileName = tool()->paramDict()["%dir_target"] + '/' + tool()->paramDict()["%target"];
0362     tool()->manager()->viewManager()->openInDocumentViewer(QUrl::fromLocalFile(fileName));
0363     if(tool()->paramDict().contains("%sourceFileName")
0364             && tool()->paramDict().contains("%sourceLine")) {
0365         const QString sourceFileName = tool()->paramDict()["%sourceFileName"];
0366         const QString lineString = tool()->paramDict()["%sourceLine"];
0367         tool()->manager()->viewManager()->showSourceLocationInDocumentViewer(sourceFileName, lineString.toInt(), 0);
0368     }
0369     emit(done(Success));
0370 
0371     return true;
0372 }
0373 
0374 void DocumentViewerLauncher::kill(bool emitSignals)
0375 {
0376     Q_UNUSED(emitSignals);
0377 }
0378 
0379 }
0380