Changeset View
Changeset View
Standalone View
Standalone View
src/bugzillaintegration/bugzillalib.cpp
1 | /******************************************************************* | 1 | /******************************************************************* | ||
---|---|---|---|---|---|
2 | * bugzillalib.cpp | 2 | * bugzillalib.cpp | ||
3 | * Copyright 2009, 2011 Dario Andres Rodriguez <andresbajotierra@gmail.com> | 3 | * Copyright 2009, 2011 Dario Andres Rodriguez <andresbajotierra@gmail.com> | ||
4 | * Copyright 2012 George Kiagiadakis <kiagiadakis.george@gmail.com> | 4 | * Copyright 2012 George Kiagiadakis <kiagiadakis.george@gmail.com> | ||
5 | * Copyright 2019 Harald Sitter <sitter@kde.org> | ||||
5 | * | 6 | * | ||
6 | * This program is free software; you can redistribute it and/or | 7 | * 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 | * modify it under the terms of the GNU General Public License as | ||
8 | * published by the Free Software Foundation; either version 2 of | 9 | * published by the Free Software Foundation; either version 2 of | ||
9 | * the License, or (at your option) any later version. | 10 | * the License, or (at your option) any later version. | ||
10 | * | 11 | * | ||
11 | * This program is distributed in the hope that it will be useful, | 12 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | 15 | * GNU General Public License for more details. | ||
15 | * | 16 | * | ||
16 | * You should have received a copy of the GNU General Public License | 17 | * 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 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
18 | * | 19 | * | ||
19 | ******************************************************************/ | 20 | ******************************************************************/ | ||
20 | 21 | | |||
21 | #include "bugzillalib.h" | 22 | #include "bugzillalib.h" | ||
22 | 23 | | |||
23 | #include <QtGlobal> | 24 | #include "bugzillaintegration/libbugzilla/connection.h" | ||
24 | #include <QTextStream> | 25 | #include "bugzillaintegration/libbugzilla/bugzilla.h" | ||
25 | #include <QByteArray> | | |||
26 | #include <QString> | | |||
27 | | ||||
28 | #include <QDomNode> | | |||
29 | #include <QDomNodeList> | | |||
30 | #include <QDomElement> | | |||
31 | #include <QDomNamedNodeMap> | | |||
32 | | ||||
33 | #include <KIO/Job> | | |||
34 | #include <KLocalizedString> | | |||
35 | #include "drkonqi_debug.h" | 26 | #include "drkonqi_debug.h" | ||
36 | 27 | | |||
37 | #define MAKE_BUGZILLA_VERSION(a,b,c) (((a) << 16) | ((b) << 8) | (c)) | | |||
38 | | ||||
39 | static const char columns[] = "bug_severity,priority,bug_status,product,short_desc,resolution"; | | |||
40 | | ||||
41 | //Bugzilla URLs | | |||
42 | static const char searchUrl[] = | | |||
43 | "buglist.cgi?query_format=advanced&order=Importance&ctype=csv" | | |||
44 | "&product=%1" | | |||
45 | "&longdesc_type=allwordssubstr&longdesc=%2" | | |||
46 | "&chfieldfrom=%3&chfieldto=%4&chfield=[Bug+creation]" | | |||
47 | "&bug_severity=%5" | | |||
48 | "&columnlist=%6"; | | |||
49 | // short_desc, product, long_desc(possible backtraces lines), searchFrom, searchTo, severity, columnList | | |||
50 | static const char showBugUrl[] = "show_bug.cgi?id=%1"; | 28 | static const char showBugUrl[] = "show_bug.cgi?id=%1"; | ||
51 | static const char fetchBugUrl[] = "show_bug.cgi?id=%1&ctype=xml"; | | |||
52 | 29 | | |||
53 | static inline Component buildComponent(const QVariantMap& map); | 30 | #warning do a pass over all lingering qasserts and qunreachables. drkonqi should not crash, so unexpected things should generally raise a signal | ||
54 | static inline Version buildVersion(const QVariantMap& map); | 31 | #warning do a pass over all qdebugs to either drop or categorize | ||
55 | static inline Product buildProduct(const QVariantMap& map); | | |||
56 | | ||||
57 | //BEGIN BugzillaManager | | |||
58 | 32 | | |||
59 | BugzillaManager::BugzillaManager(const QString &bugTrackerUrl, QObject *parent) | 33 | BugzillaManager::BugzillaManager(const QString &bugTrackerUrl, QObject *parent) | ||
60 | : QObject(parent) | 34 | : QObject(parent) | ||
61 | , m_bugTrackerUrl(bugTrackerUrl) | 35 | , m_bugTrackerUrl(bugTrackerUrl) | ||
62 | , m_logged(false) | 36 | , m_logged(false) | ||
63 | , m_searchJob(nullptr) | 37 | , m_searchJob(nullptr) | ||
64 | { | 38 | { | ||
65 | m_xmlRpcClient = new KXmlRpc::Client(QUrl(m_bugTrackerUrl + QStringLiteral("xmlrpc.cgi")), this); | 39 | Q_ASSERT(bugTrackerUrl.endsWith(QChar('/'))); | ||
66 | m_xmlRpcClient->setUserAgent(QStringLiteral("DrKonqi")); | | |||
67 | // Allow constructors for ReportInterface and assistant dialogs to finish. | 40 | // Allow constructors for ReportInterface and assistant dialogs to finish. | ||
68 | // We do not want them to be racing the remote Bugzilla database. | 41 | // Otherwise we may have a race on our hand if the lookup finishes before | ||
42 | // the constructors. | ||||
69 | QMetaObject::invokeMethod (this, &BugzillaManager::lookupVersion, Qt::QueuedConnection); | 43 | QMetaObject::invokeMethod(this, &BugzillaManager::lookupVersion, Qt::QueuedConnection); | ||
70 | } | 44 | } | ||
71 | 45 | | |||
72 | // BEGIN Checks of Bugzilla software versions. | | |||
73 | void BugzillaManager::lookupVersion() | 46 | void BugzillaManager::lookupVersion() | ||
74 | { | 47 | { | ||
75 | QMap<QString, QVariant> args; | 48 | KJob *job = Bugzilla::version(); | ||
76 | callBugzilla("Bugzilla.version", "version", args, SecurityDisabled); | 49 | connect(job, &KJob::finished, this, [this](KJob *job) { | ||
50 | #warning FIXME: version detection in the dialog is completely broken and racing with this. make the signal a queued connection for now it has no signal is the reason | ||||
51 | try { | ||||
52 | QString version = Bugzilla::version(job); | ||||
53 | setFeaturesForVersion(version); | ||||
54 | emit bugzillaVersionFound(); | ||||
55 | } catch (Bugzilla::ProtocolException &e) { | ||||
56 | // Version detection problems simply mean we'll not mark the version | ||||
57 | // found and the UI will not allow reporting. | ||||
58 | qDebug() << e.whatString(); | ||||
59 | } | ||||
60 | }); | ||||
61 | job->start(); | ||||
77 | } | 62 | } | ||
78 | 63 | | |||
79 | void BugzillaManager::setFeaturesForVersion(const QString& version) | 64 | void BugzillaManager::setFeaturesForVersion(const QString& version) | ||
80 | { | 65 | { | ||
81 | // A procedure to change Dr Konqi behaviour automatically when Bugzilla | 66 | // A procedure to change Dr Konqi behaviour automatically when Bugzilla | ||
82 | // software versions change. | 67 | // software versions change. | ||
83 | // | 68 | // | ||
84 | // Changes should be added to Dr Konqi AHEAD of when the corresponding | 69 | // Changes should be added to Dr Konqi AHEAD of when the corresponding | ||
Show All 11 Lines | |||||
96 | QString seps = QLatin1String("[._-]"); | 81 | QString seps = QLatin1String("[._-]"); | ||
97 | QStringList digits = version.split(QRegExp(seps), QString::SkipEmptyParts); | 82 | QStringList digits = version.split(QRegExp(seps), QString::SkipEmptyParts); | ||
98 | while (digits.count() < nVersionParts) { | 83 | while (digits.count() < nVersionParts) { | ||
99 | digits << QLatin1String("0"); | 84 | digits << QLatin1String("0"); | ||
100 | } | 85 | } | ||
101 | if (digits.count() > nVersionParts) { | 86 | if (digits.count() > nVersionParts) { | ||
102 | qCWarning(DRKONQI_LOG) << QStringLiteral("Current Bugzilla version %1 has more than %2 parts. Check that this is not a problem.").arg(version).arg(nVersionParts); | 87 | qCWarning(DRKONQI_LOG) << QStringLiteral("Current Bugzilla version %1 has more than %2 parts. Check that this is not a problem.").arg(version).arg(nVersionParts); | ||
103 | } | 88 | } | ||
104 | int currentVersion = MAKE_BUGZILLA_VERSION(digits.at(0).toUInt(), | | |||
105 | digits.at(1).toUInt(), digits.at(2).toUInt()); | | |||
106 | | ||||
107 | // Set the code(s) for historical versions of Bugzilla - before any change. | | |||
108 | m_security = UseCookies; // Used to have cookies for update-security. | | |||
109 | 89 | | |||
110 | if (currentVersion >= MAKE_BUGZILLA_VERSION(4, 4, 3)) { | 90 | qCDebug(DRKONQI_LOG) << "VERSION" << version; | ||
111 | // Security method changes from cookies to tokens in Bugzilla 4.4.3. | | |||
112 | // BUT, tokens fail when kio_http sends any cookies found in KCookieJar, | | |||
113 | // so go directly to passwords-only security (supported since Bugzilla | | |||
114 | // 3.6 and will be enforced in Bugzilla 4.5.x). | | |||
115 | m_security = UsePasswords; | | |||
116 | } | | |||
117 | | ||||
118 | qCDebug(DRKONQI_LOG) << "VERSION" << version << "SECURITY" << m_security; | | |||
119 | } | | |||
120 | // END Checks of Bugzilla software versions. | | |||
121 | | ||||
122 | // BEGIN Generic remote-procedure (RPC) call to Bugzilla | | |||
123 | void BugzillaManager::callBugzilla(const char* method, const char* id, | | |||
124 | QMap<QString, QVariant>& args, | | |||
125 | SecurityStatus security) | | |||
126 | { | | |||
127 | if (security == SecurityEnabled) { | | |||
128 | switch (m_security) { | | |||
129 | case UseTokens: | | |||
130 | qCDebug(DRKONQI_LOG) << method << id << "using token"; | | |||
131 | args.insert(QLatin1String("Bugzilla_token"), m_token); | | |||
132 | break; | | |||
133 | case UsePasswords: | | |||
134 | qCDebug(DRKONQI_LOG) << method << id << "using username" << m_username; | | |||
135 | args.insert(QLatin1String("Bugzilla_login"), m_username); | | |||
136 | args.insert(QLatin1String("Bugzilla_password"), m_password); | | |||
137 | break; | | |||
138 | case UseCookies: | | |||
139 | qCDebug(DRKONQI_LOG) << method << id << "using cookies"; | | |||
140 | // Some KDE process other than Dr Konqi should provide cookies. | | |||
141 | break; | | |||
142 | } | 91 | } | ||
143 | } | | |||
144 | | ||||
145 | m_xmlRpcClient->call(QLatin1String(method), args, | | |||
146 | this, SLOT(callMessage(QList<QVariant>,QVariant)), | | |||
147 | this, SLOT(callFault(int,QString,QVariant)), | | |||
148 | QLatin1String(id)); | | |||
149 | } | | |||
150 | // END Generic call to Bugzilla | | |||
151 | 92 | | |||
152 | //BEGIN Login methods | | |||
153 | void BugzillaManager::tryLogin(const QString& username, const QString& password) | 93 | void BugzillaManager::tryLogin(const QString &username, const QString &password) | ||
154 | { | 94 | { | ||
155 | m_username = username; | 95 | m_username = username; | ||
156 | if (m_security == UsePasswords) { | | |||
157 | m_password = password; | 96 | m_password = password; | ||
158 | } | | |||
159 | m_logged = false; | 97 | m_logged = false; | ||
160 | 98 | | |||
161 | QMap<QString, QVariant> args; | 99 | KJob *job = Bugzilla::login(username, password); | ||
162 | args.insert(QLatin1String("login"), username); | 100 | connect(job, &KJob::finished, this, [this](KJob *job) { | ||
163 | args.insert(QLatin1String("password"), password); | 101 | try { | ||
164 | if (m_security == UseCookies) { | 102 | auto details = Bugzilla::login(job); | ||
165 | // Removed in Bugzilla 4.4.3 software, which no longer issues cookies. | 103 | m_token = details.token; | ||
166 | args.insert(QLatin1String("remember"), false); | 104 | Q_ASSERT(!m_token.isEmpty()); | ||
105 | Bugzilla::connection().setToken(m_token); | ||||
106 | m_logged = true; | ||||
107 | emit loginFinished(true); | ||||
108 | } catch (Bugzilla::ProtocolException &e) { | ||||
109 | // Version detection problems simply mean we'll not mark the version | ||||
110 | // found and the UI will not allow reporting. | ||||
111 | emit loginError(e.whatString()); | ||||
167 | } | 112 | } | ||
168 | 113 | }); | |||
169 | callBugzilla("User.login", "login", args, SecurityDisabled); | | |||
170 | } | 114 | } | ||
171 | 115 | | |||
172 | bool BugzillaManager::getLogged() const | 116 | bool BugzillaManager::getLogged() const | ||
173 | { | 117 | { | ||
174 | return m_logged; | 118 | return m_logged; | ||
175 | } | 119 | } | ||
176 | 120 | | |||
177 | QString BugzillaManager::getUsername() const | 121 | QString BugzillaManager::getUsername() const | ||
178 | { | 122 | { | ||
179 | return m_username; | 123 | return m_username; | ||
180 | } | 124 | } | ||
181 | //END Login methods | | |||
182 | 125 | | |||
183 | //BEGIN Bugzilla Action methods | | |||
184 | void BugzillaManager::fetchBugReport(int bugnumber, QObject * jobOwner) | 126 | void BugzillaManager::fetchBugReport(int bugnumber, QObject * jobOwner) | ||
185 | { | 127 | { | ||
186 | QUrl url(m_bugTrackerUrl + QString::fromLatin1(fetchBugUrl).arg(bugnumber)); | 128 | Bugzilla::Search search; | ||
129 | search.id = bugnumber; | ||||
187 | 130 | | |||
188 | if (!jobOwner) { | 131 | #warning this code seems a bit exessive | ||
189 | jobOwner = this; | 132 | Bugzilla::BugClient client; | ||
190 | } | 133 | auto job = m_searchJob = Bugzilla::BugClient().search(search); | ||
134 | connect(job, &KJob::finished, this, [this, &client, jobOwner](KJob *job) { | ||||
135 | if (job->error() != KJob::NoError) { | ||||
136 | #warning fixme exceptions not handled | ||||
137 | #warning fixme what the flip is the parnet for | ||||
138 | emit bugReportError(job->errorString(), jobOwner); | ||||
139 | return; | ||||
140 | } | ||||
141 | | ||||
142 | auto list = client.search(job); | ||||
143 | | ||||
144 | Q_ASSERT(list.size() == 1); | ||||
145 | auto bug = list.at(0); | ||||
146 | | ||||
147 | Bugzilla::CommentClient commentsClient; | ||||
148 | auto commentsJob = commentsClient.getFromBug(bug->id()); | ||||
149 | connect(commentsJob, &KJob::finished, this, [this, &commentsClient, jobOwner, bug](KJob *job) { | ||||
150 | auto comments = commentsClient.getFromBug(job); | ||||
151 | qDebug() << "comments arrived" << comments.size(); | ||||
152 | bug->setComments(comments); | ||||
191 | 153 | | |||
192 | KIO::Job * fetchBugJob = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); | 154 | m_searchJob = nullptr; | ||
193 | fetchBugJob->setParent(jobOwner); | 155 | #warning fixme what the flip is the parnet for | ||
194 | connect(fetchBugJob, &KIO::Job::finished, this, &BugzillaManager::fetchBugJobFinished); | 156 | emit bugReportFetched(bug, jobOwner); | ||
157 | }); | ||||
158 | }); | ||||
195 | } | 159 | } | ||
196 | 160 | | |||
197 | | ||||
198 | void BugzillaManager::searchBugs(const QStringList & products, | 161 | void BugzillaManager::searchBugs(const QStringList &products, | ||
199 | const QString & severity, const QString & date_start, | 162 | const QString &severity, const QString &creationDate, | ||
200 | const QString & date_end, QString comment) | 163 | const QString &comment) | ||
201 | { | 164 | { | ||
202 | QString product; | 165 | #warning comment not implemented. comment would filter by first backtrace function (see duplicates.cpp) but I am not sure how that is meant to work; sounds super expensive | ||
203 | if (products.size() > 0) { | 166 | qDebug() << products << "commenT:" << comment; | ||
204 | if (products.size() == 1) { | 167 | Bugzilla::Search search; | ||
205 | product = products.at(0); | 168 | search.products = products; | ||
206 | } else { | 169 | search.severity = severity; | ||
207 | Q_FOREACH(const QString & p, products) { | 170 | search.creationTime = creationDate; | ||
208 | product += p + QStringLiteral("&product="); | | |||
209 | } | | |||
210 | product = product.mid(0,product.size()-9); | | |||
211 | } | | |||
212 | } | | |||
213 | 171 | | |||
214 | QString url = QString(m_bugTrackerUrl) + | 172 | #warning fixme api does not have way to force order on returned array, does that matter | ||
215 | QString::fromLatin1(searchUrl).arg(product, comment.replace(QLatin1Char(' ') , QLatin1Char('+')), date_start, | | |||
216 | date_end, severity, QString::fromLatin1(columns)); | | |||
217 | 173 | | |||
218 | stopCurrentSearch(); | 174 | stopCurrentSearch(); | ||
219 | 175 | | |||
220 | m_searchJob = KIO::storedGet(QUrl(url) , KIO::Reload, KIO::HideProgressInfo); | 176 | Bugzilla::BugClient client; | ||
221 | connect(m_searchJob, &KIO::Job::finished, this, &BugzillaManager::searchBugsJobFinished); | 177 | auto job = m_searchJob = Bugzilla::BugClient().search(search); | ||
178 | connect(job, &KJob::finished, this, [this, &client](KJob *job) { | ||||
179 | try { | ||||
180 | auto list = client.search(job); | ||||
181 | m_searchJob = nullptr; | ||||
182 | emit searchFinished(list); | ||||
183 | } catch (Bugzilla::ProtocolException &e) { | ||||
184 | emit searchError(e.whatString()); | ||||
185 | } | ||||
186 | }); | ||||
222 | } | 187 | } | ||
223 | 188 | | |||
224 | void BugzillaManager::sendReport(const BugReport & report) | 189 | void BugzillaManager::sendReport(const Bugzilla::NewBug &bug) | ||
225 | { | 190 | { | ||
226 | QMap<QString, QVariant> args; | 191 | auto job = Bugzilla::BugClient().create(bug); | ||
227 | args.insert(QLatin1String("product"), report.product()); | 192 | connect(job, &KJob::finished, this, [this](KJob *job) { | ||
228 | args.insert(QLatin1String("component"), report.component()); | 193 | try { | ||
229 | args.insert(QLatin1String("version"), report.version()); | 194 | int id = Bugzilla::BugClient().create(job); | ||
230 | args.insert(QLatin1String("summary"), report.shortDescription()); | 195 | Q_ASSERT(id > 0); | ||
231 | args.insert(QLatin1String("description"), report.description()); | 196 | emit reportSent(id); | ||
232 | args.insert(QLatin1String("op_sys"), report.operatingSystem()); | 197 | } catch (Bugzilla::ProtocolException &e) { | ||
233 | args.insert(QLatin1String("platform"), report.platform()); | 198 | emit sendReportError(e.whatString()); | ||
234 | args.insert(QLatin1String("keywords"), report.keywords()); | 199 | } | ||
235 | args.insert(QLatin1String("priority"), report.priority()); | 200 | }); | ||
236 | args.insert(QLatin1String("severity"), report.bugSeverity()); | | |||
237 | | ||||
238 | callBugzilla("Bug.create", "Bug.create", args, SecurityEnabled); | | |||
239 | } | 201 | } | ||
240 | 202 | | |||
241 | void BugzillaManager::attachTextToReport(const QString & text, const QString & filename, | 203 | void BugzillaManager::attachTextToReport(const QString & text, const QString & filename, | ||
242 | const QString & summary, int bugId, const QString & comment) | 204 | const QString & summary, int bugId, const QString & comment) | ||
243 | { | 205 | { | ||
244 | QMap<QString, QVariant> args; | 206 | Bugzilla::NewAttachment attachment; | ||
245 | args.insert(QLatin1String("ids"), QVariantList() << bugId); | 207 | attachment.ids = QList<int> { bugId }; | ||
246 | args.insert(QLatin1String("file_name"), filename); | 208 | attachment.data = text; | ||
247 | args.insert(QLatin1String("summary"), summary); | 209 | attachment.file_name = filename; | ||
248 | args.insert(QLatin1String("comment"), comment); | 210 | attachment.summary = summary; | ||
249 | args.insert(QLatin1String("content_type"), QLatin1String("text/plain")); | 211 | attachment.comment = comment; | ||
250 | 212 | attachment.content_type = QLatin1Literal("text/plain"); | |||
251 | //data needs to be a QByteArray so that it is encoded in base64 (query.cpp:246) | 213 | | ||
252 | args.insert(QLatin1String("data"), text.toUtf8()); | 214 | auto job = Bugzilla::AttachmentClient().createAttachment(bugId, attachment); | ||
253 | 215 | connect(job, &KJob::finished, this, [this](KJob *job) { | |||
254 | callBugzilla("Bug.add_attachment", "Bug.add_attachment", args, | 216 | try { | ||
255 | SecurityEnabled); | 217 | QList<int> ids = Bugzilla::AttachmentClient().createAttachment(job); | ||
218 | Q_ASSERT(ids.size() == 1); | ||||
219 | emit attachToReportSent(ids.at(0)); | ||||
220 | } catch (Bugzilla::ProtocolException &e) { | ||||
221 | emit attachToReportError(e.whatString()); | ||||
222 | } | ||||
223 | }); | ||||
256 | } | 224 | } | ||
257 | 225 | | |||
258 | void BugzillaManager::addMeToCC(int bugId) | 226 | void BugzillaManager::addMeToCC(int bugId) | ||
259 | { | 227 | { | ||
260 | QMap<QString, QVariant> args; | 228 | Bugzilla::BugUpdate update; | ||
261 | args.insert(QLatin1String("ids"), QVariantList() << bugId); | 229 | Q_ASSERT(!m_username.isEmpty()); | ||
262 | 230 | update.cc.add << m_username; | |||
263 | QMap<QString, QVariant> ccChanges; | 231 | | ||
264 | ccChanges.insert(QLatin1String("add"), QVariantList() << m_username); | 232 | auto job = Bugzilla::BugClient().update(bugId, update); | ||
265 | args.insert(QLatin1String("cc"), ccChanges); | 233 | connect(job, &KJob::finished, this, [bugId, this](KJob *) { | ||
266 | 234 | #warning currently not looking at response! | |||
267 | callBugzilla("Bug.update", "Bug.update.cc", args, SecurityEnabled); | 235 | #warning capturing bugid in lamda | ||
236 | try { | ||||
237 | Q_ASSERT(bugId != 0); | ||||
238 | addMeToCCFinished(bugId); | ||||
239 | } catch (Bugzilla::ProtocolException &e) { | ||||
240 | emit addMeToCCError(e.whatString()); | ||||
241 | } | ||||
242 | }); | ||||
268 | } | 243 | } | ||
269 | 244 | | |||
270 | void BugzillaManager::fetchProductInfo(const QString & product) | 245 | void BugzillaManager::fetchProductInfo(const QString &product) | ||
271 | { | 246 | { | ||
272 | QMap<QString, QVariant> args; | 247 | auto job = Bugzilla::ProductClient().get(product); | ||
273 | 248 | connect(job, &KJob::finished, this, [this](KJob *job) { | |||
274 | args.insert(QStringLiteral("names"), (QStringList() << product) ) ; | 249 | try { | ||
275 | 250 | auto ptr = Bugzilla::ProductClient().get(job); | |||
276 | QStringList includeFields; | 251 | Q_ASSERT(ptr); | ||
277 | // currently we only need these informations | 252 | productInfoFetched(ptr); | ||
278 | includeFields << QStringLiteral("name") << QStringLiteral("is_active") << QStringLiteral("components") << QStringLiteral("versions"); | 253 | } catch (Bugzilla::ProtocolException &e) { | ||
279 | 254 | #warning why does this not have a string | |||
280 | args.insert(QStringLiteral("include_fields"), includeFields) ; | 255 | emit productInfoError(); | ||
281 | 256 | } | |||
282 | callBugzilla("Product.get", "Product.get.versions", args, SecurityDisabled); | 257 | }); | ||
283 | } | 258 | } | ||
284 | 259 | | |||
285 | | ||||
286 | //END Bugzilla Action methods | | |||
287 | | ||||
288 | //BEGIN Misc methods | | |||
289 | QString BugzillaManager::urlForBug(int bug_number) const | 260 | QString BugzillaManager::urlForBug(int bug_number) const | ||
290 | { | 261 | { | ||
291 | return QString(m_bugTrackerUrl) + QString::fromLatin1(showBugUrl).arg(bug_number); | 262 | return QString(m_bugTrackerUrl) + QString::fromLatin1(showBugUrl).arg(bug_number); | ||
292 | } | 263 | } | ||
293 | 264 | | |||
294 | void BugzillaManager::stopCurrentSearch() | 265 | void BugzillaManager::stopCurrentSearch() | ||
295 | { | 266 | { | ||
296 | if (m_searchJob) { //Stop previous searchJob | 267 | if (m_searchJob) { //Stop previous searchJob | ||
297 | m_searchJob->disconnect(); | 268 | m_searchJob->disconnect(); | ||
298 | m_searchJob->kill(); | 269 | m_searchJob->kill(); | ||
299 | m_searchJob = nullptr; | 270 | m_searchJob = nullptr; | ||
300 | } | 271 | } | ||
301 | } | 272 | } | ||
302 | //END Misc methods | | |||
303 | | ||||
304 | //BEGIN Slots to handle KJob::finished | | |||
305 | | ||||
306 | void BugzillaManager::fetchBugJobFinished(KJob* job) | | |||
307 | { | | |||
308 | if (!job->error()) { | | |||
309 | KIO::StoredTransferJob * fetchBugJob = static_cast<KIO::StoredTransferJob*>(job); | | |||
310 | | ||||
311 | BugReportXMLParser * parser = new BugReportXMLParser(fetchBugJob->data()); | | |||
312 | BugReport report = parser->parse(); | | |||
313 | | ||||
314 | if (parser->isValid()) { | | |||
315 | emit bugReportFetched(report, job->parent()); | | |||
316 | } else { | | |||
317 | emit bugReportError(i18nc("@info","Invalid report information (malformed data). This " | | |||
318 | "could mean that the bug report does not exist, or the " | | |||
319 | "bug tracking site is experiencing a problem."), job->parent()); | | |||
320 | } | | |||
321 | | ||||
322 | delete parser; | | |||
323 | } else { | | |||
324 | emit bugReportError(job->errorString(), job->parent()); | | |||
325 | } | | |||
326 | } | | |||
327 | | ||||
328 | void BugzillaManager::searchBugsJobFinished(KJob * job) | | |||
329 | { | | |||
330 | if (!job->error()) { | | |||
331 | KIO::StoredTransferJob * searchBugsJob = static_cast<KIO::StoredTransferJob*>(job); | | |||
332 | | ||||
333 | BugListCSVParser * parser = new BugListCSVParser(searchBugsJob->data()); | | |||
334 | BugMapList list = parser->parse(); | | |||
335 | | ||||
336 | if (parser->isValid()) { | | |||
337 | emit searchFinished(list); | | |||
338 | } else { | | |||
339 | emit searchError(i18nc("@info","Invalid bug list: corrupted data")); | | |||
340 | } | | |||
341 | | ||||
342 | delete parser; | | |||
343 | } else { | | |||
344 | emit searchError(job->errorString()); | | |||
345 | } | | |||
346 | | ||||
347 | m_searchJob = nullptr; | | |||
348 | } | | |||
349 | | ||||
350 | static inline Component buildComponent(const QVariantMap& map) | | |||
351 | { | | |||
352 | QString name = map.value(QStringLiteral("name")).toString(); | | |||
353 | bool active = map.value(QStringLiteral("is_active")).toBool(); | | |||
354 | | ||||
355 | return Component(name, active); | | |||
356 | } | | |||
357 | | ||||
358 | static inline Version buildVersion(const QVariantMap& map) | | |||
359 | { | | |||
360 | QString name = map.value(QStringLiteral("name")).toString(); | | |||
361 | bool active = map.value(QStringLiteral("is_active")).toBool(); | | |||
362 | | ||||
363 | return Version(name, active); | | |||
364 | } | | |||
365 | | ||||
366 | static inline Product buildProduct(const QVariantMap& map) | | |||
367 | { | | |||
368 | QString name = map.value(QStringLiteral("name")).toString(); | | |||
369 | bool active = map.value(QStringLiteral("is_active")).toBool(); | | |||
370 | | ||||
371 | Product product(name, active); | | |||
372 | | ||||
373 | QVariantList components = map.value(QStringLiteral("components")).toList(); | | |||
374 | foreach (const QVariant& c, components) { | | |||
375 | Component component = buildComponent(c.toMap()); | | |||
376 | product.addComponent(component); | | |||
377 | | ||||
378 | } | | |||
379 | | ||||
380 | QVariantList versions = map.value(QStringLiteral("versions")).toList(); | | |||
381 | foreach (const QVariant& v, versions) { | | |||
382 | Version version = buildVersion(v.toMap()); | | |||
383 | product.addVersion(version); | | |||
384 | } | | |||
385 | | ||||
386 | return product; | | |||
387 | } | | |||
388 | | ||||
389 | void BugzillaManager::fetchProductInfoFinished(const QVariantMap & map) | | |||
390 | { | | |||
391 | QList<Product> products; | | |||
392 | | ||||
393 | QVariantList plist = map.value(QStringLiteral("products")).toList(); | | |||
394 | foreach (const QVariant& p, plist) { | | |||
395 | Product product = buildProduct(p.toMap()); | | |||
396 | products.append(product); | | |||
397 | } | | |||
398 | | ||||
399 | if ( products.size() > 0 ) { | | |||
400 | emit productInfoFetched(products.at(0)); | | |||
401 | } else { | | |||
402 | emit productInfoError(); | | |||
403 | } | | |||
404 | } | | |||
405 | | ||||
406 | //END Slots to handle KJob::finished | | |||
407 | | ||||
408 | void BugzillaManager::callMessage(const QList<QVariant> & result, const QVariant & id) | | |||
409 | { | | |||
410 | qCDebug(DRKONQI_LOG) << id << result; | | |||
411 | | ||||
412 | if (id.toString() == QLatin1String("login")) { | | |||
413 | if ((m_security == UseTokens) && (result.count() > 0)) { | | |||
414 | QVariantMap map = result.at(0).toMap(); | | |||
415 | m_token = map.value(QLatin1String("token")).toString(); | | |||
416 | } | | |||
417 | m_logged = true; | | |||
418 | Q_EMIT loginFinished(true); | | |||
419 | } else if (id.toString() == QLatin1String("Product.get.versions")) { | | |||
420 | QVariantMap map = result.at(0).toMap(); | | |||
421 | fetchProductInfoFinished(map); | | |||
422 | } else if (id.toString() == QLatin1String("Bug.create")) { | | |||
423 | QVariantMap map = result.at(0).toMap(); | | |||
424 | int bug_id = map.value(QLatin1String("id")).toInt(); | | |||
425 | Q_ASSERT(bug_id != 0); | | |||
426 | Q_EMIT reportSent(bug_id); | | |||
427 | } else if (id.toString() == QLatin1String("Bug.add_attachment")) { | | |||
428 | QVariantMap map = result.at(0).toMap(); | | |||
429 | if (map.contains(QLatin1String("attachments"))){ // for bugzilla 4.2 | | |||
430 | map = map.value(QLatin1String("attachments")).toMap(); | | |||
431 | map = map.constBegin()->toMap(); | | |||
432 | const int attachment_id = map.value(QLatin1String("id")).toInt(); | | |||
433 | Q_EMIT attachToReportSent(attachment_id); | | |||
434 | } else if (map.contains(QLatin1String("ids"))) { // for bugzilla 4.4 | | |||
435 | const int attachment_id = map.value(QLatin1String("ids")).toList().at(0).toInt(); | | |||
436 | Q_EMIT attachToReportSent(attachment_id); | | |||
437 | } | | |||
438 | } else if (id.toString() == QLatin1String("Bug.update.cc")) { | | |||
439 | QVariantMap map = result.at(0).toMap().value(QLatin1String("bugs")).toList().at(0).toMap(); | | |||
440 | int bug_id = map.value(QLatin1String("id")).toInt(); | | |||
441 | Q_ASSERT(bug_id != 0); | | |||
442 | Q_EMIT addMeToCCFinished(bug_id); | | |||
443 | } else if (id.toString() == QLatin1String("version")) { | | |||
444 | QVariantMap map = result.at(0).toMap(); | | |||
445 | QString bugzillaVersion = map.value(QLatin1String("version")).toString(); | | |||
446 | setFeaturesForVersion(bugzillaVersion); | | |||
447 | Q_EMIT bugzillaVersionFound(); | | |||
448 | } | | |||
449 | } | | |||
450 | | ||||
451 | void BugzillaManager::callFault(int errorCode, const QString & errorString, const QVariant & id) | | |||
452 | { | | |||
453 | qCDebug(DRKONQI_LOG) << id << errorCode << errorString; | | |||
454 | | ||||
455 | QString genericError = i18nc("@info", "Received unexpected error code %1 from bugzilla. " | | |||
456 | "Error message was: %2", errorCode, errorString); | | |||
457 | | ||||
458 | if (id.toString() == QLatin1String("login")) { | | |||
459 | switch(errorCode) { | | |||
460 | case 300: //invalid username or password | | |||
461 | Q_EMIT loginFinished(false); //TODO replace with loginError | | |||
462 | break; | | |||
463 | default: | | |||
464 | Q_EMIT loginError(genericError); | | |||
465 | break; | | |||
466 | } | | |||
467 | } else if (id.toString() == QLatin1String("Bug.create")) { | | |||
468 | switch (errorCode) { | | |||
469 | case 51: //invalid object (one example is invalid platform value) | | |||
470 | case 105: //invalid component | | |||
471 | case 106: //invalid product | | |||
472 | Q_EMIT sendReportErrorInvalidValues(); | | |||
473 | break; | | |||
474 | default: | | |||
475 | Q_EMIT sendReportError(genericError); | | |||
476 | break; | | |||
477 | } | | |||
478 | } else if (id.toString() == QLatin1String("Bug.add_attachment")) { | | |||
479 | switch (errorCode) { | | |||
480 | default: | | |||
481 | Q_EMIT attachToReportError(genericError); | | |||
482 | break; | | |||
483 | } | | |||
484 | } else if (id.toString() == QLatin1String("Bug.update.cc")) { | | |||
485 | switch (errorCode) { | | |||
486 | default: | | |||
487 | Q_EMIT addMeToCCError(genericError); | | |||
488 | break; | | |||
489 | } | | |||
490 | } | | |||
491 | } | | |||
492 | | ||||
493 | //END BugzillaManager | | |||
494 | | ||||
495 | //BEGIN BugzillaCSVParser | | |||
496 | | ||||
497 | BugListCSVParser::BugListCSVParser(const QByteArray& data) | | |||
498 | { | | |||
499 | m_data = data; | | |||
500 | m_isValid = false; | | |||
501 | } | | |||
502 | | ||||
503 | BugMapList BugListCSVParser::parse() | | |||
504 | { | | |||
505 | BugMapList list; | | |||
506 | | ||||
507 | if (!m_data.isEmpty()) { | | |||
508 | //Parse buglist CSV | | |||
509 | QTextStream ts(&m_data); | | |||
510 | QString headersLine = ts.readLine().remove(QLatin1Char('\"')) ; //Discard headers | | |||
511 | QString expectedHeadersLine = QString::fromLatin1(columns); | | |||
512 | | ||||
513 | if (headersLine == (QStringLiteral("bug_id,") + expectedHeadersLine)) { | | |||
514 | QStringList headers = expectedHeadersLine.split(QLatin1Char(','), QString::KeepEmptyParts); | | |||
515 | int headersCount = headers.count(); | | |||
516 | | ||||
517 | while (!ts.atEnd()) { | | |||
518 | BugMap bug; //bug report data map | | |||
519 | | ||||
520 | QString line = ts.readLine(); | | |||
521 | | ||||
522 | //Get bug_id (always at first column) | | |||
523 | int bug_id_index = line.indexOf(QLatin1Char(',')); | | |||
524 | QString bug_id = line.left(bug_id_index); | | |||
525 | bug.insert(QStringLiteral("bug_id"), bug_id); | | |||
526 | | ||||
527 | line = line.mid(bug_id_index + 2); | | |||
528 | | ||||
529 | QStringList fields = line.split(QStringLiteral(",\"")); | | |||
530 | | ||||
531 | for (int i = 0; i < headersCount && i < fields.count(); i++) { | | |||
532 | QString field = fields.at(i); | | |||
533 | field = field.left(field.size() - 1) ; //Remove trailing " | | |||
534 | bug.insert(headers.at(i), field); | | |||
535 | } | | |||
536 | | ||||
537 | list.append(bug); | | |||
538 | } | | |||
539 | | ||||
540 | m_isValid = true; | | |||
541 | } | | |||
542 | } | | |||
543 | | ||||
544 | return list; | | |||
545 | } | | |||
546 | | ||||
547 | //END BugzillaCSVParser | | |||
548 | | ||||
549 | //BEGIN BugzillaXMLParser | | |||
550 | | ||||
551 | BugReportXMLParser::BugReportXMLParser(const QByteArray & data) | | |||
552 | { | | |||
553 | m_valid = m_xml.setContent(data, true); | | |||
554 | } | | |||
555 | | ||||
556 | BugReport BugReportXMLParser::parse() | | |||
557 | { | | |||
558 | BugReport report; //creates an invalid and empty report object | | |||
559 | | ||||
560 | if (m_valid) { | | |||
561 | //Check bug notfound | | |||
562 | QDomNodeList bug_number = m_xml.elementsByTagName(QStringLiteral("bug")); | | |||
563 | QDomNode d = bug_number.at(0); | | |||
564 | QDomNamedNodeMap a = d.attributes(); | | |||
565 | QDomNode d2 = a.namedItem(QStringLiteral("error")); | | |||
566 | m_valid = d2.isNull(); | | |||
567 | | ||||
568 | if (m_valid) { | | |||
569 | report.setValid(true); | | |||
570 | | ||||
571 | //Get basic fields | | |||
572 | report.setBugNumber(getSimpleValue(QStringLiteral("bug_id"))); | | |||
573 | report.setShortDescription(getSimpleValue(QStringLiteral("short_desc"))); | | |||
574 | report.setProduct(getSimpleValue(QStringLiteral("product"))); | | |||
575 | report.setComponent(getSimpleValue(QStringLiteral("component"))); | | |||
576 | report.setVersion(getSimpleValue(QStringLiteral("version"))); | | |||
577 | report.setOperatingSystem(getSimpleValue(QStringLiteral("op_sys"))); | | |||
578 | report.setBugStatus(getSimpleValue(QStringLiteral("bug_status"))); | | |||
579 | report.setResolution(getSimpleValue(QStringLiteral("resolution"))); | | |||
580 | report.setPriority(getSimpleValue(QStringLiteral("priority"))); | | |||
581 | report.setBugSeverity(getSimpleValue(QStringLiteral("bug_severity"))); | | |||
582 | report.setMarkedAsDuplicateOf(getSimpleValue(QStringLiteral("dup_id"))); | | |||
583 | report.setVersionFixedIn(getSimpleValue(QStringLiteral("cf_versionfixedin"))); | | |||
584 | | ||||
585 | //Parse full content + comments | | |||
586 | QStringList m_commentList; | | |||
587 | QDomNodeList comments = m_xml.elementsByTagName(QStringLiteral("long_desc")); | | |||
588 | for (int i = 0; i < comments.count(); i++) { | | |||
589 | QDomElement element = comments.at(i).firstChildElement(QStringLiteral("thetext")); | | |||
590 | m_commentList << element.text(); | | |||
591 | } | | |||
592 | | ||||
593 | report.setComments(m_commentList); | | |||
594 | | ||||
595 | } //isValid | | |||
596 | } //isValid | | |||
597 | | ||||
598 | return report; | | |||
599 | } | | |||
600 | | ||||
601 | QString BugReportXMLParser::getSimpleValue(const QString & name) //Extract an unique tag from XML | | |||
602 | { | | |||
603 | QString ret; | | |||
604 | | ||||
605 | QDomNodeList bug_number = m_xml.elementsByTagName(name); | | |||
606 | if (bug_number.count() == 1) { | | |||
607 | QDomNode node = bug_number.at(0); | | |||
608 | ret = node.toElement().text(); | | |||
609 | } | | |||
610 | return ret; | | |||
611 | } | | |||
612 | | ||||
613 | //END BugzillaXMLParser | | |||
614 | | ||||
615 | void BugReport::setBugStatus(const QString &stat) | | |||
616 | { | | |||
617 | setData(QStringLiteral("bug_status"), stat); | | |||
618 | | ||||
619 | m_status = parseStatus(stat); | | |||
620 | } | | |||
621 | | ||||
622 | void BugReport::setResolution(const QString &res) | | |||
623 | { | | |||
624 | setData(QStringLiteral("resolution"), res); | | |||
625 | | ||||
626 | m_resolution = parseResolution(res); | | |||
627 | } | | |||
628 | | ||||
629 | BugReport::Status BugReport::parseStatus(const QString &stat) | | |||
630 | { | | |||
631 | if (stat == QLatin1String("UNCONFIRMED")) { | | |||
632 | return Unconfirmed; | | |||
633 | } else if (stat == QLatin1String("CONFIRMED")) { | | |||
634 | return New; | | |||
635 | } else if (stat == QLatin1String("ASSIGNED")) { | | |||
636 | return Assigned; | | |||
637 | } else if (stat == QLatin1String("REOPENED")) { | | |||
638 | return Reopened; | | |||
639 | } else if (stat == QLatin1String("RESOLVED")) { | | |||
640 | return Resolved; | | |||
641 | } else if (stat == QLatin1String("NEEDSINFO")) { | | |||
642 | return NeedsInfo; | | |||
643 | } else if (stat == QLatin1String("VERIFIED")) { | | |||
644 | return Verified; | | |||
645 | } else if (stat == QLatin1String("CLOSED")) { | | |||
646 | return Closed; | | |||
647 | } else { | | |||
648 | return UnknownStatus; | | |||
649 | } | | |||
650 | } | | |||
651 | | ||||
652 | BugReport::Resolution BugReport::parseResolution(const QString &res) | | |||
653 | { | | |||
654 | if (res.isEmpty()) { | | |||
655 | return NotResolved; | | |||
656 | } else if (res == QLatin1String("FIXED")) { | | |||
657 | return Fixed; | | |||
658 | } else if (res == QLatin1String("INVALID")) { | | |||
659 | return Invalid; | | |||
660 | } else if (res == QLatin1String("WONTFIX")) { | | |||
661 | return WontFix; | | |||
662 | } else if (res == QLatin1String("LATER")) { | | |||
663 | return Later; | | |||
664 | } else if (res == QLatin1String("REMIND")) { | | |||
665 | return Remind; | | |||
666 | } else if (res == QLatin1String("DUPLICATE")) { | | |||
667 | return Duplicate; | | |||
668 | } else if (res == QLatin1String("WORKSFORME")) { | | |||
669 | return WorksForMe; | | |||
670 | } else if (res == QLatin1String("MOVED")) { | | |||
671 | return Moved; | | |||
672 | } else if (res == QLatin1String("UPSTREAM")) { | | |||
673 | return Upstream; | | |||
674 | } else if (res == QLatin1String("DOWNSTREAM")) { | | |||
675 | return Downstream; | | |||
676 | } else if (res == QLatin1String("WAITINGFORINFO")) { | | |||
677 | return WaitingForInfo; | | |||
678 | } else if (res == QLatin1String("BACKTRACE")) { | | |||
679 | return Backtrace; | | |||
680 | } else if (res == QLatin1String("UNMAINTAINED")) { | | |||
681 | return Unmaintained; | | |||
682 | } else { | | |||
683 | return UnknownResolution; | | |||
684 | } | | |||
685 | } | |