1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include "ExternalToolRunTask.h"
23 
24 #include <QDir>
25 #include <QRegularExpression>
26 
27 #include <U2Core/AnnotationTableObject.h>
28 #include <U2Core/AppContext.h>
29 #include <U2Core/AppSettings.h>
30 #include <U2Core/CmdlineTaskRunner.h>
31 #include <U2Core/ExternalToolRegistry.h>
32 #include <U2Core/GUrlUtils.h>
33 #include <U2Core/Log.h>
34 #include <U2Core/ScriptingToolRegistry.h>
35 #include <U2Core/U2SafePoints.h>
36 #include <U2Core/UserApplicationsSettings.h>
37 
38 #ifdef Q_OS_WIN
39 #    include <windows.h>
40 #endif
41 
42 #ifdef Q_OS_UNIX
43 #    include <signal.h>
44 #    include <unistd.h>
45 #endif
46 
47 namespace U2 {
48 
49 #define WIN_LAUNCH_CMD_COMMAND "cmd /C "
50 #define START_WAIT_MSEC 3000
51 
ExternalToolRunTask(const QString & _toolId,const QStringList & _arguments,ExternalToolLogParser * _logParser,const QString & _workingDirectory,const QStringList & _additionalPaths,bool parseOutputFile)52 ExternalToolRunTask::ExternalToolRunTask(const QString &_toolId, const QStringList &_arguments, ExternalToolLogParser *_logParser, const QString &_workingDirectory, const QStringList &_additionalPaths, bool parseOutputFile)
53     : Task(AppContext::getExternalToolRegistry()->getToolNameById(_toolId) + tr(" tool"), TaskFlag_None),
54       arguments(_arguments),
55       logParser(_logParser),
56       toolId(_toolId),
57       workingDirectory(_workingDirectory),
58       additionalPaths(_additionalPaths),
59       externalToolProcess(nullptr),
60       helper(nullptr),
61       listener(nullptr),
62       parseOutputFile(parseOutputFile) {
63     CHECK_EXT(AppContext::getExternalToolRegistry()->getById(toolId) != nullptr, stateInfo.setError(tr("External tool is absent")), );
64 
65     toolName = AppContext::getExternalToolRegistry()->getToolNameById(toolId);
66     coreLog.trace("Creating run task for: " + toolName);
67     if (logParser != nullptr) {
68         logParser->setParent(this);
69     }
70 }
71 
~ExternalToolRunTask()72 ExternalToolRunTask::~ExternalToolRunTask() {
73     delete externalToolProcess;
74 }
75 
run()76 void ExternalToolRunTask::run() {
77     if (hasError() || isCanceled()) {
78         return;
79     }
80 
81     ProcessRun pRun = ExternalToolSupportUtils::prepareProcess(toolId, arguments, workingDirectory, additionalPaths, stateInfo, listener);
82     CHECK_OP(stateInfo, );
83     externalToolProcess = pRun.process;
84 
85     if (!inputFile.isEmpty()) {
86         externalToolProcess->setStandardInputFile(inputFile);
87     }
88     if (!outputFile.isEmpty()) {
89         externalToolProcess->setStandardOutputFile(outputFile);
90     }
91     if (!additionalEnvVariables.isEmpty()) {
92         QProcessEnvironment processEnvironment = externalToolProcess->processEnvironment();
93         foreach (const QString &envVarName, additionalEnvVariables.keys()) {
94             processEnvironment.insert(envVarName, additionalEnvVariables.value(envVarName));
95         }
96         externalToolProcess->setProcessEnvironment(processEnvironment);
97     }
98 
99     helper.reset(new ExternalToolRunTaskHelper(this));
100     if (listener != nullptr) {
101         helper->addOutputListener(listener);
102     }
103 
104     externalToolProcess->start(pRun.program, pRun.arguments);
105     bool started = externalToolProcess->waitForStarted(START_WAIT_MSEC);
106 
107     if (!started) {
108         ExternalTool *tool = AppContext::getExternalToolRegistry()->getById(toolId);
109         if (tool->isValid()) {
110             stateInfo.setError(tr("Can not run %1 tool.").arg(toolName));
111         } else {
112             stateInfo.setError(tr("Can not run %1 tool. May be tool path '%2' not valid?")
113                                    .arg(toolName)
114                                    .arg(AppContext::getExternalToolRegistry()->getById(toolId)->getPath()));
115         }
116         return;
117     }
118     while (!externalToolProcess->waitForFinished(1000)) {
119         if (isCanceled()) {
120             killProcess(externalToolProcess);
121             algoLog.details(tr("Tool %1 is cancelled").arg(toolName));
122             return;
123         }
124     }
125 
126     {
127         QProcess::ExitStatus status = externalToolProcess->exitStatus();
128         int exitCode = externalToolProcess->exitCode();
129         if (status == QProcess::CrashExit && !hasError()) {
130             QString error = parseStandartOutputFile();
131             if (error.isEmpty()) {
132                 QString intendedError = tr("%1 tool exited with the following error: %2 (Code: %3)")
133                                             .arg(toolName)
134                                             .arg(externalToolProcess->errorString())
135                                             .arg(externalToolProcess->exitCode());
136                 parseError(intendedError);
137                 error = logParser->getLastError();
138             }
139 
140             setError(error);
141         } else if (status == QProcess::NormalExit && exitCode != EXIT_SUCCESS && !hasError()) {
142             QString error = parseStandartOutputFile();
143             setError(error.isEmpty() ? tr("%1 tool exited with code %2").arg(toolName).arg(exitCode) : error);
144         } else if (status == QProcess::NormalExit && exitCode == EXIT_SUCCESS && !hasError()) {
145             algoLog.details(tr("Tool %1 finished successfully").arg(toolName));
146         }
147     }
148 }
149 
killProcess(QProcess * process)150 void ExternalToolRunTask::killProcess(QProcess *process) {
151     CmdlineTaskRunner::killProcessTree(process);
152 }
153 
getChildPidsRecursive(long parentPid)154 QList<long> ExternalToolRunTask::getChildPidsRecursive(long parentPid) {
155     return CmdlineTaskRunner::getChildrenProcesses(parentPid);
156 }
157 
addOutputListener(ExternalToolListener * outputListener)158 void ExternalToolRunTask::addOutputListener(ExternalToolListener *outputListener) {
159     if (helper) {
160         helper->addOutputListener(outputListener);
161     }
162     listener = outputListener;
163 }
164 
parseStandartOutputFile() const165 QString ExternalToolRunTask::parseStandartOutputFile() const {
166     CHECK(parseOutputFile, QString());
167 
168     QFile f(outputFile);
169     CHECK(f.open(QIODevice::ReadOnly), QString());
170 
171     QString output;
172     for (QByteArray line = f.readLine(); line.length() > 0; line = f.readLine()) {
173         output += line;
174     }
175     f.close();
176     logParser->parseOutput(output);
177 
178     return logParser->getLastError();
179 }
180 
parseError(const QString & error) const181 void ExternalToolRunTask::parseError(const QString &error) const {
182     logParser->parseErrOutput(error);
183 }
184 
185 ////////////////////////////////////////
186 // ExternalToolSupportTask
setListenerForTask(ExternalToolRunTask * runTask,int listenerNumber)187 void ExternalToolSupportTask::setListenerForTask(ExternalToolRunTask *runTask, int listenerNumber) {
188     CHECK(listeners.size() > listenerNumber, );
189     runTask->addOutputListener(listeners.at(listenerNumber));
190 }
191 
setListenerForHelper(ExternalToolRunTaskHelper * helper,int listenerNumber)192 void ExternalToolSupportTask::setListenerForHelper(ExternalToolRunTaskHelper *helper, int listenerNumber) {
193     CHECK(listeners.size() > listenerNumber, );
194     helper->addOutputListener(listeners.at(listenerNumber));
195 }
196 
getListener(int listenerNumber)197 ExternalToolListener *ExternalToolSupportTask::getListener(int listenerNumber) {
198     CHECK(listeners.size() > listenerNumber, nullptr);
199     return listeners.at(listenerNumber);
200 }
201 
202 ////////////////////////////////////////
203 // ExternalToolRunTaskHelper
ExternalToolRunTaskHelper(ExternalToolRunTask * t)204 ExternalToolRunTaskHelper::ExternalToolRunTaskHelper(ExternalToolRunTask *t)
205     : os(t->stateInfo), logParser(t->logParser), process(t->externalToolProcess), listener(nullptr) {
206     logData.resize(1000);
207     connect(process, SIGNAL(readyReadStandardOutput()), SLOT(sl_onReadyToReadLog()));
208     connect(process, SIGNAL(readyReadStandardError()), SLOT(sl_onReadyToReadErrLog()));
209 }
210 
ExternalToolRunTaskHelper(QProcess * _process,ExternalToolLogParser * _logParser,U2OpStatus & _os)211 ExternalToolRunTaskHelper::ExternalToolRunTaskHelper(QProcess *_process, ExternalToolLogParser *_logParser, U2OpStatus &_os)
212     : os(_os), logParser(_logParser), process(_process), listener(nullptr) {
213     logData.resize(1000);
214     connect(process, SIGNAL(readyReadStandardOutput()), SLOT(sl_onReadyToReadLog()));
215     connect(process, SIGNAL(readyReadStandardError()), SLOT(sl_onReadyToReadErrLog()));
216 }
217 
sl_onReadyToReadLog()218 void ExternalToolRunTaskHelper::sl_onReadyToReadLog() {
219     QMutexLocker locker(&logMutex);
220 
221     CHECK(nullptr != process, );
222     if (process->readChannel() == QProcess::StandardError) {
223         process->setReadChannel(QProcess::StandardOutput);
224     }
225     int numberReadChars = static_cast<int>(process->read(logData.data(), logData.size()));
226     while (numberReadChars > 0) {
227         // call log parser
228         QString line = QString::fromLocal8Bit(logData.constData(), numberReadChars);
229         logParser->parseOutput(line);
230         if (nullptr != listener) {
231             listener->addNewLogMessage(line, ExternalToolListener::OUTPUT_LOG);
232         }
233         numberReadChars = static_cast<int>(process->read(logData.data(), logData.size()));
234     }
235     os.setProgress(logParser->getProgress());
236 }
237 
sl_onReadyToReadErrLog()238 void ExternalToolRunTaskHelper::sl_onReadyToReadErrLog() {
239     QMutexLocker locker(&logMutex);
240 
241     CHECK(nullptr != process, );
242     if (process->readChannel() == QProcess::StandardOutput) {
243         process->setReadChannel(QProcess::StandardError);
244     }
245     int numberReadChars = static_cast<int>(process->read(logData.data(), logData.size()));
246     while (numberReadChars > 0) {
247         // call log parser
248         QString line = QString::fromLocal8Bit(logData.constData(), numberReadChars);
249         logParser->parseErrOutput(line);
250         if (nullptr != listener) {
251             listener->addNewLogMessage(line, ExternalToolListener::ERROR_LOG);
252         }
253         numberReadChars = static_cast<int>(process->read(logData.data(), logData.size()));
254     }
255     processErrorToLog();
256     os.setProgress(logParser->getProgress());
257 }
258 
addOutputListener(ExternalToolListener * _listener)259 void ExternalToolRunTaskHelper::addOutputListener(ExternalToolListener *_listener) {
260     listener = _listener;
261 }
262 
processErrorToLog()263 void ExternalToolRunTaskHelper::processErrorToLog() {
264     QString lastErr = logParser->getLastError();
265     if (!lastErr.isEmpty()) {
266         os.setError(lastErr);
267     }
268 }
269 
270 ////////////////////////////////////////
271 // ExternalToolLogParser
ExternalToolLogParser(bool _writeErrorsToLog)272 ExternalToolLogParser::ExternalToolLogParser(bool _writeErrorsToLog) {
273     progress = -1;
274     lastLine = "";
275     lastErrLine = "";
276     lastError = "";
277     writeErrorsToLog = _writeErrorsToLog;
278 }
279 
parseOutput(const QString & partOfLog)280 void ExternalToolLogParser::parseOutput(const QString &partOfLog) {
281     lastPartOfLog = partOfLog.split(QRegularExpression("\\r?\\n"));
282     lastPartOfLog.first() = lastLine + lastPartOfLog.first();
283     // It's a possible situation, that one message will be processed twice
284     lastLine = lastPartOfLog.last();
285     foreach (const QString &buf, lastPartOfLog) {
286         processLine(buf);
287     }
288 }
289 
parseErrOutput(const QString & partOfLog)290 void ExternalToolLogParser::parseErrOutput(const QString &partOfLog) {
291     lastPartOfLog = partOfLog.split(QRegularExpression("\\r?\\n"));
292     lastPartOfLog.first() = lastErrLine + lastPartOfLog.first();
293     // It's a possible situation, that one message will be processed twice
294     lastErrLine = lastPartOfLog.last();
295     foreach (const QString &buf, lastPartOfLog) {
296         processErrLine(buf);
297     }
298 }
299 
processLine(const QString & line)300 void ExternalToolLogParser::processLine(const QString &line) {
301     if (isError(line)) {
302         setLastError(line);
303     } else {
304         ioLog.trace(line);
305     }
306 }
307 
processErrLine(const QString & line)308 void ExternalToolLogParser::processErrLine(const QString &line) {
309     if (isError(line)) {
310         setLastError(line);
311     } else {
312         ioLog.trace(line);
313     }
314 }
315 
isError(const QString & line) const316 bool ExternalToolLogParser::isError(const QString &line) const {
317     return line.contains("error", Qt::CaseInsensitive);
318 }
319 
setLastError(const QString & value)320 void ExternalToolLogParser::setLastError(const QString &value) {
321     if (!value.isEmpty() && writeErrorsToLog) {
322         ioLog.error(value);
323     }
324     lastError = value;
325 }
326 
327 ////////////////////////////////////////
328 // ExternalToolSupportUtils
removeTmpDir(const QString & tmpDirUrl,U2OpStatus & os)329 void ExternalToolSupportUtils::removeTmpDir(const QString &tmpDirUrl, U2OpStatus &os) {
330     if (tmpDirUrl.isEmpty()) {
331         os.setError(tr("Can not remove temporary folder: path is empty."));
332         return;
333     }
334     QDir tmpDir(tmpDirUrl);
335     foreach (const QString &file, tmpDir.entryList(QDir::NoDotAndDotDot | QDir::AllEntries)) {
336         if (!tmpDir.remove(file)) {
337             os.setError(tr("Can not remove files from temporary folder."));
338             return;
339         }
340     }
341     if (!tmpDir.rmdir(tmpDir.absolutePath())) {
342         os.setError(tr("Can not remove folder for temporary files."));
343     }
344 }
345 
createTmpDir(const QString & prePath,const QString & domain,U2OpStatus & os)346 QString ExternalToolSupportUtils::createTmpDir(const QString &prePath, const QString &domain, U2OpStatus &os) {
347     int i = 0;
348     while (true) {
349         QString tmpDirName = QString("d_%1").arg(i);
350         QString tmpDirPath = prePath + "/" + domain + "/" + tmpDirName;
351         QDir tmpDir(tmpDirPath);
352 
353         if (!tmpDir.exists()) {
354             if (!QDir().mkpath(tmpDirPath)) {
355                 os.setError(tr("Can not create folder for temporary files: %1").arg(tmpDirPath));
356             }
357             return tmpDir.absolutePath();
358         }
359         i++;
360     }
361 }
362 
createTmpDir(const QString & domain,U2OpStatus & os)363 QString ExternalToolSupportUtils::createTmpDir(const QString &domain, U2OpStatus &os) {
364     QString tmpDirPath = AppContext::getAppSettings()->getUserAppsSettings()->getCurrentProcessTemporaryDirPath();
365     return createTmpDir(tmpDirPath, domain, os);
366 }
367 
appendExistingFile(const QString & path,QStringList & files)368 void ExternalToolSupportUtils::appendExistingFile(const QString &path, QStringList &files) {
369     GUrl url(path);
370     if (QFile::exists(url.getURLString())) {
371         files << url.getURLString();
372     }
373 }
374 
startExternalProcess(QProcess * process,const QString & program,const QStringList & arguments)375 bool ExternalToolSupportUtils::startExternalProcess(QProcess *process, const QString &program, const QStringList &arguments) {
376     process->start(program, arguments);
377     bool started = process->waitForStarted(START_WAIT_MSEC);
378 
379 #ifdef Q_OS_WIN32
380     if (!started) {
381         QString execStr = WIN_LAUNCH_CMD_COMMAND + program;
382         foreach (const QString arg, arguments) {
383             execStr += " " + arg;
384         }
385         process->start(execStr);
386         coreLog.trace(tr("Can't run an executable file \"%1\" as it is. Try to run it as a cmd line command: \"%2\"")
387                           .arg(program)
388                           .arg(execStr));
389         started = process->waitForStarted(START_WAIT_MSEC);
390     }
391 #endif
392 
393     return started;
394 }
395 
prepareProcess(const QString & toolId,const QStringList & arguments,const QString & workingDirectory,const QStringList & additionalPaths,U2OpStatus & os,ExternalToolListener * listener)396 ProcessRun ExternalToolSupportUtils::prepareProcess(const QString &toolId, const QStringList &arguments, const QString &workingDirectory, const QStringList &additionalPaths, U2OpStatus &os, ExternalToolListener *listener) {
397     ProcessRun result;
398     result.process = nullptr;
399     result.arguments = arguments;
400 
401     ExternalTool *tool = AppContext::getExternalToolRegistry()->getById(toolId);
402     CHECK_EXT(nullptr != tool, os.setError(tr("A tool with the ID %1 is absent").arg(toolId)), result);
403 
404     const QString toolName = tool->getName();
405     if (tool->getPath().isEmpty()) {
406         os.setError(tr("Path for '%1' tool not set").arg(toolName));
407         return result;
408     }
409     result.program = tool->getPath();
410     QString toolRunnerProgram = tool->getToolRunnerProgramId();
411 
412     if (!toolRunnerProgram.isEmpty()) {
413         ScriptingToolRegistry *stregister = AppContext::getScriptingToolRegistry();
414         SAFE_POINT_EXT(nullptr != stregister, os.setError("No scripting tool registry"), result);
415         ScriptingTool *stool = stregister->getById(toolRunnerProgram);
416         if (nullptr == stool || stool->getPath().isEmpty()) {
417             os.setError(QString("The tool %1 that runs %2 is not installed. Please set the path of the tool in the External Tools settings").arg(toolRunnerProgram).arg(toolName));
418             return result;
419         }
420         result.arguments.prepend(result.program);
421 
422         for (int i = stool->getRunParameters().size() - 1; i >= 0; i--) {
423             result.arguments.prepend(stool->getRunParameters().at(i));
424         }
425         foreach (const QString &param, tool->getToolRunnerAdditionalOptions()) {
426             result.arguments.prepend(param);
427         }
428         result.program = stool->getPath();
429     }
430 
431 #ifdef Q_OS_WIN
432     const QString pathVariableSeparator = ";";
433 #else
434     const QString pathVariableSeparator = ":";
435 #endif
436 
437     QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment();
438     QString path = additionalPaths.join(pathVariableSeparator) + pathVariableSeparator +
439                    tool->getAdditionalPaths().join(pathVariableSeparator) + pathVariableSeparator +
440                    processEnvironment.value("PATH");
441     if (!additionalPaths.isEmpty()) {
442         algoLog.trace(QString("PATH environment variable: '%1'").arg(path));
443     }
444     processEnvironment.insert("PATH", path);
445 
446     result.process = new QProcess();
447     result.process->setProcessEnvironment(processEnvironment);
448     if (!workingDirectory.isEmpty()) {
449         result.process->setWorkingDirectory(workingDirectory);
450         algoLog.details(tr("Working folder is \"%1\"").arg(result.process->workingDirectory()));
451     }
452 
453     // QProcess wraps arguments that contain spaces in quotes automatically.
454     // But launched line should look correctly in the log.
455     const QString commandWithArguments = GUrlUtils::getQuotedString(result.program) + ExternalToolSupportUtils::prepareArgumentsForCmdLine(result.arguments);
456     algoLog.details(tr("Launching %1 tool: %2").arg(toolName).arg(commandWithArguments));
457 
458     if (nullptr != listener) {
459         listener->setToolName(toolName);
460         listener->addNewLogMessage(commandWithArguments, ExternalToolListener::PROGRAM_WITH_ARGUMENTS);
461     }
462     return result;
463 }
464 
prepareArgumentsForCmdLine(const QStringList & arguments)465 QString ExternalToolSupportUtils::prepareArgumentsForCmdLine(const QStringList &arguments) {
466     QString argumentsLine;
467     foreach (QString argumentStr, arguments) {
468         // Find start of the parameter value
469         int startIndex = argumentStr.indexOf('=') + 1;
470         // Add quotes if parameter contains whitespace characters
471         QString valueStr = argumentStr.mid(startIndex);
472         if (valueStr.contains(' ') || valueStr.contains('\t')) {
473             argumentStr.append('"');
474             argumentStr.insert(startIndex, '"');
475         }
476         argumentsLine += ' ' + argumentStr;
477     }
478     return argumentsLine;
479 }
480 
splitCmdLineArguments(const QString & program)481 QStringList ExternalToolSupportUtils::splitCmdLineArguments(const QString &program) {
482     // a function body from "qprocess.cpp"
483 
484     QStringList args;
485     QString tmp;
486     int quoteCount = 0;
487     bool inQuote = false;
488 
489     // handle quoting. tokens can be surrounded by double quotes
490     // "hello world". three consecutive double quotes represent
491     // the quote character itself.
492     for (int i = 0; i < program.size(); ++i) {
493         if (program.at(i) == QLatin1Char('"') || program.at(i) == QLatin1Char('\'')) {
494             ++quoteCount;
495             if (quoteCount == 3) {
496                 // third consecutive quote
497                 quoteCount = 0;
498                 tmp += program.at(i);
499             }
500             continue;
501         }
502         if (quoteCount) {
503             if (quoteCount == 1)
504                 inQuote = !inQuote;
505             quoteCount = 0;
506         }
507         if (!inQuote && program.at(i).isSpace()) {
508             if (!tmp.isEmpty()) {
509                 args += tmp;
510                 tmp.clear();
511             }
512         } else {
513             tmp += program.at(i);
514         }
515     }
516     if (!tmp.isEmpty())
517         args += tmp;
518 
519     return args;
520 }
521 
getScoresGapDependencyMap()522 QVariantMap ExternalToolSupportUtils::getScoresGapDependencyMap() {
523     QVariantMap map;
524     QVariantMap gaps;
525     gaps["2 2"] = "2 2";
526     gaps["1 2"] = "1 2";
527     gaps["0 2"] = "0 2";
528     gaps["2 1"] = "2 1";
529     gaps["1 1"] = "1 1";
530     map.insert("1 -4", gaps);
531     map.insert("1 -3", gaps);
532 
533     gaps.clear();
534     gaps["2 2"] = "2 2";
535     gaps["1 2"] = "1 2";
536     gaps["0 2"] = "0 2";
537     gaps["3 1"] = "3 1";
538     gaps["2 1"] = "2 1";
539     gaps["1 1"] = "1 1";
540     map.insert("1 -2", gaps);
541 
542     gaps.clear();
543     gaps["4 2"] = "4 2";
544     gaps["3 2"] = "3 2";
545     gaps["2 2"] = "2 2";
546     gaps["1 2"] = "1 2";
547     gaps["0 2"] = "0 2";
548     gaps["4 1"] = "4 1";
549     gaps["3 1"] = "3 1";
550     gaps["2 1"] = "2 1";
551     map.insert("1 -1", gaps);
552 
553     gaps.clear();
554     gaps["4 4"] = "4 4";
555     gaps["2 4"] = "2 4";
556     gaps["0 4"] = "0 4";
557     gaps["4 2"] = "4 2";
558     gaps["2 2"] = "2 2";
559     map.insert("2 -7", gaps);
560     map.insert("2 -5", gaps);
561 
562     gaps.clear();
563     gaps["6 4"] = "6 4";
564     gaps["4 4"] = "4 4";
565     gaps["2 4"] = "2 4";
566     gaps["0 4"] = "0 4";
567     gaps["3 3"] = "3 3";
568     gaps["6 2"] = "6 2";
569     gaps["5 2"] = "5 2";
570     gaps["4 2"] = "4 2";
571     gaps["2 2"] = "2 2";
572     map.insert("2 -3", gaps);
573 
574     gaps.clear();
575     gaps["12 8"] = "12 8";
576     gaps["6 5"] = "6 5";
577     gaps["5 5"] = "5 5";
578     gaps["4 5"] = "4 5";
579     gaps["3 5"] = "3 5";
580     map.insert("4 -5", gaps);
581     map.insert("5 -4", gaps);
582 
583     return map;
584 }
585 
~ExternalToolLogProcessor()586 ExternalToolLogProcessor::~ExternalToolLogProcessor() {
587 }
588 
ExternalToolListener(ExternalToolLogProcessor * logProcessor)589 ExternalToolListener::ExternalToolListener(ExternalToolLogProcessor *logProcessor)
590     : logProcessor(logProcessor) {
591 }
592 
~ExternalToolListener()593 ExternalToolListener::~ExternalToolListener() {
594     delete logProcessor;
595 }
596 
setToolName(const QString & _toolName)597 void ExternalToolListener::setToolName(const QString &_toolName) {
598     toolName = _toolName;
599 }
600 
setLogProcessor(ExternalToolLogProcessor * newLogProcessor)601 void ExternalToolListener::setLogProcessor(ExternalToolLogProcessor *newLogProcessor) {
602     delete logProcessor;
603     logProcessor = newLogProcessor;
604 }
605 
getToolName() const606 const QString &ExternalToolListener::getToolName() const {
607     return toolName;
608 }
609 
610 }  // namespace U2
611