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 "WorkflowUtils.h"
23
24 #include <QListWidgetItem>
25
26 #include <U2Core/AnnotationTableObject.h>
27 #include <U2Core/AppContext.h>
28 #include <U2Core/BaseDocumentFormats.h>
29 #include <U2Core/CredentialsAsker.h>
30 #include <U2Core/DocumentUtils.h>
31 #include <U2Core/ExternalToolRegistry.h>
32 #include <U2Core/ExternalToolRunTask.h>
33 #include <U2Core/FileAndDirectoryUtils.h>
34 #include <U2Core/Folder.h>
35 #include <U2Core/GObject.h>
36 #include <U2Core/L10n.h>
37 #include <U2Core/MultipleSequenceAlignmentObject.h>
38 #include <U2Core/PasswordStorage.h>
39 #include <U2Core/Settings.h>
40 #include <U2Core/StringAdapter.h>
41 #include <U2Core/U2OpStatusUtils.h>
42 #include <U2Core/U2SafePoints.h>
43
44 #include <U2Lang/BaseSlots.h>
45 #include <U2Lang/BaseTypes.h>
46 #include <U2Lang/CoreLibConstants.h>
47 #include <U2Lang/HRSchemaSerializer.h>
48 #include <U2Lang/IntegralBus.h>
49 #include <U2Lang/IntegralBusModel.h>
50 #include <U2Lang/IntegralBusType.h>
51 #include <U2Lang/SharedDbUrlUtils.h>
52 #include <U2Lang/URLAttribute.h>
53 #include <U2Lang/WorkflowIOTasks.h>
54 #include <U2Lang/WorkflowSettings.h>
55
56 namespace U2 {
57 /*****************************
58 * WorkflowUtils
59 *****************************/
60 const QStringList WorkflowUtils::WD_FILE_EXTENSIONS = initExtensions();
61 const QString WorkflowUtils::WD_XML_FORMAT_EXTENSION("uws");
62 const QString WorkflowUtils::HREF_PARAM_ID("param");
63
initExtensions()64 QStringList WorkflowUtils::initExtensions() {
65 QStringList exts;
66 exts << "uwl";
67 return exts;
68 }
69
getRichDoc(const Descriptor & d)70 QString WorkflowUtils::getRichDoc(const Descriptor &d) {
71 QString result = QString();
72 if (d.getDisplayName().isEmpty()) {
73 if (!d.getDocumentation().isEmpty()) {
74 result = QString("%1").arg(d.getDocumentation());
75 }
76 } else {
77 if (d.getDocumentation().isEmpty()) {
78 result = QString("<b>%1</b>").arg(d.getDisplayName());
79 } else {
80 result = QString("<b>%1</b>: %2").arg(d.getDisplayName()).arg(d.getDocumentation());
81 }
82 }
83 result.replace("\n", "<br>");
84 return result;
85 }
86
getDropUrl(QList<DocumentFormat * > & fs,const QMimeData * md)87 QString WorkflowUtils::getDropUrl(QList<DocumentFormat *> &fs, const QMimeData *md) {
88 QString url;
89 const GObjectMimeData *gomd = qobject_cast<const GObjectMimeData *>(md);
90 const DocumentMimeData *domd = qobject_cast<const DocumentMimeData *>(md);
91 if (gomd) {
92 GObject *obj = gomd->objPtr.data();
93 if (obj) {
94 fs << obj->getDocument()->getDocumentFormat();
95 url = obj->getDocument()->getURLString();
96 }
97 } else if (domd) {
98 Document *doc = domd->objPtr.data();
99 if (doc) {
100 fs << doc->getDocumentFormat();
101 url = doc->getURLString();
102 }
103 } else if (md->hasUrls()) {
104 QList<QUrl> urls = md->urls();
105 if (urls.size() == 1) {
106 url = urls.first().toLocalFile();
107 QList<FormatDetectionResult> formats = DocumentUtils::detectFormat(url);
108 foreach (const FormatDetectionResult &di, formats) {
109 fs << di.format;
110 }
111 }
112 }
113 return url;
114 }
115
setQObjectProperties(QObject & o,const QVariantMap & params)116 void WorkflowUtils::setQObjectProperties(QObject &o, const QVariantMap ¶ms) {
117 QMapIterator<QString, QVariant> i(params);
118 while (i.hasNext()) {
119 i.next();
120 // log.debug("set param " + i.key() + "="+i.value().toString());
121 o.setProperty(i.key().toLatin1(), i.value());
122 }
123 }
124
expandToUrls(const QString & s)125 QStringList WorkflowUtils::expandToUrls(const QString &s) {
126 QStringList urls = s.split(";");
127 QStringList result;
128 QRegExp wcard("[*?\\[\\]]");
129 foreach (QString url, urls) {
130 int idx = url.indexOf(wcard);
131 if (idx >= 0) {
132 int dirIdx = url.lastIndexOf('/', idx);
133 QDir dir;
134 if (dirIdx >= 0) {
135 dir = QDir(url.left(dirIdx));
136 url = url.right(url.length() - dirIdx - 1);
137 }
138
139 foreach (QFileInfo fi, dir.entryInfoList((QStringList() << url), QDir::Files | QDir::NoSymLinks)) {
140 result << fi.absoluteFilePath();
141 }
142 } else {
143 // if (QFile::exists(url))
144 {
145 result << url;
146 }
147 }
148 }
149 return result;
150 }
151
152 namespace {
153
validateParameters(const Schema & schema,NotificationsList & infoList)154 bool validateParameters(const Schema &schema, NotificationsList &infoList) {
155 bool good = true;
156 foreach (Actor *a, schema.getProcesses()) {
157 const int notificationCountBefore = infoList.size();
158 good = a->validate(infoList) && good;
159 for (int i = notificationCountBefore; i < infoList.size(); ++i) {
160 infoList[i].actorId = a->getId();
161 }
162 }
163 return good;
164 }
165
validateExternalTools(Actor * actor,NotificationsList & infoList)166 bool validateExternalTools(Actor *actor, NotificationsList &infoList) {
167 bool isValid = true;
168 StrStrMap tools = actor->getProto()->getExternalTools();
169 foreach (const QString &toolId, tools.keys()) {
170 Attribute *attr = actor->getParameter(tools[toolId]);
171 ExternalTool *tool = AppContext::getExternalToolRegistry()->getById(toolId);
172 if (tool == nullptr) {
173 isValid = false;
174 infoList << WorkflowNotification(WorkflowUtils::externalToolIsAbsentError(toolId),
175 actor->getId(),
176 WorkflowNotification::U2_ERROR);
177 continue;
178 }
179
180 bool isToolFromAttribute = attr != nullptr && !attr->isDefaultValue();
181 isValid = isToolFromAttribute ? !attr->isEmpty() : !tool->getPath().isEmpty();
182 if (!isValid) {
183 infoList << WorkflowNotification(WorkflowUtils::externalToolError(tool->getName()),
184 actor->getId(),
185 WorkflowNotification::U2_ERROR);
186 } else if (!isToolFromAttribute && !tool->isValid()) {
187 if (tool->isCustom()) {
188 infoList << WorkflowNotification(WorkflowUtils::customExternalToolInvalidError(tool->getName(), actor->getLabel()),
189 actor->getProto()->getId(),
190 WorkflowNotification::U2_ERROR);
191 isValid = false;
192 } else {
193 infoList << WorkflowNotification(WorkflowUtils::externalToolInvalidError(tool->getName()),
194 actor->getProto()->getId(),
195 WorkflowNotification::U2_WARNING);
196 }
197 }
198 }
199 return isValid;
200 }
201
validatePorts(Actor * a,NotificationsList & infoList)202 bool validatePorts(Actor *a, NotificationsList &infoList) {
203 bool good = true;
204 foreach (Port *p, a->getEnabledPorts()) {
205 NotificationsList notificationList;
206 good = p->validate(notificationList) && good;
207 if (!notificationList.isEmpty()) {
208 foreach (WorkflowNotification notification, notificationList) {
209 WorkflowNotification item;
210 item.message = notification.message;
211 item.port = p->getId();
212 item.actorId = a->getId();
213 item.type = notification.type;
214 infoList << item;
215 }
216 }
217 }
218 return good;
219 }
220
graphDepthFirstSearch(Actor * vertex,QList<Actor * > & visitedVertices)221 bool graphDepthFirstSearch(Actor *vertex, QList<Actor *> &visitedVertices) {
222 visitedVertices.append(vertex);
223 const QList<Port *> outputPorts = vertex->getOutputPorts();
224 QList<Actor *> receivingVertices;
225 foreach (Port *outputPort, outputPorts) {
226 foreach (Port *receivingPort, outputPort->getLinks().keys()) {
227 receivingVertices.append(receivingPort->owner());
228 }
229 }
230 foreach (Actor *receivingVertex, receivingVertices) {
231 if (visitedVertices.contains(receivingVertex)) {
232 return false;
233 } else {
234 return graphDepthFirstSearch(receivingVertex, visitedVertices);
235 }
236 }
237 return true;
238 }
239
240 // the returning values signals about cycles existence in the scheme
hasSchemeCycles(const Schema & scheme)241 bool hasSchemeCycles(const Schema &scheme) {
242 foreach (Actor *vertex, scheme.getProcesses()) {
243 QList<Actor *> visitedVertices;
244 if (!graphDepthFirstSearch(vertex, visitedVertices)) {
245 return false;
246 }
247 }
248 return true;
249 }
250
validateScript(Actor * a,NotificationsList & infoList)251 bool validateScript(Actor *a, NotificationsList &infoList) {
252 SAFE_POINT(nullptr != a, "NULL actor", false);
253 SAFE_POINT(nullptr != a->getScript(), "NULL script", false);
254 const QString scriptText = a->getScript()->getScriptText();
255 if (scriptText.simplified().isEmpty()) {
256 infoList << WorkflowNotification(QObject::tr("Empty script text"), a->getId());
257 return false;
258 }
259 QScopedPointer<WorkflowScriptEngine> engine(new WorkflowScriptEngine(nullptr));
260 QScriptSyntaxCheckResult syntaxResult = engine->checkSyntax(scriptText);
261
262 if (syntaxResult.state() != QScriptSyntaxCheckResult::Valid) {
263 WorkflowNotification notification;
264 notification.message = QObject::tr("Script syntax check failed! Line: %1, error: %2")
265 .arg(syntaxResult.errorLineNumber())
266 .arg(syntaxResult.errorMessage());
267 notification.actorId = a->getId();
268 notification.type = WorkflowNotification::U2_ERROR;
269 infoList << notification;
270 return false;
271 }
272 return true;
273 }
274
275 } // namespace
276
validate(const Schema & schema,NotificationsList & notificationList)277 bool WorkflowUtils::validate(const Schema &schema, NotificationsList ¬ificationList) {
278 bool isValid = validateOutputDir(WorkflowSettings::getWorkflowOutputDirectory(), notificationList);
279 foreach (Actor *actor, schema.getProcesses()) {
280 isValid = validatePorts(actor, notificationList) && isValid;
281 if (actor->getProto()->isScriptFlagSet()) {
282 isValid = validateScript(actor, notificationList) && isValid;
283 }
284 isValid = validateExternalTools(actor, notificationList) && isValid;
285 }
286 if (!hasSchemeCycles(schema)) {
287 isValid = false;
288 notificationList << WorkflowNotification(tr("The schema contains loops"));
289 }
290
291 isValid = validateParameters(schema, notificationList) && isValid;
292 return isValid;
293 }
294
295 // used in GUI schema validating
validate(const Schema & schema,QList<QListWidgetItem * > & infoList)296 bool WorkflowUtils::validate(const Schema &schema, QList<QListWidgetItem *> &infoList) {
297 NotificationsList notifications;
298 bool good = validate(schema, notifications);
299
300 foreach (const WorkflowNotification ¬ification, notifications) {
301 QListWidgetItem *item = nullptr;
302 Actor *a = nullptr;
303 if (notification.actorId.isEmpty()) {
304 item = new QListWidgetItem(notification.message);
305 } else {
306 a = schema.actorById(notification.actorId);
307 item = new QListWidgetItem(QString("%1: %2").arg(a->getLabel()).arg(notification.message));
308 }
309 if (notification.type == WorkflowNotification::U2_ERROR) {
310 item->setIcon(QIcon(":U2Lang/images/error.png"));
311 } else if (notification.type == WorkflowNotification::U2_WARNING) {
312 item->setIcon(QIcon(":U2Lang/images/warning.png"));
313 } else if (a != nullptr) {
314 item->setIcon(a->getProto()->getIcon());
315 }
316
317 item->setData(ACTOR_ID_REF, notification.actorId);
318 item->setData(PORT_REF, notification.port);
319 item->setData(TEXT_REF, notification.message);
320 item->setData(TYPE_REF, notification.type);
321
322 infoList << item;
323 }
324
325 return good;
326 }
327
328 // used in cmdline schema validating
validate(const Workflow::Schema & schema,QStringList & errs)329 bool WorkflowUtils::validate(const Workflow::Schema &schema, QStringList &errs) {
330 NotificationsList notifications;
331 bool good = validate(schema, notifications);
332
333 foreach (const WorkflowNotification ¬ification, notifications) {
334 QString res = QString();
335 Actor *a = schema.actorById(notification.actorId);
336 if (notification.actorId.isEmpty() || a == nullptr) {
337 res = notification.message;
338 } else {
339 QString message = notification.message;
340 res = QString("%1: %2").arg(a->getLabel()).arg(message);
341
342 QString option;
343 foreach (const Attribute *attr, a->getAttributes()) {
344 if (message.contains(attr->getDisplayName())) {
345 option = a->getParamAliases().value(attr->getId());
346 }
347 }
348 if (!option.isEmpty()) {
349 res += tr(" (use --%1 option)").arg(option);
350 }
351 }
352 errs << res;
353 }
354
355 return good;
356 }
357
findMatchingTypes(DataTypePtr set,DataTypePtr elementDataType)358 QList<Descriptor> WorkflowUtils::findMatchingTypes(DataTypePtr set, DataTypePtr elementDataType) {
359 QList<Descriptor> result;
360 foreach (const Descriptor &d, set->getAllDescriptors()) {
361 if (set->getDatatypeByDescriptor(d) == elementDataType) {
362 result.append(d);
363 }
364 }
365 return result;
366 }
367
candidatesAsStringList(const QList<Descriptor> & descList)368 QStringList WorkflowUtils::candidatesAsStringList(const QList<Descriptor> &descList) {
369 QStringList res;
370 foreach (const Descriptor &desc, descList) {
371 res << desc.getId();
372 }
373 return res;
374 }
375
findMatchingTypesAsStringList(DataTypePtr set,DataTypePtr elementDatatype)376 QStringList WorkflowUtils::findMatchingTypesAsStringList(DataTypePtr set, DataTypePtr elementDatatype) {
377 QList<Descriptor> descList = findMatchingTypes(set, elementDatatype);
378 return candidatesAsStringList(descList);
379 }
380
newEmptyValuesDesc()381 Descriptor newEmptyValuesDesc() {
382 return Descriptor("", QObject::tr("<empty>"), QObject::tr("Default value"));
383 }
384
findMatchingCandidates(DataTypePtr from,DataTypePtr elementDatatype)385 QList<Descriptor> WorkflowUtils::findMatchingCandidates(DataTypePtr from, DataTypePtr elementDatatype) {
386 QList<Descriptor> candidates = findMatchingTypes(from, elementDatatype);
387 if (elementDatatype->isList()) {
388 candidates += findMatchingTypes(from, elementDatatype->getDatatypeByDescriptor());
389 } else {
390 candidates.append(newEmptyValuesDesc());
391 }
392 return candidates;
393 }
394
findMatchingCandidates(DataTypePtr from,DataTypePtr to,const Descriptor & key)395 QList<Descriptor> WorkflowUtils::findMatchingCandidates(DataTypePtr from, DataTypePtr to, const Descriptor &key) {
396 return findMatchingCandidates(from, to->getDatatypeByDescriptor(key));
397 }
398
getCurrentMatchingDescriptor(const QList<Descriptor> & candidates,DataTypePtr to,const Descriptor & key,const StrStrMap & bindings)399 Descriptor WorkflowUtils::getCurrentMatchingDescriptor(const QList<Descriptor> &candidates, DataTypePtr to, const Descriptor &key, const StrStrMap &bindings) {
400 DataTypePtr elementDatatype = to->getDatatypeByDescriptor(key);
401 if (elementDatatype->isList()) {
402 QString currentVal = bindings.value(key.getId());
403 if (!currentVal.isEmpty()) {
404 return Descriptor(currentVal, tr("<List of values>"), tr("List of values"));
405 } else {
406 return newEmptyValuesDesc();
407 }
408 } else {
409 int idx = bindings.contains(key.getId()) ? candidates.indexOf(bindings.value(key.getId())) : 0;
410 return idx >= 0 ? candidates.at(idx) : newEmptyValuesDesc();
411 }
412 }
413
getToDatatypeForBusport(IntegralBusPort * p)414 DataTypePtr WorkflowUtils::getToDatatypeForBusport(IntegralBusPort *p) {
415 assert(p != nullptr);
416 DataTypePtr to;
417 DataTypePtr t = to = p->getType();
418 if (!t->isMap()) {
419 QMap<Descriptor, DataTypePtr> map;
420 map.insert(*p, t);
421 to = new MapDataType(Descriptor(), map);
422 // IntegralBusType* bt = new IntegralBusType(Descriptor(), QMap<Descriptor, DataTypePtr>());
423 // bt->addOutput(t, p);
424 }
425 return to;
426 }
427
getFromDatatypeForBusport(IntegralBusPort * p,DataTypePtr to)428 DataTypePtr WorkflowUtils::getFromDatatypeForBusport(IntegralBusPort *p, DataTypePtr to) {
429 assert(p != nullptr);
430
431 DataTypePtr from;
432 if (p->isOutput() || p->getWidth() == 0) {
433 // nothing to edit, go info mode
434 from = to;
435 } else {
436 // port is input and has links, go editing mode
437 IntegralBusType *bt = new IntegralBusType(Descriptor(), QMap<Descriptor, DataTypePtr>());
438 bt->addInputs(p, false);
439 from = bt;
440 }
441 return from;
442 }
443
findPathToSchemaFile(const QString & name)444 QString WorkflowUtils::findPathToSchemaFile(const QString &name) {
445 // full path given
446 if (QFile::exists(name)) {
447 return name;
448 }
449 // search schema in data dir
450 QString filenameWithDataPrefix = QString(PATH_PREFIX_DATA) + ":" + "cmdline/" + name;
451 if (QFile::exists(filenameWithDataPrefix)) {
452 return filenameWithDataPrefix;
453 }
454 foreach (const QString &ext, WorkflowUtils::WD_FILE_EXTENSIONS) {
455 QString filenameWithDataPrefixAndExt = QString(PATH_PREFIX_DATA) + ":" + "cmdline/" + name + "." + ext;
456 if (QFile::exists(filenameWithDataPrefixAndExt)) {
457 return filenameWithDataPrefixAndExt;
458 }
459 }
460
461 // if no such file found -> search name in settings. user saved schemas
462 Settings *settings = AppContext::getSettings();
463 assert(settings != nullptr);
464
465 // FIXME: same as WorkflowSceneIOTasks::SCHEMA_PATHS_SETTINGS_TAG
466 QVariantMap pathsMap = settings->getValue("workflow_settings/schema_paths").toMap();
467 QString path = pathsMap.value(name).toString();
468 if (QFile::exists(path)) {
469 return path;
470 }
471 return QString();
472 }
473
getLinkedActorsId(Actor * a,QList<QString> & linkedActors)474 void WorkflowUtils::getLinkedActorsId(Actor *a, QList<QString> &linkedActors) {
475 if (!linkedActors.contains(a->getId())) {
476 linkedActors.append(a->getId());
477 foreach (Port *p, a->getPorts()) {
478 foreach (Port *pp, p->getLinks().keys()) {
479 getLinkedActorsId(pp->owner(), linkedActors);
480 }
481 }
482 } else {
483 return;
484 }
485 }
486
isPathExist(const Port * src,const Port * dest)487 bool WorkflowUtils::isPathExist(const Port *src, const Port *dest) {
488 SAFE_POINT((src->isInput() ^ dest->isInput()), "The ports have the same direction", true);
489 if (!src->isOutput() && !dest->isInput()) {
490 const Port *tmp = dest;
491 dest = src;
492 src = tmp;
493 }
494 const Actor *destElement = dest->owner();
495
496 foreach (const Port *port, src->owner()->getPorts()) {
497 if (src == port) {
498 continue;
499 }
500 foreach (const Port *p, port->getLinks().keys()) {
501 if (destElement == p->owner()) {
502 return true;
503 }
504 if (isPathExist(p, dest)) {
505 return true;
506 }
507 }
508 }
509 return false;
510 }
511
getSlotDescOfDatatype(const DataTypePtr & dt)512 Descriptor WorkflowUtils::getSlotDescOfDatatype(const DataTypePtr &dt) {
513 QString dtId = dt->getId();
514 if (dtId == BaseTypes::DNA_SEQUENCE_TYPE()->getId()) {
515 return BaseSlots::DNA_SEQUENCE_SLOT();
516 }
517 if (dtId == BaseTypes::ANNOTATION_TABLE_TYPE()->getId()) {
518 return BaseSlots::ANNOTATION_TABLE_SLOT();
519 }
520 if (dtId == BaseTypes::MULTIPLE_ALIGNMENT_TYPE()->getId()) {
521 return BaseSlots::MULTIPLE_ALIGNMENT_SLOT();
522 }
523 if (dtId == BaseTypes::STRING_TYPE()->getId()) {
524 return BaseSlots::TEXT_SLOT();
525 }
526 SAFE_POINT(false, "Unexpected slot type", Descriptor());
527 return Descriptor();
528 }
529
initLowerToUpperList()530 static QStringList initLowerToUpperList() {
531 QStringList res;
532 res << "true";
533 res << "false";
534 return res;
535 }
536 static const QStringList lowerToUpperList = initLowerToUpperList();
537
getStringForParameterDisplayRole(const QVariant & value)538 QString WorkflowUtils::getStringForParameterDisplayRole(const QVariant &value) {
539 if (value.canConvert<QList<Dataset>>()) {
540 QString res;
541 foreach (const Dataset &dSet, value.value<QList<Dataset>>()) {
542 res += dSet.getName() + "; ";
543 }
544 return res;
545 }
546 QString str = value.toString();
547 if (lowerToUpperList.contains(str)) {
548 return str.at(0).toUpper() + str.mid(1);
549 }
550 return str;
551 }
552
findActorByParamAlias(const QList<Actor * > & procs,const QString & alias,QString & attrName,bool writeLog)553 Actor *WorkflowUtils::findActorByParamAlias(const QList<Actor *> &procs, const QString &alias, QString &attrName, bool writeLog) {
554 QList<Actor *> actors;
555 foreach (Actor *actor, procs) {
556 assert(actor != nullptr);
557 if (actor->getParamAliases().values().contains(alias)) {
558 actors << actor;
559 }
560 }
561
562 if (actors.isEmpty()) {
563 return nullptr;
564 } else if (actors.size() > 1) {
565 if (writeLog) {
566 coreLog.error(WorkflowUtils::tr("%1 actors in workflow have '%2' alias").arg(actors.size()).arg(alias));
567 }
568 }
569
570 Actor *ret = actors.first();
571 attrName = ret->getParamAliases().key(alias);
572 return ret;
573 }
574
getParamIdFromHref(const QString & href)575 QString WorkflowUtils::getParamIdFromHref(const QString &href) {
576 QStringList args = href.split('&');
577 const QString &prefix = QString("%1:").arg(HREF_PARAM_ID);
578 QString id;
579 foreach (QString arg, args) {
580 if (arg.startsWith(prefix)) {
581 id = arg.mid(prefix.length());
582 break;
583 }
584 }
585 return id;
586 }
587
generateIdFromName(const QString & name)588 QString WorkflowUtils::generateIdFromName(const QString &name) {
589 QString id = name;
590 id.replace(QRegularExpression("\\s"), "-").replace(WorkflowEntityValidator::INACCEPTABLE_SYMBOLS_IN_ID, "_");
591 return id;
592 }
593
data2text(WorkflowContext * context,DocumentFormatId formatId,GObject * obj,QString & text)594 static void data2text(WorkflowContext *context, DocumentFormatId formatId, GObject *obj, QString &text) {
595 QList<GObject *> objList;
596 objList << obj;
597
598 IOAdapterFactory *iof = AppContext::getIOAdapterRegistry()->getIOAdapterFactoryById(BaseIOAdapters::STRING);
599 DocumentFormat *df = AppContext::getDocumentFormatRegistry()->getFormatById(formatId);
600 QScopedPointer<Document> d(new Document(df, iof, GUrl(), context->getDataStorage()->getDbiRef(), objList));
601 d->setDocumentOwnsDbiResources(false);
602 StringAdapter *io = dynamic_cast<StringAdapter *>(iof->createIOAdapter());
603 io->open(GUrl(), IOAdapterMode_Write);
604 U2OpStatusImpl os;
605
606 df->storeDocument(d.data(), io, os);
607
608 text += io->getBuffer();
609 io->close();
610 }
611
612 #define STRING_TYPE QVariant::String
613 #define SEQUENCE_TYPE QVariant::ByteArray
614 #define MSA_TYPE QVariant::UserType
615 #define ANNOTATIONS_TYPE QVariant::List
616
print(const QString & slotString,const QVariant & data,DataTypePtr type,WorkflowContext * context)617 void WorkflowUtils::print(const QString &slotString, const QVariant &data, DataTypePtr type, WorkflowContext *context) {
618 QString text = slotString + ":\n";
619 Workflow::DbiDataStorage *storage = context->getDataStorage();
620 if ("string" == type->getId() || BaseTypes::STRING_LIST_TYPE() == type) {
621 text += data.toString();
622 } else if (BaseTypes::DNA_SEQUENCE_TYPE() == type) {
623 QScopedPointer<U2SequenceObject> obj(StorageUtils::getSequenceObject(storage, data.value<SharedDbiDataHandler>()));
624 CHECK(nullptr != obj.data(), );
625 data2text(context, BaseDocumentFormats::FASTA, obj.data(), text);
626 } else if (BaseTypes::MULTIPLE_ALIGNMENT_TYPE() == type) {
627 QScopedPointer<MultipleSequenceAlignmentObject> obj(StorageUtils::getMsaObject(storage, data.value<SharedDbiDataHandler>()));
628 CHECK(nullptr != obj.data(), );
629 data2text(context, BaseDocumentFormats::CLUSTAL_ALN, obj.data(), text);
630 } else if (BaseTypes::ANNOTATION_TABLE_TYPE() == type || BaseTypes::ANNOTATION_TABLE_LIST_TYPE() == type) {
631 QList<SharedAnnotationData> annotationList = StorageUtils::getAnnotationTable(storage, data);
632 AnnotationTableObject obj("Annotations", storage->getDbiRef());
633 obj.addAnnotations(annotationList);
634 data2text(context, BaseDocumentFormats::PLAIN_GENBANK, &obj, text);
635 } else {
636 text += "Can not print data of this type: " + type->getDisplayName();
637 }
638 printf("\n%s\n", text.toLatin1().data());
639 }
640
validateSchemaForIncluding(const Schema & s,QString & error)641 bool WorkflowUtils::validateSchemaForIncluding(const Schema &s, QString &error) {
642 // TEMPORARY disallow filter and grouper elements in includes
643 static QString errorStr = tr("The %1 element is a %2. Sorry, but current version of "
644 "UGENE doesn't support of filters and groupers in the includes.");
645 foreach (Actor *actor, s.getProcesses()) {
646 ActorPrototype *proto = actor->getProto();
647 if (proto->getInfluenceOnPathFlag() || CoreLibConstants::GROUPER_ID == proto->getId()) {
648 error = errorStr;
649 error = error.arg(actor->getLabel());
650 if (proto->getInfluenceOnPathFlag()) {
651 error = error.arg(tr("filter"));
652 } else {
653 error = error.arg(tr("grouper"));
654 }
655 return false;
656 }
657 }
658
659 const QList<PortAlias> &portAliases = s.getPortAliases();
660 if (portAliases.isEmpty()) {
661 error = tr("The workflow has not any aliased ports");
662 return false;
663 }
664
665 foreach (Actor *actor, s.getProcesses()) {
666 // check that free input ports are aliased
667 foreach (Port *port, actor->getPorts()) {
668 if (!port->isInput()) {
669 continue;
670 }
671 if (!port->getLinks().isEmpty()) {
672 continue;
673 }
674 bool aliased = false;
675 foreach (const PortAlias &alias, portAliases) {
676 if (alias.getSourcePort() == port) {
677 if (alias.getSlotAliases().isEmpty()) {
678 error = tr("The aliased port %1.%2 has no aliased slots").arg(actor->getLabel()).arg(port->getDisplayName());
679 return false;
680 } else {
681 aliased = true;
682 break;
683 }
684 }
685 }
686 if (!aliased) {
687 error = tr("The free port %1.%2 is not aliased").arg(actor->getLabel()).arg(port->getId());
688 return false;
689 }
690 }
691
692 // check that every required attribute is aliased or has set value
693 const QMap<QString, QString> ¶mAliases = actor->getParamAliases();
694 foreach (const QString &attrName, actor->getParameters().keys()) {
695 Attribute *attr = actor->getParameters().value(attrName);
696 if (attr->isRequiredAttribute() && !attr->canBeEmpty()) {
697 if (!paramAliases.contains(attr->getId())) {
698 QVariant val = attr->getAttributeValueWithoutScript<QVariant>();
699 if (val.isNull()) {
700 error = tr("The required parameter %1.%2 is empty and not aliased").arg(actor->getLabel()).arg(attr->getDisplayName());
701 return false;
702 }
703 }
704 }
705 }
706 }
707
708 return true;
709 }
710
extractPathsFromBindings(StrStrMap & busMap,SlotPathMap & pathMap)711 void WorkflowUtils::extractPathsFromBindings(StrStrMap &busMap, SlotPathMap &pathMap) {
712 QString srcId;
713 QStringList path;
714 foreach (const QString &dest, busMap.keys()) {
715 QStringList srcs = busMap.value(dest).split(";");
716 foreach (const QString &src, srcs) {
717 BusMap::parseSource(src, srcId, path);
718 if (!path.isEmpty()) {
719 QPair<QString, QString> slotPair(dest, srcId);
720 busMap[dest] = srcId;
721 pathMap.insertMulti(slotPair, path);
722 }
723 }
724 }
725 }
726
applyPathsToBusMap(StrStrMap & busMap,const SlotPathMap & pathMap)727 void WorkflowUtils::applyPathsToBusMap(StrStrMap &busMap, const SlotPathMap &pathMap) {
728 foreach (const QString &dest, busMap.keys()) {
729 QStringList newSrcs;
730
731 QStringList srcs = busMap.value(dest).split(";");
732 QStringList uniqList;
733 foreach (QString src, srcs) {
734 if (!uniqList.contains(src)) {
735 uniqList << src;
736 }
737 }
738
739 foreach (const QString &src, uniqList) {
740 QPair<QString, QString> slotPair(dest, src);
741 if (pathMap.contains(slotPair)) {
742 QList<QStringList> paths = pathMap.values(slotPair);
743 if (!paths.isEmpty()) {
744 foreach (const QStringList &path, paths) {
745 QString newSrc = src + ">" + path.join(",");
746 newSrcs << newSrc;
747 }
748 }
749 } else {
750 newSrcs << src;
751 }
752 }
753 busMap[dest] = newSrcs.join(";");
754 }
755 }
756
startExternalProcess(QProcess * process,const QString & program,const QStringList & arguments)757 bool WorkflowUtils::startExternalProcess(QProcess *process, const QString &program, const QStringList &arguments) {
758 return ExternalToolSupportUtils::startExternalProcess(process, program, arguments);
759 }
760
getDatasetsUrls(const QList<Dataset> & sets)761 QStringList WorkflowUtils::getDatasetsUrls(const QList<Dataset> &sets) {
762 QStringList result;
763 foreach (const Dataset &dSet, sets) {
764 foreach (URLContainer *url, dSet.getUrls()) {
765 result << url->getUrl();
766 }
767 }
768 return result;
769 }
770
getAttributeUrls(Attribute * attribute)771 QStringList WorkflowUtils::getAttributeUrls(Attribute *attribute) {
772 QStringList urlList;
773 QVariant var = attribute->getAttributePureValue();
774 if (var.canConvert<QList<Dataset>>()) {
775 urlList = WorkflowUtils::getDatasetsUrls(var.value<QList<Dataset>>());
776 } else if (var.canConvert(QVariant::String)) {
777 urlList = var.toString().split(";");
778 }
779 return urlList;
780 }
781
actorById(const QList<Actor * > & actors,const ActorId & id)782 Actor *WorkflowUtils::actorById(const QList<Actor *> &actors, const ActorId &id) {
783 foreach (Actor *a, actors) {
784 if (a->getId() == id) {
785 return a;
786 }
787 }
788 return nullptr;
789 }
790
getBusType(Port * inPort)791 QMap<Descriptor, DataTypePtr> WorkflowUtils::getBusType(Port *inPort) {
792 QMap<Port *, Link *> links = inPort->getLinks();
793 if (links.size() == 1) {
794 Port *src = links.keys().first();
795 assert(src->isOutput());
796 IntegralBusPort *bus = dynamic_cast<IntegralBusPort *>(src);
797 assert(nullptr != bus);
798 DataTypePtr type = bus->getType();
799 return type->getDatatypesMap();
800 }
801 return QMap<Descriptor, DataTypePtr>();
802 }
803
isBindingValid(const QString & srcSlotId,const QMap<Descriptor,DataTypePtr> & srcBus,const QString & dstSlotId,const QMap<Descriptor,DataTypePtr> & dstBus)804 bool WorkflowUtils::isBindingValid(const QString &srcSlotId, const QMap<Descriptor, DataTypePtr> &srcBus, const QString &dstSlotId, const QMap<Descriptor, DataTypePtr> &dstBus) {
805 DataTypePtr srcType;
806 // Check that incoming bus contains source slot
807 bool found = false;
808 foreach (const Descriptor &d, srcBus.keys()) {
809 if (d.getId() == srcSlotId) {
810 srcType = srcBus.value(d);
811 found = true;
812 break;
813 }
814 }
815 if (!found) {
816 return false;
817 }
818
819 // Check that source and destination slots have equal types
820 foreach (const Descriptor &d, dstBus.keys()) {
821 if (d.getId() == dstSlotId) {
822 DataTypePtr destType = dstBus.value(d);
823 QString stringTypeId("string");
824 if (destType == srcType) {
825 return true;
826 } else if (destType == BaseTypes::ANNOTATION_TABLE_TYPE()) {
827 return (srcType == BaseTypes::ANNOTATION_TABLE_LIST_TYPE());
828 } else if (destType == BaseTypes::ANNOTATION_TABLE_LIST_TYPE()) {
829 return (srcType == BaseTypes::ANNOTATION_TABLE_TYPE());
830 } else if (destType->getId() == stringTypeId) {
831 return (srcType == BaseTypes::STRING_LIST_TYPE());
832 } else if (destType == BaseTypes::STRING_LIST_TYPE()) {
833 return (srcType->getId() == stringTypeId);
834 }
835 break;
836 }
837 }
838
839 return false;
840 }
841
createUniqueString(const QString & str,const QString & sep,const QStringList & uniqueStrs)842 QString WorkflowUtils::createUniqueString(const QString &str, const QString &sep, const QStringList &uniqueStrs) {
843 QString result = str;
844 int number = 0;
845 bool found = false;
846 foreach (const QString &uniq, uniqueStrs) {
847 if (uniq == str) {
848 found = true;
849 number = qMax(number, 1);
850 } else {
851 int idx = uniq.lastIndexOf(sep);
852 if (-1 != idx) {
853 QString left = uniq.left(idx);
854 if (str == left) {
855 QString right = uniq.mid(idx + 1);
856 bool ok = false;
857 int num = right.toInt(&ok);
858 if (ok) {
859 found = true;
860 number = qMax(number, num + 1);
861 }
862 }
863 }
864 }
865 }
866
867 if (found) {
868 result += sep + QString::number(number);
869 }
870 return result;
871 }
872
updateExternalToolPath(const QString & id,const QString & path)873 QString WorkflowUtils::updateExternalToolPath(const QString &id, const QString &path) {
874 ExternalToolRegistry *registry = AppContext::getExternalToolRegistry();
875 SAFE_POINT(nullptr != registry, "NULL external tool registry", "");
876 ExternalTool *tool = registry->getById(id);
877 SAFE_POINT(nullptr != tool, QString("Unknown tool: %1").arg(id), "");
878
879 if (QString::compare(path, "default", Qt::CaseInsensitive) != 0) {
880 tool->setPath(path);
881 }
882 return tool->getPath();
883 }
884
getExternalToolPath(const QString & toolId)885 QString WorkflowUtils::getExternalToolPath(const QString &toolId) {
886 ExternalToolRegistry *registry = AppContext::getExternalToolRegistry();
887 SAFE_POINT(nullptr != registry, "NULL external tool registry", "");
888
889 ExternalTool *tool = registry->getById(toolId);
890 SAFE_POINT(nullptr != tool, QString("Unknown tool (id): %1").arg(toolId), "");
891
892 return tool->getPath();
893 }
894
externalToolIsAbsentError(const QString & toolName)895 QString WorkflowUtils::externalToolIsAbsentError(const QString &toolName) {
896 return tr("Specified variable \"%%1%\" does not exist, please check the command again.").arg(toolName);
897 }
898
externalToolError(const QString & toolName)899 QString WorkflowUtils::externalToolError(const QString &toolName) {
900 return tr("External tool \"%1\" is not set. You can set it in Settings -> Preferences -> External Tools").arg(toolName);
901 }
902
externalToolInvalidError(const QString & toolName)903 QString WorkflowUtils::externalToolInvalidError(const QString &toolName) {
904 return tr("External tool \"%1\" is invalid. UGENE may not support this version of the tool or a wrong path to the tools is selected").arg(toolName);
905 }
906
customExternalToolInvalidError(const QString & toolName,const QString & elementName)907 QString WorkflowUtils::customExternalToolInvalidError(const QString &toolName, const QString &elementName) {
908 return tr("Custom tool \"%1\", specified for the \"%2\" element, didn't pass validation.").arg(toolName).arg(elementName);
909 }
910
schemaFromFile(const QString & url,Schema * schema,Metadata * meta,U2OpStatus & os)911 void WorkflowUtils::schemaFromFile(const QString &url, Schema *schema, Metadata *meta, U2OpStatus &os) {
912 QFile file(url);
913 if (!file.open(QIODevice::ReadOnly)) {
914 os.setError(L10N::errorOpeningFileRead(url));
915 return;
916 }
917 QTextStream in(&file);
918 in.setCodec("UTF-8");
919 QString rawData = in.readAll();
920 file.close();
921
922 QString error = HRSchemaSerializer::string2Schema(rawData, schema, meta);
923 if (!error.isEmpty()) {
924 os.setError(error);
925 }
926 }
927
isDatasetsAttr(Attribute * attr)928 static bool isDatasetsAttr(Attribute *attr) {
929 URLAttribute *dsa = dynamic_cast<URLAttribute *>(attr);
930 return (nullptr != dsa);
931 }
932
isUrlAttribute(Attribute * attr,const Actor * actor)933 UrlAttributeType WorkflowUtils::isUrlAttribute(Attribute *attr, const Actor *actor) {
934 SAFE_POINT(nullptr != attr, "NULL attribute!", NotAnUrl);
935 SAFE_POINT(nullptr != actor, "NULL actor!", NotAnUrl);
936
937 if (isDatasetsAttr(attr)) {
938 return DatasetAttr;
939 }
940
941 ConfigurationEditor *editor = actor->getEditor();
942 CHECK(nullptr != editor, NotAnUrl);
943 PropertyDelegate *delegate = editor->getDelegate(attr->getId());
944 CHECK(nullptr != delegate, NotAnUrl);
945
946 if (PropertyDelegate::INPUT_FILE == delegate->type()) {
947 return InputFile;
948 }
949 if (PropertyDelegate::INPUT_DIR == delegate->type()) {
950 return InputDir;
951 }
952 if (PropertyDelegate::OUTPUT_FILE == delegate->type()) {
953 return OutputFile;
954 }
955 if (PropertyDelegate::OUTPUT_DIR == delegate->type()) {
956 return OutputDir;
957 }
958
959 return NotAnUrl;
960 }
961
962 /** Truncate the last ';' character */
normalizeUrls(QString & urls)963 static void normalizeUrls(QString &urls) {
964 if (!urls.isEmpty() && (1 != urls.size()) && (urls[urls.size() - 1] == ';')) {
965 urls.truncate(urls.size() - 1);
966 }
967 }
968
validateInputFiles(QString urls,NotificationsList & notificationList)969 bool WorkflowUtils::validateInputFiles(QString urls, NotificationsList ¬ificationList) {
970 normalizeUrls(urls);
971 if (urls.isEmpty()) {
972 return true;
973 }
974
975 // Verify each URL
976 QStringList urlsList = urls.split(';');
977 bool res = true;
978 foreach (const QString &url, urlsList) {
979 QFileInfo fi(url);
980 if (!fi.exists()) {
981 notificationList << WorkflowNotification(L10N::errorFileNotFound(url));
982 res = false;
983 } else if (!fi.isFile()) {
984 notificationList << WorkflowNotification(L10N::errorIsNotAFile(url));
985 res = false;
986 } else {
987 QFile testReadAccess(url);
988 if (testReadAccess.open(QIODevice::ReadOnly)) {
989 testReadAccess.close();
990 } else {
991 notificationList << WorkflowNotification(L10N::errorOpeningFileRead(url));
992 res = false;
993 }
994 }
995 }
996 return res;
997 }
998
validateInputDirs(QString urls,NotificationsList & notificationList)999 bool WorkflowUtils::validateInputDirs(QString urls, NotificationsList ¬ificationList) {
1000 normalizeUrls(urls);
1001 if (urls.isEmpty()) {
1002 return true;
1003 }
1004
1005 QStringList urlsList = urls.split(';');
1006 bool res = true;
1007 foreach (const QString &url, urlsList) {
1008 QFileInfo fi(url);
1009 if (!fi.exists()) {
1010 notificationList << WorkflowNotification(L10N::errorDirNotFound(url));
1011 res = false;
1012 } else if (!fi.isDir()) {
1013 notificationList << WorkflowNotification(L10N::errorIsNotADir(url));
1014 res = false;
1015 }
1016 }
1017 return res;
1018 }
1019
1020 namespace {
1021
url2Ref(const QString & url)1022 U2DbiRef url2Ref(const QString &url) {
1023 const QStringList urlParts = url.split(SharedDbUrlUtils::DB_PROVIDER_SEP);
1024 CHECK(urlParts.size() == 2, U2DbiRef());
1025
1026 return U2DbiRef(urlParts[0], urlParts[1]);
1027 }
1028
checkDbCredentials(const QString & dbUrl)1029 bool checkDbCredentials(const QString &dbUrl) {
1030 QString userName;
1031 const QString shortDbiUrl = U2DbiUtils::full2shortDbiUrl(dbUrl, userName);
1032 CHECK(!userName.isEmpty(), false);
1033
1034 if (!AppContext::getPasswordStorage()->contains(dbUrl)) {
1035 return AppContext::getCredentialsAsker()->askWithFixedLogin(dbUrl);
1036 } else {
1037 return true;
1038 }
1039 }
1040
checkObjectInDb(const QString & url)1041 bool checkObjectInDb(const QString &url) {
1042 const QStringList urlParts = url.split(",");
1043 SAFE_POINT(urlParts.size() == 2, "Invalid DB object URL", false);
1044 const QString dbUrl = urlParts[0];
1045
1046 U2OpStatusImpl os;
1047 const U2DbiRef dbRef = url2Ref(dbUrl);
1048 CHECK(dbRef.isValid(), false);
1049
1050 const U2DataId realId = SharedDbUrlUtils::getObjectIdByUrl(url);
1051 CHECK(!realId.isEmpty(), false);
1052
1053 DbiConnection connection(dbRef, os);
1054 CHECK_OP(os, false);
1055 CHECK(nullptr != connection.dbi, false);
1056
1057 U2ObjectDbi *oDbi = connection.dbi->getObjectDbi();
1058 CHECK(nullptr != oDbi, false);
1059 U2Object testObject;
1060 oDbi->getObject(testObject, realId, os);
1061 CHECK_OP(os, false);
1062
1063 return testObject.hasValidId();
1064 }
1065
checkFolderInDb(const QString & dbUrl,const QString & folderPath)1066 bool checkFolderInDb(const QString &dbUrl, const QString &folderPath) {
1067 U2OpStatusImpl os;
1068 const U2DbiRef dbRef = url2Ref(dbUrl);
1069 CHECK(dbRef.isValid(), false);
1070
1071 CHECK(!folderPath.isEmpty() && folderPath.startsWith(U2ObjectDbi::ROOT_FOLDER), false);
1072
1073 DbiConnection connection(dbRef, os);
1074 CHECK_OP(os, false);
1075 CHECK(nullptr != connection.dbi, false);
1076
1077 U2ObjectDbi *oDbi = connection.dbi->getObjectDbi();
1078 CHECK(nullptr != oDbi, false);
1079 const qint64 folderVersion = oDbi->getFolderLocalVersion(folderPath, os);
1080 CHECK_OP(os, false);
1081
1082 return -1 != folderVersion;
1083 }
1084
checkWritePermissionsForDb(const QString & fullDbUrl)1085 bool checkWritePermissionsForDb(const QString &fullDbUrl) {
1086 U2OpStatusImpl os;
1087 const U2DbiRef dbRef = SharedDbUrlUtils::getDbRefFromEntityUrl(fullDbUrl);
1088 CHECK(dbRef.isValid(), false);
1089
1090 DbiConnection connection(dbRef, os);
1091 CHECK_OP(os, false);
1092 return !connection.dbi->getFeatures().contains(U2DbiFeature_GlobalReadOnly);
1093 }
1094
1095 // If a database was unavailable for some reasons during previous validation procedures
1096 // and now has become available, it is needed to remove previous error messages regarding this from a notification list.
checkDbConnectionAndFixProblems(const QString & dbUrl,NotificationsList & notificationList,const WorkflowNotification & notificationMsg)1097 bool checkDbConnectionAndFixProblems(const QString &dbUrl, NotificationsList ¬ificationList, const WorkflowNotification ¬ificationMsg) {
1098 if (!WorkflowUtils::checkSharedDbConnection(dbUrl)) {
1099 notificationList << notificationMsg;
1100 return false;
1101 } else {
1102 foreach (WorkflowNotification notification, notificationList) {
1103 if (notification.message == notificationMsg.message && notification.type == notificationMsg.type) {
1104 notificationList.removeAll(notification);
1105 }
1106 }
1107 return true;
1108 }
1109 }
1110
1111 } // namespace
1112
checkSharedDbConnection(const QString & fullDbUrl)1113 bool WorkflowUtils::checkSharedDbConnection(const QString &fullDbUrl) {
1114 U2OpStatusImpl os;
1115 const U2DbiRef dbRef = SharedDbUrlUtils::getDbRefFromEntityUrl(fullDbUrl);
1116 CHECK(dbRef.isValid(), false);
1117 CHECK(checkDbCredentials(dbRef.dbiId), false);
1118
1119 DbiConnection connection(dbRef, os);
1120 CHECK_OP_EXT(os, AppContext::getPasswordStorage()->removeEntry(dbRef.dbiId), false);
1121 return connection.isOpen();
1122 }
1123
validateInputDbObject(const QString & url,NotificationsList & notificationList)1124 bool WorkflowUtils::validateInputDbObject(const QString &url, NotificationsList ¬ificationList) {
1125 const QString dbUrl = SharedDbUrlUtils::getDbUrlFromEntityUrl(url);
1126 const U2DataId objId = SharedDbUrlUtils::getObjectIdByUrl(url);
1127 const QString objName = SharedDbUrlUtils::getDbObjectNameByUrl(url);
1128 const QString shortDbName = SharedDbUrlUtils::getDbShortNameFromEntityUrl(url);
1129 if (dbUrl.isEmpty() || objId.isEmpty() || objName.isEmpty()) {
1130 notificationList << WorkflowNotification(L10N::errorWrongDbObjUrlFormat(url));
1131 return false;
1132 } else if (!checkDbConnectionAndFixProblems(dbUrl, notificationList, WorkflowNotification(L10N::errorDbInacsessible(shortDbName)))) {
1133 return false;
1134 } else if (!checkObjectInDb(url)) {
1135 notificationList << WorkflowNotification(L10N::errorDbObjectInaccessible(shortDbName, objName));
1136 return false;
1137 }
1138 return true;
1139 }
1140
validateInputDbFolders(QString urls,NotificationsList & notificationList)1141 bool WorkflowUtils::validateInputDbFolders(QString urls, NotificationsList ¬ificationList) {
1142 normalizeUrls(urls);
1143 if (urls.isEmpty()) {
1144 return true;
1145 }
1146
1147 QStringList urlsList = urls.split(';');
1148 bool res = true;
1149 foreach (const QString &url, urlsList) {
1150 const QString dbUrl = SharedDbUrlUtils::getDbUrlFromEntityUrl(url);
1151 const QString folderPath = SharedDbUrlUtils::getDbFolderPathByUrl(url);
1152 const U2DataType dataType = SharedDbUrlUtils::getDbFolderDataTypeByUrl(url);
1153 const QString shortDbName = SharedDbUrlUtils::getDbShortNameFromEntityUrl(url);
1154 if (dbUrl.isEmpty() || folderPath.isEmpty() || U2Type::Unknown == dataType) {
1155 notificationList << WorkflowNotification(L10N::errorWrongDbFolderUrlFormat(url));
1156 res = false;
1157 } else if (!checkDbConnectionAndFixProblems(dbUrl, notificationList, WorkflowNotification(L10N::errorDbInacsessible(shortDbName)))) {
1158 res = false;
1159 } else if (!checkFolderInDb(dbUrl, folderPath)) {
1160 notificationList << WorkflowNotification(L10N::errorDbFolderInacsessible(shortDbName, folderPath));
1161 res = false;
1162 }
1163 }
1164 return res;
1165 }
1166
validateOutputFile(const QString & url,NotificationsList & notificationList)1167 bool WorkflowUtils::validateOutputFile(const QString &url, NotificationsList ¬ificationList) {
1168 if (url.isEmpty()) {
1169 return true;
1170 }
1171
1172 QFileInfo fi(url);
1173 if (fi.isRelative()) {
1174 fi.setFile(QDir(WorkflowSettings::getWorkflowOutputDirectory()), url);
1175 }
1176
1177 if (FileAndDirectoryUtils::canWriteToPath(fi.absolutePath())) {
1178 return true;
1179 }
1180 notificationList << WorkflowNotification(tr("Can't access output file path: '%1'").arg(fi.absoluteFilePath()));
1181 return false;
1182 }
1183
validateOutputDir(const QString & url,NotificationsList & notificationList)1184 bool WorkflowUtils::validateOutputDir(const QString &url, NotificationsList ¬ificationList) {
1185 if (url.isEmpty()) {
1186 return true;
1187 }
1188
1189 QFileInfo fi(url);
1190 if (fi.isRelative()) {
1191 fi.setFile(QDir(WorkflowSettings::getWorkflowOutputDirectory()), url);
1192 }
1193
1194 if (FileAndDirectoryUtils::canWriteToPath(fi.absoluteFilePath())) {
1195 return true;
1196 }
1197 notificationList << WorkflowNotification(tr("Workflow output folder '%1' can't be accessed. Check that the folder exists and you have"
1198 " enough permissions to write to it, or choose another folder in the UGENE Application Settings.")
1199 .arg(url),
1200 "",
1201 WorkflowNotification::U2_ERROR);
1202 return false;
1203 }
1204
isSharedDbUrlAttribute(const Attribute * attr,const Actor * actor)1205 bool WorkflowUtils::isSharedDbUrlAttribute(const Attribute *attr, const Actor *actor) {
1206 SAFE_POINT(nullptr != attr, "Invalid attribute supplied", false);
1207 SAFE_POINT(nullptr != actor, "Invalid actor supplied", false);
1208
1209 ConfigurationEditor *editor = actor->getEditor();
1210 CHECK(nullptr != editor, false);
1211 PropertyDelegate *delegate = editor->getDelegate(attr->getId());
1212 CHECK(nullptr != delegate, false);
1213
1214 return PropertyDelegate::SHARED_DB_URL == delegate->type();
1215 }
1216
validateSharedDbUrl(const QString & url,NotificationsList & notificationList)1217 bool WorkflowUtils::validateSharedDbUrl(const QString &url, NotificationsList ¬ificationList) {
1218 if (url.isEmpty()) {
1219 notificationList << WorkflowNotification(tr("Empty shared database URL specified"));
1220 return false;
1221 }
1222
1223 const U2DbiRef dbRef = SharedDbUrlUtils::getDbRefFromEntityUrl(url);
1224 const QString shortDbName = SharedDbUrlUtils::getDbShortNameFromEntityUrl(url);
1225 if (!dbRef.isValid()) {
1226 notificationList << WorkflowNotification(L10N::errorWrongDbFolderUrlFormat(url));
1227 return false;
1228 } else if (!checkDbConnectionAndFixProblems(url, notificationList, WorkflowNotification(L10N::errorDbInacsessible(shortDbName)))) {
1229 return false;
1230 } else if (!checkWritePermissionsForDb(url)) {
1231 notificationList << WorkflowNotification(L10N::errorDbWritePermissons(shortDbName));
1232 return false;
1233 }
1234
1235 return true;
1236 }
1237
validateDatasets(const QList<Dataset> & sets,NotificationsList & notificationList)1238 bool WorkflowUtils::validateDatasets(const QList<Dataset> &sets, NotificationsList ¬ificationList) {
1239 bool res = true;
1240 foreach (const Dataset &set, sets) {
1241 foreach (URLContainer *urlContainer, set.getUrls()) {
1242 SAFE_POINT(nullptr != urlContainer, "NULL URLContainer!", false);
1243 bool urlIsValid = urlContainer->validateUrl(notificationList);
1244 res = res && urlIsValid;
1245 }
1246 }
1247 return res;
1248 }
1249
datasetsToScript(const QList<Dataset> & sets,QScriptEngine & engine)1250 QScriptValue WorkflowUtils::datasetsToScript(const QList<Dataset> &sets, QScriptEngine &engine) {
1251 QScriptValue setsArray = engine.newArray(sets.size());
1252
1253 for (int setIdx = 0; setIdx < sets.size(); setIdx++) {
1254 Dataset set = sets[setIdx];
1255 QScriptValue urls = engine.newArray(set.getUrls().size());
1256 for (int urlIdx = 0; urlIdx < set.getUrls().size(); urlIdx++) {
1257 QString url = set.getUrls()[urlIdx]->getUrl();
1258 urls.setProperty(urlIdx, engine.newVariant(url));
1259 }
1260 setsArray.setProperty(setIdx, urls);
1261 }
1262
1263 return setsArray;
1264 }
1265
getDatasetSplitter(const QString & filePaths)1266 QString WorkflowUtils::getDatasetSplitter(const QString &filePaths) {
1267 static const QString defaultSplitter = ";";
1268 static const QString additionalSplitter = ",";
1269
1270 if (filePaths.contains(defaultSplitter)) {
1271 return defaultSplitter;
1272 }
1273 return additionalSplitter;
1274 }
1275
packSamples(const QList<TophatSample> & samples)1276 QString WorkflowUtils::packSamples(const QList<TophatSample> &samples) {
1277 QStringList result;
1278 foreach (const TophatSample &sample, samples) {
1279 result << sample.name + ":" + sample.datasets.join(";");
1280 }
1281 return result.join(";;");
1282 }
1283
unpackSamples(const QString & samplesStr,U2OpStatus & os)1284 QList<TophatSample> WorkflowUtils::unpackSamples(const QString &samplesStr, U2OpStatus &os) {
1285 QList<TophatSample> result;
1286
1287 QStringList pairs = samplesStr.split(";;", QString::SkipEmptyParts);
1288 foreach (const QString &pairStr, pairs) {
1289 QStringList pair = pairStr.split(":", QString::KeepEmptyParts);
1290 if (2 != pair.size()) {
1291 os.setError(tr("Wrong samples map string"));
1292 return result;
1293 }
1294 result << TophatSample(pair[0], pair[1].split(";", QString::SkipEmptyParts));
1295 }
1296 return result;
1297 }
1298
1299 const QString WorkflowEntityValidator::NAME_INACCEPTABLE_SYMBOLS_TEMPLATE = "=\\\"";
1300 const QString WorkflowEntityValidator::ID_ACCEPTABLE_SYMBOLS_TEMPLATE = "a-zA-Z0-9\\-_";
1301
1302 const QRegularExpression WorkflowEntityValidator::ACCEPTABLE_NAME("[^" + NAME_INACCEPTABLE_SYMBOLS_TEMPLATE + "]*");
1303 const QRegularExpression WorkflowEntityValidator::INACCEPTABLE_SYMBOL_IN_NAME("[" + NAME_INACCEPTABLE_SYMBOLS_TEMPLATE + "]");
1304 const QRegularExpression WorkflowEntityValidator::ACCEPTABLE_ID("[" + ID_ACCEPTABLE_SYMBOLS_TEMPLATE + "]*");
1305 const QRegularExpression WorkflowEntityValidator::INACCEPTABLE_SYMBOLS_IN_ID("[^" + ID_ACCEPTABLE_SYMBOLS_TEMPLATE + "]");
1306
1307 /*****************************
1308 * PrompterBaseImpl
1309 *****************************/
getParameter(const QString & id)1310 QVariant PrompterBaseImpl::getParameter(const QString &id) {
1311 if (map.contains(id)) {
1312 return map.value(id);
1313 } else {
1314 return target->getParameter(id)->getAttributePureValue();
1315 }
1316 }
1317
getURL(const QString & id,bool * empty,const QString & onEmpty,const QString & defaultValue)1318 QString PrompterBaseImpl::getURL(const QString &id, bool *empty, const QString &onEmpty, const QString &defaultValue) {
1319 QVariant urlVar = getParameter(id);
1320 QString url;
1321 if (urlVar.canConvert<QList<Dataset>>()) {
1322 QStringList urls = WorkflowUtils::getDatasetsUrls(urlVar.value<QList<Dataset>>());
1323 url = urls.join(";");
1324 } else {
1325 url = getParameter(id).toString();
1326 }
1327 if (empty != nullptr) {
1328 *empty = false;
1329 }
1330 if (!target->getParameter(id)->getAttributeScript().isEmpty()) {
1331 url = "got from user script";
1332 } else if (url.isEmpty()) {
1333 if (!onEmpty.isEmpty()) {
1334 return onEmpty;
1335 }
1336 if (!defaultValue.isEmpty()) {
1337 url = defaultValue;
1338 } else {
1339 url = "<font color='red'>" + tr("unset") + "</font>";
1340 }
1341 if (empty != nullptr) {
1342 *empty = true;
1343 }
1344 } else if (url.indexOf(";") != -1) {
1345 url = tr("the list of files");
1346 } else if (SharedDbUrlUtils::isDbObjectUrl(url)) {
1347 url = SharedDbUrlUtils::getDbObjectNameByUrl(url);
1348 } else if (SharedDbUrlUtils::isDbFolderUrl(url)) {
1349 url = Folder::getFolderName(SharedDbUrlUtils::getDbFolderPathByUrl(url));
1350 } else {
1351 QString name = QFileInfo(url).fileName();
1352 if (!name.isEmpty()) {
1353 url = name;
1354 }
1355 }
1356 return url;
1357 }
1358
getRequiredParam(const QString & id)1359 QString PrompterBaseImpl::getRequiredParam(const QString &id) {
1360 QString url = getParameter(id).toString();
1361 if (url.isEmpty()) {
1362 url = "<font color='red'>" + tr("unset") + "</font>";
1363 }
1364 return url;
1365 }
1366
getScreenedURL(IntegralBusPort * input,const QString & id,const QString & slot,const QString & onEmpty)1367 QString PrompterBaseImpl::getScreenedURL(IntegralBusPort *input, const QString &id, const QString &slot, const QString &onEmpty) {
1368 bool empty = false;
1369 QString attrUrl = QString("<u>%1</u>").arg(getURL(id, &empty, onEmpty));
1370 if (!empty) {
1371 return attrUrl;
1372 }
1373
1374 Actor *origin = input->getProducer(slot);
1375 QString slotUrl;
1376 if (origin != nullptr) {
1377 slotUrl = tr("file(s) alongside of input sources of <u>%1</u>").arg(origin->getLabel());
1378 return slotUrl;
1379 }
1380
1381 assert(!attrUrl.isEmpty());
1382 return attrUrl;
1383 }
1384
getProducers(const QString & port,const QString & slot)1385 QString PrompterBaseImpl::getProducers(const QString &port, const QString &slot) {
1386 IntegralBusPort *input = qobject_cast<IntegralBusPort *>(target->getPort(port));
1387 CHECK(nullptr != input, "");
1388 QList<Actor *> producers = input->getProducers(slot);
1389
1390 QStringList labels;
1391 foreach (Actor *a, producers) {
1392 labels << a->getLabel();
1393 }
1394 return labels.join(", ");
1395 }
1396
getProducersOrUnset(const QString & port,const QString & slot)1397 QString PrompterBaseImpl::getProducersOrUnset(const QString &port, const QString &slot) {
1398 static const QString unsetStr = "<font color='red'>" + tr("unset") + "</font>";
1399 QString prods = getProducers(port, slot);
1400 return prods.isEmpty() ? unsetStr : prods;
1401 }
1402
getHyperlink(const QString & id,const QString & val)1403 QString PrompterBaseImpl::getHyperlink(const QString &id, const QString &val) {
1404 return QString("<a href=%1:%2>%3</a>").arg(WorkflowUtils::HREF_PARAM_ID).arg(id).arg(val);
1405 }
1406
getHyperlink(const QString & id,int val)1407 QString PrompterBaseImpl::getHyperlink(const QString &id, int val) {
1408 return getHyperlink(id, QString::number(val));
1409 }
1410
getHyperlink(const QString & id,qreal val)1411 QString PrompterBaseImpl::getHyperlink(const QString &id, qreal val) {
1412 return getHyperlink(id, QString::number(val));
1413 }
1414
sl_actorModified()1415 void PrompterBaseImpl::sl_actorModified() {
1416 if (AppContext::isGUIMode()) {
1417 setHtml(QString("<center><b>%1</b></center><hr>%2").arg(target->getLabel()).arg(composeRichDoc()));
1418 }
1419 }
1420
1421 } // namespace U2
1422