1 | /******************************************************************* |
2 | * bugzillalib.cpp |
3 | * Copyright 2009, 2011 Dario Andres Rodriguez <andresbajotierra@gmail.com> |
4 | * Copyright 2012 George Kiagiadakis <kiagiadakis.george@gmail.com> |
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 as |
8 | * published by the Free Software Foundation; either version 2 of |
9 | * 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, see <http://www.gnu.org/licenses/>. |
18 | * |
19 | ******************************************************************/ |
20 | |
21 | #include "bugzillalib.h" |
22 | |
23 | #include <QtCore/QTextStream> |
24 | #include <QtCore/QByteArray> |
25 | #include <QtCore/QString> |
26 | |
27 | #include <QtXml/QDomNode> |
28 | #include <QtXml/QDomNodeList> |
29 | #include <QtXml/QDomElement> |
30 | #include <QtXml/QDomNamedNodeMap> |
31 | |
32 | #include <KIO/Job> |
33 | #include <KUrl> |
34 | #include <KLocalizedString> |
35 | #include <KDebug> |
36 | |
37 | |
38 | static const char columns[] = "bug_severity,priority,bug_status,product,short_desc,resolution" ; |
39 | |
40 | //Bugzilla URLs |
41 | static const char searchUrl[] = |
42 | "buglist.cgi?query_format=advanced&order=Importance&ctype=csv" |
43 | "&product=%1" |
44 | "&longdesc_type=allwordssubstr&longdesc=%2" |
45 | "&chfieldfrom=%3&chfieldto=%4&chfield=[Bug+creation]" |
46 | "&bug_severity=%5" |
47 | "&columnlist=%6" ; |
48 | // short_desc, product, long_desc(possible backtraces lines), searchFrom, searchTo, severity, columnList |
49 | static const char showBugUrl[] = "show_bug.cgi?id=%1" ; |
50 | static const char fetchBugUrl[] = "show_bug.cgi?id=%1&ctype=xml" ; |
51 | |
52 | static inline Component buildComponent(const QVariantMap& map); |
53 | static inline Version buildVersion(const QVariantMap& map); |
54 | static inline Product buildProduct(const QVariantMap& map); |
55 | |
56 | //BEGIN BugzillaManager |
57 | |
58 | BugzillaManager::BugzillaManager(const QString &bugTrackerUrl, QObject *parent) |
59 | : QObject(parent) |
60 | , m_bugTrackerUrl(bugTrackerUrl) |
61 | , m_logged(false) |
62 | , m_searchJob(0) |
63 | { |
64 | m_xmlRpcClient = new KXmlRpc::Client(KUrl(m_bugTrackerUrl + "xmlrpc.cgi" ), this); |
65 | m_xmlRpcClient->setUserAgent(QLatin1String("DrKonqi" )); |
66 | } |
67 | |
68 | //BEGIN Login methods |
69 | void BugzillaManager::tryLogin(const QString& username, const QString& password) |
70 | { |
71 | m_username = username; |
72 | m_logged = false; |
73 | |
74 | QMap<QString, QVariant> args; |
75 | args.insert(QLatin1String("login" ), username); |
76 | args.insert(QLatin1String("password" ), password); |
77 | args.insert(QLatin1String("remember" ), false); |
78 | |
79 | m_xmlRpcClient->call(QLatin1String("User.login" ), args, |
80 | this, SLOT(callMessage(QList<QVariant>,QVariant)), |
81 | this, SLOT(callFault(int,QString,QVariant)), |
82 | QString::fromAscii("login" )); |
83 | } |
84 | |
85 | bool BugzillaManager::getLogged() const |
86 | { |
87 | return m_logged; |
88 | } |
89 | |
90 | QString BugzillaManager::getUsername() const |
91 | { |
92 | return m_username; |
93 | } |
94 | //END Login methods |
95 | |
96 | //BEGIN Bugzilla Action methods |
97 | void BugzillaManager::fetchBugReport(int bugnumber, QObject * jobOwner) |
98 | { |
99 | KUrl url = KUrl(QString(m_bugTrackerUrl) + QString(fetchBugUrl).arg(bugnumber)); |
100 | |
101 | if (!jobOwner) { |
102 | jobOwner = this; |
103 | } |
104 | |
105 | KIO::Job * fetchBugJob = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); |
106 | fetchBugJob->setParent(jobOwner); |
107 | connect(fetchBugJob, SIGNAL(finished(KJob*)) , this, SLOT(fetchBugJobFinished(KJob*))); |
108 | } |
109 | |
110 | |
111 | void BugzillaManager::searchBugs(const QStringList & products, |
112 | const QString & severity, const QString & date_start, |
113 | const QString & date_end, QString ) |
114 | { |
115 | QString product; |
116 | if (products.size() > 0) { |
117 | if (products.size() == 1) { |
118 | product = products.at(0); |
119 | } else { |
120 | Q_FOREACH(const QString & p, products) { |
121 | product += p + "&product=" ; |
122 | } |
123 | product = product.mid(0,product.size()-9); |
124 | } |
125 | } |
126 | |
127 | QString url = QString(m_bugTrackerUrl) + |
128 | QString(searchUrl).arg(product, comment.replace(' ' , '+'), date_start, |
129 | date_end, severity, QString(columns)); |
130 | |
131 | stopCurrentSearch(); |
132 | |
133 | m_searchJob = KIO::storedGet(KUrl(url) , KIO::Reload, KIO::HideProgressInfo); |
134 | connect(m_searchJob, SIGNAL(finished(KJob*)) , this, SLOT(searchBugsJobFinished(KJob*))); |
135 | } |
136 | |
137 | void BugzillaManager::sendReport(const BugReport & report) |
138 | { |
139 | QMap<QString, QVariant> args; |
140 | args.insert(QLatin1String("product" ), report.product()); |
141 | args.insert(QLatin1String("component" ), report.component()); |
142 | args.insert(QLatin1String("version" ), report.version()); |
143 | args.insert(QLatin1String("summary" ), report.shortDescription()); |
144 | args.insert(QLatin1String("description" ), report.description()); |
145 | args.insert(QLatin1String("op_sys" ), report.operatingSystem()); |
146 | args.insert(QLatin1String("platform" ), report.platform()); |
147 | args.insert(QLatin1String("keywords" ), report.keywords()); |
148 | args.insert(QLatin1String("priority" ), report.priority()); |
149 | args.insert(QLatin1String("severity" ), report.bugSeverity()); |
150 | |
151 | m_xmlRpcClient->call(QLatin1String("Bug.create" ), args, |
152 | this, SLOT(callMessage(QList<QVariant>,QVariant)), |
153 | this, SLOT(callFault(int,QString,QVariant)), |
154 | QString::fromAscii("Bug.create" )); |
155 | } |
156 | |
157 | void BugzillaManager::attachTextToReport(const QString & text, const QString & filename, |
158 | const QString & summary, int bugId, const QString & ) |
159 | { |
160 | QMap<QString, QVariant> args; |
161 | args.insert(QLatin1String("ids" ), QVariantList() << bugId); |
162 | args.insert(QLatin1String("file_name" ), filename); |
163 | args.insert(QLatin1String("summary" ), summary); |
164 | args.insert(QLatin1String("comment" ), comment); |
165 | args.insert(QLatin1String("content_type" ), QString::fromAscii("text/plain" )); |
166 | |
167 | //data needs to be a QByteArray so that it is encoded in base64 (query.cpp:246) |
168 | args.insert(QLatin1String("data" ), text.toUtf8()); |
169 | |
170 | m_xmlRpcClient->call(QLatin1String("Bug.add_attachment" ), args, |
171 | this, SLOT(callMessage(QList<QVariant>,QVariant)), |
172 | this, SLOT(callFault(int,QString,QVariant)), |
173 | QString::fromAscii("Bug.add_attachment" )); |
174 | } |
175 | |
176 | void BugzillaManager::addMeToCC(int bugId) |
177 | { |
178 | QMap<QString, QVariant> args; |
179 | args.insert(QLatin1String("ids" ), QVariantList() << bugId); |
180 | |
181 | QMap<QString, QVariant> ccChanges; |
182 | ccChanges.insert(QLatin1String("add" ), QVariantList() << m_username); |
183 | args.insert(QLatin1String("cc" ), ccChanges); |
184 | |
185 | m_xmlRpcClient->call(QLatin1String("Bug.update" ), args, |
186 | this, SLOT(callMessage(QList<QVariant>,QVariant)), |
187 | this, SLOT(callFault(int,QString,QVariant)), |
188 | QString::fromAscii("Bug.update.cc" )); |
189 | } |
190 | |
191 | void BugzillaManager::fetchProductInfo(const QString & product) |
192 | { |
193 | QMap<QString, QVariant> args; |
194 | |
195 | args.insert("names" , (QStringList() << product) ) ; |
196 | |
197 | QStringList includeFields; |
198 | // currently we only need these informations |
199 | includeFields << "name" << "is_active" << "components" << "versions" ; |
200 | |
201 | args.insert("include_fields" , includeFields) ; |
202 | |
203 | m_xmlRpcClient->call(QLatin1String("Product.get" ), args, |
204 | this, SLOT(callMessage(QList<QVariant>,QVariant)), |
205 | this, SLOT(callFault(int,QString,QVariant)), |
206 | QString::fromAscii("Product.get.versions" )); |
207 | } |
208 | |
209 | |
210 | //END Bugzilla Action methods |
211 | |
212 | //BEGIN Misc methods |
213 | QString BugzillaManager::urlForBug(int bug_number) const |
214 | { |
215 | return QString(m_bugTrackerUrl) + QString(showBugUrl).arg(bug_number); |
216 | } |
217 | |
218 | void BugzillaManager::stopCurrentSearch() |
219 | { |
220 | if (m_searchJob) { //Stop previous searchJob |
221 | m_searchJob->disconnect(); |
222 | m_searchJob->kill(); |
223 | m_searchJob = 0; |
224 | } |
225 | } |
226 | //END Misc methods |
227 | |
228 | //BEGIN Slots to handle KJob::finished |
229 | |
230 | void BugzillaManager::fetchBugJobFinished(KJob* job) |
231 | { |
232 | if (!job->error()) { |
233 | KIO::StoredTransferJob * fetchBugJob = static_cast<KIO::StoredTransferJob*>(job); |
234 | |
235 | BugReportXMLParser * parser = new BugReportXMLParser(fetchBugJob->data()); |
236 | BugReport report = parser->parse(); |
237 | |
238 | if (parser->isValid()) { |
239 | emit bugReportFetched(report, job->parent()); |
240 | } else { |
241 | emit bugReportError(i18nc("@info" ,"Invalid report information (malformed data). This " |
242 | "could mean that the bug report does not exist, or the " |
243 | "bug tracking site is experiencing a problem." ), job->parent()); |
244 | } |
245 | |
246 | delete parser; |
247 | } else { |
248 | emit bugReportError(job->errorString(), job->parent()); |
249 | } |
250 | } |
251 | |
252 | void BugzillaManager::searchBugsJobFinished(KJob * job) |
253 | { |
254 | if (!job->error()) { |
255 | KIO::StoredTransferJob * searchBugsJob = static_cast<KIO::StoredTransferJob*>(job); |
256 | |
257 | BugListCSVParser * parser = new BugListCSVParser(searchBugsJob->data()); |
258 | BugMapList list = parser->parse(); |
259 | |
260 | if (parser->isValid()) { |
261 | emit searchFinished(list); |
262 | } else { |
263 | emit searchError(i18nc("@info" ,"Invalid bug list: corrupted data" )); |
264 | } |
265 | |
266 | delete parser; |
267 | } else { |
268 | emit searchError(job->errorString()); |
269 | } |
270 | |
271 | m_searchJob = 0; |
272 | } |
273 | |
274 | static inline Component buildComponent(const QVariantMap& map) |
275 | { |
276 | QString name = map.value("name" ).toString(); |
277 | bool active = map.value("is_active" ).toBool(); |
278 | |
279 | return Component(name, active); |
280 | } |
281 | |
282 | static inline Version buildVersion(const QVariantMap& map) |
283 | { |
284 | QString name = map.value("name" ).toString(); |
285 | bool active = map.value("is_active" ).toBool(); |
286 | |
287 | return Version(name, active); |
288 | } |
289 | |
290 | static inline Product buildProduct(const QVariantMap& map) |
291 | { |
292 | QString name = map.value("name" ).toString(); |
293 | bool active = map.value("is_active" ).toBool(); |
294 | |
295 | Product product(name, active); |
296 | |
297 | QVariantList components = map.value("components" ).toList(); |
298 | foreach (const QVariant& c, components) { |
299 | Component component = buildComponent(c.toMap()); |
300 | product.addComponent(component); |
301 | |
302 | } |
303 | |
304 | QVariantList versions = map.value("versions" ).toList(); |
305 | foreach (const QVariant& v, versions) { |
306 | Version version = buildVersion(v.toMap()); |
307 | product.addVersion(version); |
308 | } |
309 | |
310 | return product; |
311 | } |
312 | |
313 | void BugzillaManager::fetchProductInfoFinished(const QVariantMap & map) |
314 | { |
315 | QList<Product> products; |
316 | |
317 | QVariantList plist = map.value("products" ).toList(); |
318 | foreach (const QVariant& p, plist) { |
319 | Product product = buildProduct(p.toMap()); |
320 | products.append(product); |
321 | } |
322 | |
323 | if ( products.size() > 0 ) { |
324 | emit productInfoFetched(products.at(0)); |
325 | } else { |
326 | emit productInfoError(); |
327 | } |
328 | } |
329 | |
330 | //END Slots to handle KJob::finished |
331 | |
332 | void BugzillaManager::callMessage(const QList<QVariant> & result, const QVariant & id) |
333 | { |
334 | kDebug() << id << result; |
335 | |
336 | if (id.toString() == QLatin1String("login" )) { |
337 | m_logged = true; |
338 | Q_EMIT loginFinished(true); |
339 | } else if (id.toString() == QLatin1String("Product.get.versions" )) { |
340 | QVariantMap map = result.at(0).toMap(); |
341 | fetchProductInfoFinished(map); |
342 | } else if (id.toString() == QLatin1String("Bug.create" )) { |
343 | QVariantMap map = result.at(0).toMap(); |
344 | int bug_id = map.value(QLatin1String("id" )).toInt(); |
345 | Q_ASSERT(bug_id != 0); |
346 | Q_EMIT reportSent(bug_id); |
347 | } else if (id.toString() == QLatin1String("Bug.add_attachment" )) { |
348 | QVariantMap map = result.at(0).toMap(); |
349 | if (map.contains(QLatin1String("attachments" ))){ // for bugzilla 4.2 |
350 | map = map.value(QLatin1String("attachments" )).toMap(); |
351 | map = map.constBegin()->toMap(); |
352 | const int attachment_id = map.value(QLatin1String("id" )).toInt(); |
353 | Q_EMIT attachToReportSent(attachment_id); |
354 | } else if (map.contains(QLatin1String("ids" ))) { // for bugzilla 4.4 |
355 | const int attachment_id = map.value(QLatin1String("ids" )).toList().at(0).toInt(); |
356 | Q_EMIT attachToReportSent(attachment_id); |
357 | } |
358 | } else if (id.toString() == QLatin1String("Bug.update.cc" )) { |
359 | QVariantMap map = result.at(0).toMap().value(QLatin1String("bugs" )).toList().at(0).toMap(); |
360 | int bug_id = map.value(QLatin1String("id" )).toInt(); |
361 | Q_ASSERT(bug_id != 0); |
362 | Q_EMIT addMeToCCFinished(bug_id); |
363 | } |
364 | } |
365 | |
366 | void BugzillaManager::callFault(int errorCode, const QString & errorString, const QVariant & id) |
367 | { |
368 | kDebug() << id << errorCode << errorString; |
369 | |
370 | QString genericError = i18nc("@info" , "Received unexpected error code %1 from bugzilla. " |
371 | "Error message was: %2" , errorCode, errorString); |
372 | |
373 | if (id.toString() == QLatin1String("login" )) { |
374 | switch(errorCode) { |
375 | case 300: //invalid username or password |
376 | Q_EMIT loginFinished(false); //TODO replace with loginError |
377 | break; |
378 | default: |
379 | Q_EMIT loginError(genericError); |
380 | break; |
381 | } |
382 | } else if (id.toString() == QLatin1String("Bug.create" )) { |
383 | switch (errorCode) { |
384 | case 51: //invalid object (one example is invalid platform value) |
385 | case 105: //invalid component |
386 | case 106: //invalid product |
387 | Q_EMIT sendReportErrorInvalidValues(); |
388 | break; |
389 | default: |
390 | Q_EMIT sendReportError(genericError); |
391 | break; |
392 | } |
393 | } else if (id.toString() == QLatin1String("Bug.add_attachment" )) { |
394 | switch (errorCode) { |
395 | default: |
396 | Q_EMIT attachToReportError(genericError); |
397 | break; |
398 | } |
399 | } else if (id.toString() == QLatin1String("Bug.update.cc" )) { |
400 | switch (errorCode) { |
401 | default: |
402 | Q_EMIT addMeToCCError(genericError); |
403 | break; |
404 | } |
405 | } |
406 | } |
407 | |
408 | //END BugzillaManager |
409 | |
410 | //BEGIN BugzillaCSVParser |
411 | |
412 | BugListCSVParser::BugListCSVParser(const QByteArray& data) |
413 | { |
414 | m_data = data; |
415 | m_isValid = false; |
416 | } |
417 | |
418 | BugMapList BugListCSVParser::parse() |
419 | { |
420 | BugMapList list; |
421 | |
422 | if (!m_data.isEmpty()) { |
423 | //Parse buglist CSV |
424 | QTextStream ts(&m_data); |
425 | QString = ts.readLine().remove(QLatin1Char('\"')) ; //Discard headers |
426 | QString = QString(columns); |
427 | |
428 | if (headersLine == (QString("bug_id," ) + expectedHeadersLine)) { |
429 | QStringList = expectedHeadersLine.split(',', QString::KeepEmptyParts); |
430 | int = headers.count(); |
431 | |
432 | while (!ts.atEnd()) { |
433 | BugMap bug; //bug report data map |
434 | |
435 | QString line = ts.readLine(); |
436 | |
437 | //Get bug_id (always at first column) |
438 | int bug_id_index = line.indexOf(','); |
439 | QString bug_id = line.left(bug_id_index); |
440 | bug.insert("bug_id" , bug_id); |
441 | |
442 | line = line.mid(bug_id_index + 2); |
443 | |
444 | QStringList fields = line.split(",\"" ); |
445 | |
446 | for (int i = 0; i < headersCount && i < fields.count(); i++) { |
447 | QString field = fields.at(i); |
448 | field = field.left(field.size() - 1) ; //Remove trailing " |
449 | bug.insert(headers.at(i), field); |
450 | } |
451 | |
452 | list.append(bug); |
453 | } |
454 | |
455 | m_isValid = true; |
456 | } |
457 | } |
458 | |
459 | return list; |
460 | } |
461 | |
462 | //END BugzillaCSVParser |
463 | |
464 | //BEGIN BugzillaXMLParser |
465 | |
466 | BugReportXMLParser::BugReportXMLParser(const QByteArray & data) |
467 | { |
468 | m_valid = m_xml.setContent(data, true); |
469 | } |
470 | |
471 | BugReport BugReportXMLParser::parse() |
472 | { |
473 | BugReport report; //creates an invalid and empty report object |
474 | |
475 | if (m_valid) { |
476 | //Check bug notfound |
477 | QDomNodeList bug_number = m_xml.elementsByTagName("bug" ); |
478 | QDomNode d = bug_number.at(0); |
479 | QDomNamedNodeMap a = d.attributes(); |
480 | QDomNode d2 = a.namedItem("error" ); |
481 | m_valid = d2.isNull(); |
482 | |
483 | if (m_valid) { |
484 | report.setValid(true); |
485 | |
486 | //Get basic fields |
487 | report.setBugNumber(getSimpleValue("bug_id" )); |
488 | report.setShortDescription(getSimpleValue("short_desc" )); |
489 | report.setProduct(getSimpleValue("product" )); |
490 | report.setComponent(getSimpleValue("component" )); |
491 | report.setVersion(getSimpleValue("version" )); |
492 | report.setOperatingSystem(getSimpleValue("op_sys" )); |
493 | report.setBugStatus(getSimpleValue("bug_status" )); |
494 | report.setResolution(getSimpleValue("resolution" )); |
495 | report.setPriority(getSimpleValue("priority" )); |
496 | report.setBugSeverity(getSimpleValue("bug_severity" )); |
497 | report.setMarkedAsDuplicateOf(getSimpleValue("dup_id" )); |
498 | report.setVersionFixedIn(getSimpleValue("cf_versionfixedin" )); |
499 | |
500 | //Parse full content + comments |
501 | QStringList ; |
502 | QDomNodeList = m_xml.elementsByTagName("long_desc" ); |
503 | for (int i = 0; i < comments.count(); i++) { |
504 | QDomElement element = comments.at(i).firstChildElement("thetext" ); |
505 | m_commentList << element.text(); |
506 | } |
507 | |
508 | report.setComments(m_commentList); |
509 | |
510 | } //isValid |
511 | } //isValid |
512 | |
513 | return report; |
514 | } |
515 | |
516 | QString BugReportXMLParser::getSimpleValue(const QString & name) //Extract an unique tag from XML |
517 | { |
518 | QString ret; |
519 | |
520 | QDomNodeList bug_number = m_xml.elementsByTagName(name); |
521 | if (bug_number.count() == 1) { |
522 | QDomNode node = bug_number.at(0); |
523 | ret = node.toElement().text(); |
524 | } |
525 | return ret; |
526 | } |
527 | |
528 | //END BugzillaXMLParser |
529 | |
530 | void BugReport::setBugStatus(const QString &stat) |
531 | { |
532 | setData("bug_status" , stat); |
533 | |
534 | m_status = parseStatus(stat); |
535 | } |
536 | |
537 | void BugReport::setResolution(const QString &res) |
538 | { |
539 | setData("resolution" , res); |
540 | |
541 | m_resolution = parseResolution(res); |
542 | } |
543 | |
544 | BugReport::Status BugReport::parseStatus(const QString &stat) |
545 | { |
546 | if (stat == QLatin1String("UNCONFIRMED" )) { |
547 | return Unconfirmed; |
548 | } else if (stat == QLatin1String("CONFIRMED" )) { |
549 | return New; |
550 | } else if (stat == QLatin1String("ASSIGNED" )) { |
551 | return Assigned; |
552 | } else if (stat == QLatin1String("REOPENED" )) { |
553 | return Reopened; |
554 | } else if (stat == QLatin1String("RESOLVED" )) { |
555 | return Resolved; |
556 | } else if (stat == QLatin1String("NEEDSINFO" )) { |
557 | return NeedsInfo; |
558 | } else if (stat == QLatin1String("VERIFIED" )) { |
559 | return Verified; |
560 | } else if (stat == QLatin1String("CLOSED" )) { |
561 | return Closed; |
562 | } else { |
563 | return UnknownStatus; |
564 | } |
565 | } |
566 | |
567 | BugReport::Resolution BugReport::parseResolution(const QString &res) |
568 | { |
569 | if (res.isEmpty()) { |
570 | return NotResolved; |
571 | } else if (res == QLatin1String("FIXED" )) { |
572 | return Fixed; |
573 | } else if (res == QLatin1String("INVALID" )) { |
574 | return Invalid; |
575 | } else if (res == QLatin1String("WONTFIX" )) { |
576 | return WontFix; |
577 | } else if (res == QLatin1String("LATER" )) { |
578 | return Later; |
579 | } else if (res == QLatin1String("REMIND" )) { |
580 | return Remind; |
581 | } else if (res == QLatin1String("DUPLICATE" )) { |
582 | return Duplicate; |
583 | } else if (res == QLatin1String("WORKSFORME" )) { |
584 | return WorksForMe; |
585 | } else if (res == QLatin1String("MOVED" )) { |
586 | return Moved; |
587 | } else if (res == QLatin1String("UPSTREAM" )) { |
588 | return Upstream; |
589 | } else if (res == QLatin1String("DOWNSTREAM" )) { |
590 | return Downstream; |
591 | } else if (res == QLatin1String("WAITINGFORINFO" )) { |
592 | return WaitingForInfo; |
593 | } else if (res == QLatin1String("BACKTRACE" )) { |
594 | return Backtrace; |
595 | } else if (res == QLatin1String("UNMAINTAINED" )) { |
596 | return Unmaintained; |
597 | } else { |
598 | return UnknownResolution; |
599 | } |
600 | } |
601 | |