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 "Metaphlan2Worker.h"
23 
24 #include <U2Core/AppContext.h>
25 #include <U2Core/AppSettings.h>
26 #include <U2Core/FailTask.h>
27 #include <U2Core/FileAndDirectoryUtils.h>
28 #include <U2Core/GUrlUtils.h>
29 #include <U2Core/TaskSignalMapper.h>
30 #include <U2Core/U2OpStatusUtils.h>
31 #include <U2Core/UserApplicationsSettings.h>
32 
33 #include <U2Lang/WorkflowContext.h>
34 #include <U2Lang/WorkflowMonitor.h>
35 #include <U2Lang/WorkflowUtils.h>
36 
37 #include "../ngs_reads_classification/src/NgsReadsClassificationUtils.h"
38 #include "Metaphlan2Support.h"
39 #include "Metaphlan2WorkerFactory.h"
40 
41 namespace U2 {
42 namespace LocalWorkflow {
43 
44 const QString Metaphlan2Worker::METAPHLAN2_ROOT_DIR = "MetaPhlAn2";
45 const QString Metaphlan2Worker::BOWTIE2OUT_DIR = "bowtie2out";
46 const QString Metaphlan2Worker::BOWTIE2OUT_SUFFIX = "bowtie2out";
47 const QString Metaphlan2Worker::PROFILE_DIR = "profiles";
48 const QString Metaphlan2Worker::PROFILE_SUFFIX = "profile";
49 
Metaphlan2Worker(Actor * actor)50 Metaphlan2Worker::Metaphlan2Worker(Actor *actor)
51     : BaseWorker(actor),
52       input(nullptr) {
53 }
54 
init()55 void Metaphlan2Worker::init() {
56     input = ports.value(Metaphlan2WorkerFactory::INPUT_PORT_ID);
57     SAFE_POINT(nullptr != input, QString("Port with id '%1' is nullptr").arg(Metaphlan2WorkerFactory::INPUT_PORT_ID), );
58 }
59 
tick()60 Task *Metaphlan2Worker::tick() {
61     if (isReadyToRun()) {
62         U2OpStatus2Log os;
63         Metaphlan2TaskSettings settings = getSettings(os);
64         CHECK(!os.hasError(), new FailTask(os.getError()));
65 
66         Metaphlan2ClassifyTask *task = new Metaphlan2ClassifyTask(settings);
67         task->addListeners(createLogListeners());
68         connect(new TaskSignalMapper(task), SIGNAL(si_taskFinished(Task *)), SLOT(sl_taskFinished(Task *)));
69         return task;
70     }
71 
72     if (dataFinished()) {
73         setDone();
74     }
75 
76     return nullptr;
77 }
78 
cleanup()79 void Metaphlan2Worker::cleanup() {
80 }
81 
sl_taskFinished(Task * task)82 void Metaphlan2Worker::sl_taskFinished(Task *task) {
83     Metaphlan2ClassifyTask *metaphlan2Task = qobject_cast<Metaphlan2ClassifyTask *>(task);
84     if (!metaphlan2Task->isFinished() || metaphlan2Task->hasError() || metaphlan2Task->isCanceled()) {
85         return;
86     }
87 
88     addOutputToDashboard(metaphlan2Task->getBowtie2OutputUrl(), "Bowtie2");
89     addOutputToDashboard(metaphlan2Task->getOutputUrl(), "MetaPhlAn2");
90 }
91 
isReadyToRun() const92 bool Metaphlan2Worker::isReadyToRun() const {
93     return input->hasMessage();
94 }
95 
dataFinished() const96 bool Metaphlan2Worker::dataFinished() const {
97     return input->isEnded();
98 }
99 
getSettings(U2OpStatus & os)100 Metaphlan2TaskSettings Metaphlan2Worker::getSettings(U2OpStatus &os) {
101     Metaphlan2TaskSettings settings;
102     settings.isPairedEnd = getValue<QString>(Metaphlan2WorkerFactory::SEQUENCING_READS) == Metaphlan2WorkerFactory::PAIRED_END;
103 
104     const Message message = getMessageAndSetupScriptValues(input);
105     settings.readsUrl = message.getData().toMap()[Metaphlan2WorkerFactory::INPUT_SLOT].toString();
106     if (settings.isPairedEnd) {
107         settings.pairedReadsUrl = message.getData().toMap()[Metaphlan2WorkerFactory::PAIRED_INPUT_SLOT].toString();
108     }
109 
110     settings.databaseUrl = getValue<QString>(Metaphlan2WorkerFactory::DB_URL);
111     settings.numberOfThreads = getValue<int>(Metaphlan2WorkerFactory::NUM_THREADS);
112     settings.analysisType = getValue<QString>(Metaphlan2WorkerFactory::ANALYSIS_TYPE);
113     settings.taxLevel = getValue<QString>(Metaphlan2WorkerFactory::TAX_LEVEL);
114     settings.normalizeByMetagenomeSize = getValue<QString>(Metaphlan2WorkerFactory::NORMALIZE) == Metaphlan2WorkerFactory::NOT_SKIP_NORMILIZE_BY_SIZE;
115     settings.presenceThreshold = getValue<int>(Metaphlan2WorkerFactory::PRESENCE_THRESHOLD);
116 
117     const QString defaultOutputDirectory = getDefaultOutputDir();
118 
119     settings.bowtie2OutputFile = getValue<QString>(Metaphlan2WorkerFactory::BOWTIE2_OUTPUT_URL);
120     if (settings.bowtie2OutputFile.isEmpty()) {
121         settings.bowtie2OutputFile = createOutputToolDirectory(defaultOutputDirectory, message, settings.isPairedEnd, Bowtie2);
122     } else {
123         const bool dirCreated = QDir().mkpath(QFileInfo(settings.bowtie2OutputFile).absoluteDir().path());
124         CHECK_EXT(dirCreated, os.setError(tr("Can't create a folder for the output file: %1").arg(settings.bowtie2OutputFile)), settings);
125     }
126     settings.bowtie2OutputFile = GUrlUtils::rollFileName(settings.bowtie2OutputFile, "_");
127 
128     settings.outputFile = getValue<QString>(Metaphlan2WorkerFactory::OUTPUT_URL);
129     if (settings.outputFile.isEmpty()) {
130         settings.outputFile = createOutputToolDirectory(defaultOutputDirectory, message, settings.isPairedEnd, MetaPhlAn2);
131     } else {
132         const bool dirCreated = QDir().mkpath(QFileInfo(settings.outputFile).absoluteDir().path());
133         CHECK_EXT(dirCreated, os.setError(tr("Can't create a folder for the output file: %1").arg(settings.outputFile)), settings);
134     }
135     settings.outputFile = GUrlUtils::rollFileName(settings.outputFile, "_");
136 
137     QString bowtie2AlignerPath = WorkflowUtils::getExternalToolPath(Metaphlan2Support::ET_BOWTIE_2_ALIGNER_ID);
138     CHECK_EXT(!bowtie2AlignerPath.isEmpty(), os.setError("Bowtie2 aligner isn't found"), settings);
139 
140     settings.bowtie2ExternalToolPath = QFileInfo(bowtie2AlignerPath).dir().path();
141     QString pythonPath = WorkflowUtils::getExternalToolPath(Metaphlan2Support::ET_PYTHON_BIO_ID);
142     CHECK_EXT(!pythonPath.isEmpty(), os.setError("Python isn't found"), settings);
143 
144     settings.pythonExternalToolPath = QFileInfo(pythonPath).dir().path();
145     settings.tmpDir = AppContext::getAppSettings()->getUserAppsSettings()->createCurrentProcessTemporarySubDir(os, METAPHLAN2_ROOT_DIR);
146     CHECK_OP(os, settings);
147 
148     return settings;
149 }
150 
getDefaultOutputDir() const151 QString Metaphlan2Worker::getDefaultOutputDir() const {
152     QString defaultOutputDirectory = FileAndDirectoryUtils::getWorkingDir(context->workingDir(), FileAndDirectoryUtils::WORKFLOW_INTERNAL, "", context->workingDir());
153     defaultOutputDirectory += METAPHLAN2_ROOT_DIR;
154     return GUrlUtils::rollFileName(defaultOutputDirectory, "_");
155 }
156 
createOutputToolDirectory(const QString & outputDirectory,const Message & message,const bool isPairedEnd,const Output out) const157 QString Metaphlan2Worker::createOutputToolDirectory(const QString &outputDirectory, const Message &message, const bool isPairedEnd, const Output out) const {
158     QStringList suffix;
159     QString folder;
160     switch (out) {
161     case Bowtie2:
162         suffix << BOWTIE2OUT_SUFFIX;
163         folder = BOWTIE2OUT_DIR;
164         break;
165     case MetaPhlAn2:
166         suffix << PROFILE_SUFFIX;
167         folder = PROFILE_DIR;
168         break;
169     }
170     QString outputToolDirectory = QString("%1/%2").arg(outputDirectory).arg(folder);
171     createDirectory(outputToolDirectory);
172 
173     const MessageMetadata metadata = context->getMetadataStorage().get(message.getMetadataId());
174     QString result = QString("%1/%2").arg(outputToolDirectory).arg(NgsReadsClassificationUtils::getBaseFileNameWithSuffixes(metadata.getFileUrl(), suffix, "txt", isPairedEnd));
175 
176     return result;
177 }
178 
createDirectory(QString & dir) const179 void Metaphlan2Worker::createDirectory(QString &dir) const {
180     dir = GUrlUtils::rollFileName(dir, "_");
181     QDir outDir(dir);
182     outDir.mkpath(dir);
183 }
184 
addOutputToDashboard(const QString & outputUrl,const QString & outputName) const185 void Metaphlan2Worker::addOutputToDashboard(const QString &outputUrl, const QString &outputName) const {
186     if (QFileInfo::exists(outputUrl)) {
187         context->getMonitor()->addOutputFile(outputUrl, getActor()->getId());
188     } else {
189         coreLog.error(tr("%1 output file doesn't exist").arg(outputName));
190     }
191 }
192 
193 }    // namespace LocalWorkflow
194 }    // namespace U2
195