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 "CustomToolConfigParser.h"
23
24 #include <QDir>
25 #include <QDomDocument>
26 #include <QFile>
27 #include <QFileInfo>
28 #include <QRegularExpression>
29
30 #include <U2Core/CustomExternalTool.h>
31 #include <U2Core/U2OpStatus.h>
32 #include <U2Core/U2SafePoints.h>
33
34 namespace U2 {
35
36 const QString CustomToolConfigParser::ELEMENT_CONFIG = "ugeneExternalToolConfig";
37 const QString CustomToolConfigParser::ATTRIBUTE_VERSION = "version";
38 const QString CustomToolConfigParser::HARDCODED_EXPECTED_VERSION = "1.0";
39
40 const QString CustomToolConfigParser::ID = "id";
41 const QString CustomToolConfigParser::NAME = "name";
42 const QString CustomToolConfigParser::PATH = "executableFullPath";
43 const QString CustomToolConfigParser::DESCRIPTION = "description";
44 const QString CustomToolConfigParser::TOOLKIT_NAME = "toolkitName";
45 const QString CustomToolConfigParser::TOOL_VERSION = "version";
46 const QString CustomToolConfigParser::LAUNCHER_ID = "launcherId";
47 const QString CustomToolConfigParser::DEPENDENCIES = "dependencies";
48 const QString CustomToolConfigParser::BINARY_NAME = "executableName";
49
50 namespace {
compareCaseInsensetive(const QString & first,const QString & second)51 bool compareCaseInsensetive(const QString &first, const QString &second) {
52 return QString::compare(first, second, Qt::CaseInsensitive) == 0;
53 }
54 } // namespace
55
parse(U2OpStatus & os,const QString & url)56 CustomExternalTool *CustomToolConfigParser::parse(U2OpStatus &os, const QString &url) {
57 QFile file(url);
58 CHECK_EXT(file.open(QIODevice::ReadOnly), os.setError(tr("Invalid config file format: file %1 cann not be opened").arg(url)), nullptr);
59
60 QDomDocument doc;
61 doc.setContent(&file);
62 file.close();
63
64 QScopedPointer<CustomExternalTool> tool(new CustomExternalTool());
65
66 const QDomNodeList nodesList = doc.elementsByTagName(ELEMENT_CONFIG);
67 CHECK_EXT(!nodesList.isEmpty(), os.setError(tr("Invalid config file format: custom tool description not found")), nullptr);
68 CHECK_EXT(1 == nodesList.count(), os.setError(tr("Invalid config file format: there are too many entities in the file")), nullptr);
69
70 QDomElement configElement = nodesList.item(0).toElement();
71 CHECK_EXT(!configElement.isNull(), os.setError(tr("Can't parse the config file")), nullptr);
72
73 const QString &version = configElement.attribute(ATTRIBUTE_VERSION);
74 CHECK_EXT(HARDCODED_EXPECTED_VERSION == version, os.setError(tr("Can't parse config with version %1").arg(version)), nullptr);
75
76 const QDomNodeList toolConfigElements = configElement.childNodes();
77 QFileInfo urlFi(url);
78 for (int i = 0, n = toolConfigElements.count(); i < n; ++i) {
79 const QDomElement element = toolConfigElements.item(i).toElement();
80 CHECK_CONTINUE(!element.isNull());
81 const QString tagName = element.tagName();
82
83 if (compareCaseInsensetive(ID, tagName)) {
84 tool->setId(element.text());
85 } else if (compareCaseInsensetive(NAME, tagName)) {
86 tool->setName(element.text());
87 } else if (compareCaseInsensetive(PATH, tagName)) {
88 if (!element.text().isEmpty()) {
89 QString text = element.text();
90 QFileInfo pathFi(element.text());
91 QString absPath;
92 if (pathFi.isRelative()) {
93 QString newPath = urlFi.absoluteDir().absolutePath() + "/" + element.text();
94 pathFi = QFileInfo(newPath);
95 }
96 absPath = pathFi.absoluteFilePath();
97 tool->setPath(absPath);
98 }
99 } else if (compareCaseInsensetive(DESCRIPTION, tagName)) {
100 tool->setDescription(element.text().replace(QRegularExpression("\\r?\\n"), "<br>"));
101 } else if (compareCaseInsensetive(TOOLKIT_NAME, tagName)) {
102 tool->setToolkitName(element.text());
103 } else if (compareCaseInsensetive(TOOL_VERSION, tagName)) {
104 tool->setPredefinedVersion(element.text());
105 } else if (compareCaseInsensetive(LAUNCHER_ID, tagName)) {
106 tool->setLauncher(element.text());
107 } else if (compareCaseInsensetive(DEPENDENCIES, tagName)) {
108 QStringList dependencies;
109 foreach (const QString &dependency, element.text().split(",", QString::SkipEmptyParts)) {
110 dependencies << dependency.trimmed();
111 }
112 tool->setDependencies(dependencies);
113 } else if (compareCaseInsensetive(BINARY_NAME, tagName)) {
114 tool->setBinaryName(element.text());
115 } else {
116 os.addWarning(tr("Unknown element: '%1', skipping").arg(tagName));
117 }
118 }
119
120 if (tool->getPath().isEmpty()) {
121 QString expectedExecutableUrl = urlFi.absoluteDir().absolutePath() + "/" + tool->getExecutableFileName();
122 QFile expectedExecutable(expectedExecutableUrl);
123 if (expectedExecutable.exists()) {
124 tool->setPath(expectedExecutableUrl);
125 }
126 }
127
128 if (tool->getToolKitName().isEmpty()) {
129 tool->setToolkitName(tool->getName());
130 }
131
132 const bool valid = validate(os, tool.data());
133 CHECK(valid, nullptr);
134
135 return tool.take();
136 }
137
serialize(CustomExternalTool * tool)138 QDomDocument CustomToolConfigParser::serialize(CustomExternalTool *tool) {
139 QDomDocument doc;
140 QDomProcessingInstruction xmlHeader = doc.createProcessingInstruction("xml", "version = \"1.0\" encoding = \"UTF-8\"");
141 doc.appendChild(xmlHeader);
142
143 QDomElement configElement = doc.createElement(ELEMENT_CONFIG);
144 configElement.setAttribute(ATTRIBUTE_VERSION, HARDCODED_EXPECTED_VERSION);
145 configElement.appendChild(addChildElement(doc, ID, tool->getId()));
146 configElement.appendChild(addChildElement(doc, NAME, tool->getName()));
147 configElement.appendChild(addChildElement(doc, PATH, tool->getPath()));
148 configElement.appendChild(addChildElement(doc, DESCRIPTION, tool->getDescription()));
149 configElement.appendChild(addChildElement(doc, TOOLKIT_NAME, tool->getToolKitName()));
150 configElement.appendChild(addChildElement(doc, TOOL_VERSION, tool->getPredefinedVersion()));
151 configElement.appendChild(addChildElement(doc, LAUNCHER_ID, tool->getToolRunnerProgramId()));
152 configElement.appendChild(addChildElement(doc, DEPENDENCIES, tool->getDependencies().join(",")));
153 configElement.appendChild(addChildElement(doc, BINARY_NAME, tool->getExecutableFileName()));
154 doc.appendChild(configElement);
155 return doc;
156 }
157
validate(U2OpStatus & os,CustomExternalTool * tool)158 bool CustomToolConfigParser::validate(U2OpStatus &os, CustomExternalTool *tool) {
159 CHECK(nullptr != tool, false);
160 CHECK_EXT(!tool->getId().isEmpty(), os.setError(tr("The tool id is not specified in the config file.")), false);
161 CHECK_EXT(!tool->getId().contains(QRegularExpression("[^A-Za-z0-9_\\-]")), os.setError(tr("The tool id contains unexpected characters, the only letters, numbers, underlines and dashes are allowed.")), false);
162 CHECK_EXT(!tool->getId().startsWith("USUPP_"), os.setError(tr("The custom tool's ID shouldn't start with \"USUPP_\", this is a distinguishing feature of the supported tools.")), false);
163 CHECK_EXT(!tool->getId().startsWith("UCUST_"), os.setError(tr("The custom tool's ID shouldn't start with \"UCUST_\", this is a distinguishing feature of the supported tools.")), false);
164 CHECK_EXT(!tool->getName().isEmpty(), os.setError(tr("The tool name is not specified in the config file.")), false);
165
166 CHECK_EXT(!tool->getExecutableFileName().isEmpty(), os.setError(tr("The imported custom tool \"%1\" does not have an executable file. Make sure to set up a valid executable file before you use the tool.").arg(tool->getName())), false)
167 if (tool->getPath().isEmpty()) {
168 os.addWarning(tr("The imported custom tool \"%1\" does not have an executable file. Make sure to set up a valid executable file before you use the tool.").arg(tool->getName()));
169 } else {
170 QFileInfo pathFi(tool->getPath());
171 if (!pathFi.exists()) {
172 os.addWarning(tr("The executable file \"%1\" specified for the imported custom tool \"%2\" doesn't exist. Make sure to set up a valid executable file before you use the tool.").arg(tool->getPath()).arg(tool->getName()));
173 }
174 }
175
176 return true;
177 }
178
addChildElement(QDomDocument & doc,const QString & elementName,const QString & elementData)179 QDomElement CustomToolConfigParser::addChildElement(QDomDocument &doc, const QString &elementName, const QString &elementData) {
180 QDomElement element = doc.createElement(elementName);
181 QDomText elementDataNode = doc.createTextNode(elementData);
182 element.appendChild(elementDataNode);
183 return element;
184 }
185
186 } // namespace U2
187