diff --git a/src/backtracewidget.cpp b/src/backtracewidget.cpp index b09c5afe..3e8304cc 100644 --- a/src/backtracewidget.cpp +++ b/src/backtracewidget.cpp @@ -1,409 +1,409 @@ /******************************************************************* * backtracewidget.cpp * Copyright 2009,2010 Dario Andres Rodriguez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #include "backtracewidget.h" #include #include #include #include #include #include "drkonqi.h" #include "backtraceratingwidget.h" #include "crashedapplication.h" #include "backtracegenerator.h" #include "parser/backtraceparser.h" #include "drkonqi_globals.h" #include "debuggermanager.h" #include "gdbhighlighter.h" static const char extraDetailsLabelMargin[] = " margin: 5px; "; BacktraceWidget::BacktraceWidget(BacktraceGenerator *generator, QWidget *parent, bool showToggleBacktrace) : QWidget(parent), m_btGenerator(generator), m_highlighter(nullptr) { ui.setupUi(this); //Debug package installer m_debugPackageInstaller = new DebugPackageInstaller(this); connect(m_debugPackageInstaller, &DebugPackageInstaller::error, this, &BacktraceWidget::debugPackageError); connect(m_debugPackageInstaller, &DebugPackageInstaller::packagesInstalled, this, &BacktraceWidget::regenerateBacktrace); connect(m_debugPackageInstaller, &DebugPackageInstaller::canceled, this, &BacktraceWidget::debugPackageCanceled); connect(m_btGenerator, &BacktraceGenerator::starting, this, &BacktraceWidget::setAsLoading); connect(m_btGenerator, &BacktraceGenerator::done, this, &BacktraceWidget::loadData); connect(m_btGenerator, &BacktraceGenerator::someError, this, &BacktraceWidget::loadData); connect(m_btGenerator, &BacktraceGenerator::failedToStart, this, &BacktraceWidget::loadData); connect(m_btGenerator, &BacktraceGenerator::newLine, this, &BacktraceWidget::backtraceNewLine); connect(ui.m_extraDetailsLabel, &QLabel::linkActivated, this, &BacktraceWidget::extraDetailsLinkActivated); ui.m_extraDetailsLabel->setVisible(false); ui.m_extraDetailsLabel->setStyleSheet(QLatin1String(extraDetailsLabelMargin)); //Setup the buttons KGuiItem::assign(ui.m_reloadBacktraceButton, KGuiItem2(i18nc("@action:button", "&Reload"), QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@info:tooltip", "Use this button to " "reload the crash information (backtrace). This is useful when you have " "installed the proper debug symbol packages and you want to obtain " "a better backtrace."))); connect(ui.m_reloadBacktraceButton, &QPushButton::clicked, this, &BacktraceWidget::regenerateBacktrace); KGuiItem::assign(ui.m_installDebugButton, KGuiItem2(i18nc("@action:button", "&Install Debug Symbols"), QIcon::fromTheme(QStringLiteral("system-software-update")), i18nc("@info:tooltip", "Use this button to " "install the missing debug symbols packages."))); ui.m_installDebugButton->setVisible(false); connect(ui.m_installDebugButton, &QPushButton::clicked, this, &BacktraceWidget::installDebugPackages); KGuiItem::assign(ui.m_copyButton, KGuiItem2(QString(), QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@info:tooltip", "Use this button to copy the " "crash information (backtrace) to the clipboard."))); connect(ui.m_copyButton, &QPushButton::clicked, this, &BacktraceWidget::copyClicked); ui.m_copyButton->setEnabled(false); KGuiItem::assign(ui.m_saveButton, KGuiItem2(QString(), QIcon::fromTheme(QStringLiteral("document-save")), i18nc("@info:tooltip", "Use this button to save the " "crash information (backtrace) to a file. This is useful " "if you want to take a look at it or to report the bug " "later."))); connect(ui.m_saveButton, &QPushButton::clicked, this, &BacktraceWidget::saveClicked); ui.m_saveButton->setEnabled(false); //Create the rating widget m_backtraceRatingWidget = new BacktraceRatingWidget(ui.m_statusWidget); ui.m_statusWidget->addCustomStatusWidget(m_backtraceRatingWidget); ui.m_statusWidget->setIdle(QString()); //Do we need the "Show backtrace" toggle action ? if (!showToggleBacktrace) { ui.mainLayout->removeWidget(ui.m_toggleBacktraceCheckBox); ui.m_toggleBacktraceCheckBox->setVisible(false); toggleBacktrace(true); } else { //Generate help widget ui.m_backtraceHelpLabel->setText( i18n("

What is a \"backtrace\" ?

A backtrace basically describes what was " "happening inside the application when it crashed, so the developers may track " "down where the mess started. They may look meaningless to you, but they might " "actually contain a wealth of useful information.
Backtraces are commonly " "used during interactive and post-mortem debugging.

")); ui.m_backtraceHelpIcon->setPixmap(QIcon::fromTheme(QStringLiteral("help-hint")).pixmap(48,48)); connect(ui.m_toggleBacktraceCheckBox, &QCheckBox::toggled, this, &BacktraceWidget::toggleBacktrace); toggleBacktrace(false); } ui.m_backtraceEdit->setFont( QFontDatabase::systemFont(QFontDatabase::FixedFont) ); } void BacktraceWidget::setAsLoading() { //remove the syntax highlighter delete m_highlighter; m_highlighter = nullptr; //Set the widget as loading and disable all the action buttons ui.m_backtraceEdit->setText(i18nc("@info:status", "Loading...")); ui.m_backtraceEdit->setEnabled(false); ui.m_statusWidget->setBusy(i18nc("@info:status", "Generating backtrace... (this may take some time)")); m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless); m_backtraceRatingWidget->setState(BacktraceGenerator::Loading); ui.m_extraDetailsLabel->setVisible(false); ui.m_extraDetailsLabel->clear(); ui.m_installDebugButton->setVisible(false); ui.m_reloadBacktraceButton->setEnabled(false); ui.m_copyButton->setEnabled(false); ui.m_saveButton->setEnabled(false); } //Force backtrace generation void BacktraceWidget::regenerateBacktrace() { setAsLoading(); if (!DrKonqi::debuggerManager()->debuggerIsRunning()) { m_btGenerator->start(); } else { anotherDebuggerRunning(); } emit stateChanged(); } void BacktraceWidget::generateBacktrace() { if (m_btGenerator->state() == BacktraceGenerator::NotLoaded) { //First backtrace generation regenerateBacktrace(); } else if (m_btGenerator->state() == BacktraceGenerator::Loading) { //Set in loading state, the widget will catch the backtrace events anyway setAsLoading(); emit stateChanged(); } else { //*Finished* states setAsLoading(); emit stateChanged(); //Load already generated information loadData(); } } void BacktraceWidget::anotherDebuggerRunning() { //As another debugger is running, we should disable the actions and notify the user ui.m_backtraceEdit->setEnabled(false); ui.m_backtraceEdit->setText(i18nc("@info", "Another debugger is currently debugging the " "same application. The crash information could not be fetched.")); m_backtraceRatingWidget->setState(BacktraceGenerator::Failed); m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless); ui.m_statusWidget->setIdle(i18nc("@info:status", "The crash information could not be fetched.")); ui.m_extraDetailsLabel->setVisible(true); ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "Another debugging process is attached to " "the crashed application. Therefore, the DrKonqi debugger cannot " "fetch the backtrace. Please close the other debugger and " "click Reload.")); ui.m_installDebugButton->setVisible(false); ui.m_reloadBacktraceButton->setEnabled(true); } void BacktraceWidget::loadData() { //Load the backtrace data from the generator m_backtraceRatingWidget->setState(m_btGenerator->state()); if (m_btGenerator->state() == BacktraceGenerator::Loaded) { ui.m_backtraceEdit->setEnabled(true); ui.m_backtraceEdit->setPlainText(m_btGenerator->backtrace()); // scroll to crash QTextCursor crashCursor = ui.m_backtraceEdit->document()->find(QStringLiteral("[KCrash Handler]")); if (crashCursor.isNull()) { crashCursor = ui.m_backtraceEdit->document()->find(QStringLiteral("KCrash::defaultCrashHandler")); } if (!crashCursor.isNull()) { crashCursor.movePosition(QTextCursor::Up, QTextCursor::MoveAnchor); ui.m_backtraceEdit->verticalScrollBar()->setValue(ui.m_backtraceEdit->cursorRect(crashCursor).top()); } // highlight if possible if (m_btGenerator->debugger().codeName() == QLatin1String("gdb")) { m_highlighter = new GdbHighlighter(ui.m_backtraceEdit->document(), m_btGenerator->parser()->parsedBacktraceLines()); } BacktraceParser * btParser = m_btGenerator->parser(); m_backtraceRatingWidget->setUsefulness(btParser->backtraceUsefulness()); //Generate the text to put in the status widget (backtrace usefulness) QString usefulnessText; switch (btParser->backtraceUsefulness()) { case BacktraceParser::ReallyUseful: usefulnessText = i18nc("@info", "The generated crash information is useful"); break; case BacktraceParser::MayBeUseful: usefulnessText = i18nc("@info", "The generated crash information may be useful"); break; case BacktraceParser::ProbablyUseless: usefulnessText = i18nc("@info", "The generated crash information is probably not useful"); break; case BacktraceParser::Useless: usefulnessText = i18nc("@info", "The generated crash information is not useful"); break; default: //let's hope nobody will ever see this... ;) usefulnessText = i18nc("@info", "The rating of this crash information is invalid. " "This is a bug in DrKonqi itself."); break; } ui.m_statusWidget->setIdle(usefulnessText); if (btParser->backtraceUsefulness() != BacktraceParser::ReallyUseful) { //Not a perfect bactrace, ask the user to try to improve it ui.m_extraDetailsLabel->setVisible(true); if (canInstallDebugPackages()) { //The script to install the debug packages is available ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "You can click the " "Install Debug Symbols button in order to automatically " "install the missing debugging information packages. If this method " "does not work: please read How to " "create useful crash reports to learn how to get a useful " "backtrace; install the needed packages (" "list of files) and click the " "Reload button.", QLatin1String(TECHBASE_HOWTO_DOC), QLatin1String("#missingDebugPackages"))); ui.m_installDebugButton->setVisible(true); //Retrieve the libraries with missing debug info QStringList missingLibraries = btParser->librariesWithMissingDebugSymbols().toList(); m_debugPackageInstaller->setMissingLibraries(missingLibraries); } else { //No automated method to install the missing debug info //Tell the user to read the howto and reload ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "Please read How to " "create useful crash reports to learn how to get a useful " "backtrace; install the needed packages (" "list of files) and click the " "Reload button.", QLatin1String(TECHBASE_HOWTO_DOC), QLatin1String("#missingDebugPackages"))); } } ui.m_copyButton->setEnabled(true); ui.m_saveButton->setEnabled(true); } else if (m_btGenerator->state() == BacktraceGenerator::Failed) { //The backtrace could not be generated because the debugger had an error m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless); ui.m_statusWidget->setIdle(i18nc("@info:status", "The debugger has quit unexpectedly.")); ui.m_backtraceEdit->setPlainText(i18nc("@info:status", "The crash information could not be generated.")); ui.m_extraDetailsLabel->setVisible(true); ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "You could try to regenerate the " "backtrace by clicking the Reload" " button.")); } else if (m_btGenerator->state() == BacktraceGenerator::FailedToStart) { //The backtrace could not be generated because the debugger could not start (missing) //Tell the user to install it. m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless); ui.m_statusWidget->setIdle(i18nc("@info:status", "The debugger application is missing or " "could not be launched.")); ui.m_backtraceEdit->setPlainText(i18nc("@info:status", "The crash information could not be generated.")); ui.m_extraDetailsLabel->setVisible(true); ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "You need to first install the debugger " "application (%1) then click the Reload" " button.", m_btGenerator->debugger().displayName())); } ui.m_reloadBacktraceButton->setEnabled(true); emit stateChanged(); } void BacktraceWidget::backtraceNewLine(const QString & line) { // We absolutely must not have a highlighter attached. The highlighter has // a static list of lines to highlight from. When we are loading lines // this static list does not match reality breaking text length expectations // and resulting in segfaults. Q_ASSERT(!m_highlighter); //While loading the backtrace (unparsed) a new line was sent from the debugger, append it ui.m_backtraceEdit->append(line.trimmed()); } void BacktraceWidget::copyClicked() { ui.m_backtraceEdit->selectAll(); ui.m_backtraceEdit->copy(); } void BacktraceWidget::saveClicked() { DrKonqi::saveReport(m_btGenerator->backtrace(), this); } void BacktraceWidget::hilightExtraDetailsLabel(bool hilight) { const QString stylesheet = ((!hilight) ? QLatin1String("border-width: 0px;") : QLatin1String("border-width: 2px; " "border-style: solid; " "border-color: red;")) + QLatin1String(extraDetailsLabelMargin); ui.m_extraDetailsLabel->setStyleSheet(stylesheet); } void BacktraceWidget::focusImproveBacktraceButton() { ui.m_installDebugButton->setFocus(); } void BacktraceWidget::installDebugPackages() { ui.m_installDebugButton->setVisible(false); m_debugPackageInstaller->installDebugPackages(); } void BacktraceWidget::debugPackageError(const QString & errorMessage) { ui.m_installDebugButton->setVisible(true); KMessageBox::error(this, errorMessage, i18nc("@title:window", "Error during the installation of" " debug symbols")); } void BacktraceWidget::debugPackageCanceled() { ui.m_installDebugButton->setVisible(true); } bool BacktraceWidget::canInstallDebugPackages() const { return m_debugPackageInstaller->canInstallDebugPackages(); } void BacktraceWidget::toggleBacktrace(bool show) { ui.m_backtraceStack->setCurrentWidget(show ? ui.backtracePage : ui.backtraceHelpPage); } void BacktraceWidget::extraDetailsLinkActivated(QString link) { if (link.startsWith(QLatin1String("http"))) { //Open externally QDesktopServices::openUrl(QUrl(link)); } else if (link == QLatin1String("#missingDebugPackages")) { BacktraceParser * btParser = m_btGenerator->parser(); QStringList missingDbgForFiles = btParser->librariesWithMissingDebugSymbols().toList(); missingDbgForFiles.insert(0, DrKonqi::crashedApplication()->executable().absoluteFilePath()); //HTML message QString message = QStringLiteral("") + i18n("The packages containing debug information for the following application and libraries are missing:") + QStringLiteral("
    "); Q_FOREACH(const QString & string, missingDbgForFiles) { - message += QStringLiteral("
  • ") + string + QStringLiteral("
  • "); + message += QLatin1String("
  • ") + string + QLatin1String("
  • "); } message += QStringLiteral("
"); KMessageBox::information(this, message, i18nc("messagebox title","Missing debug information packages")); } } diff --git a/src/bugzillaintegration/bugzillalib.cpp b/src/bugzillaintegration/bugzillalib.cpp index f31b82e0..b8d216d2 100644 --- a/src/bugzillaintegration/bugzillalib.cpp +++ b/src/bugzillaintegration/bugzillalib.cpp @@ -1,366 +1,366 @@ /******************************************************************* * bugzillalib.cpp * Copyright 2009, 2011 Dario Andres Rodriguez * Copyright 2012 George Kiagiadakis * Copyright 2019 Harald Sitter * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #include "bugzillalib.h" #include #include "libbugzilla/clients/commentclient.h" #include "libbugzilla/connection.h" #include "libbugzilla/bugzilla.h" #include "drkonqi_debug.h" static const char showBugUrl[] = "show_bug.cgi?id=%1"; // Extra filter rigging. We don't want to leak secrets via qdebug, so install // a message handler which does nothing more than replace secrets in debug // messages with placeholders. // This is used as a global static (since message handlers are meant to be // static) and is slightly synchronizing across threads WRT the filter hash. struct QMessageFilterContainer { QMessageFilterContainer(); ~QMessageFilterContainer(); void insert(const QString &needle, const QString &replace); void clear(); QString filter(const QString &msg); // Message handler is called across threads. Synchronize for good measure. QReadWriteLock lock; QtMessageHandler handler; private: QHash filters; }; Q_GLOBAL_STATIC(QMessageFilterContainer, s_messageFilter) QMessageFilterContainer::QMessageFilterContainer() { handler = qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &context, const QString &msg) { s_messageFilter->handler(type, context, s_messageFilter->filter(msg)); }); } QMessageFilterContainer::~QMessageFilterContainer() { qInstallMessageHandler(handler); } void QMessageFilterContainer::insert(const QString &needle, const QString &replace) { if (needle.isEmpty()) { return; } QWriteLocker locker(&lock); filters[needle] = replace; } QString QMessageFilterContainer::filter(const QString &msg) { QReadLocker locker(&lock); QString filteredMsg = msg; for (auto it = filters.constBegin(); it != filters.constEnd(); ++it) { filteredMsg.replace(it.key(), it.value()); } return filteredMsg; } void QMessageFilterContainer::clear() { QWriteLocker locker(&lock); filters.clear(); } BugzillaManager::BugzillaManager(const QString &bugTrackerUrl, QObject *parent) : QObject(parent) , m_bugTrackerUrl(bugTrackerUrl) , m_logged(false) , m_searchJob(nullptr) { Q_ASSERT(bugTrackerUrl.endsWith(QLatin1Char('/'))); Bugzilla::setConnection(new Bugzilla::HTTPConnection(QUrl(m_bugTrackerUrl + QStringLiteral("rest")))); } void BugzillaManager::lookupVersion() { KJob *job = Bugzilla::version(); connect(job, &KJob::finished, this, [this](KJob *job) { try { QString version = Bugzilla::version(job); setFeaturesForVersion(version); emit bugzillaVersionFound(); } catch (Bugzilla::Exception &e) { // Version detection problems simply mean we'll not mark the version // found and the UI will not allow reporting. qCWarning(DRKONQI_LOG) << e.whatString(); emit bugzillaVersionError(e.whatString()); } }); } void BugzillaManager::setFeaturesForVersion(const QString& version) { // A procedure to change Dr Konqi behaviour automatically when Bugzilla // software versions change. // // Changes should be added to Dr Konqi AHEAD of when the corresponding // Bugzilla software changes are released into bugs.kde.org, so that // Dr Konqi can continue to operate smoothly, without bug reports and a // reactive KDE software release. // // If Bugzilla announces a change to its software that affects Dr Konqi, // add executable code to implement the change automatically when the // Bugzilla software version changes. It goes at the end of this procedure // and elsewhere in this class (BugzillaManager) and/or other classes where // the change should actually be implemented. const int nVersionParts = 3; QString seps = QLatin1String("[._-]"); QStringList digits = version.split(QRegExp(seps), QString::SkipEmptyParts); while (digits.count() < nVersionParts) { digits << QLatin1String("0"); } if (digits.count() > nVersionParts) { qCWarning(DRKONQI_LOG) << QStringLiteral("Current Bugzilla version %1 has more than %2 parts. Check that this is not a problem.").arg(version).arg(nVersionParts); } qCDebug(DRKONQI_LOG) << "VERSION" << version; } void BugzillaManager::tryLogin(const QString &username, const QString &password) { m_username = username; m_password = password; refreshToken(); } void BugzillaManager::refreshToken() { Q_ASSERT(!m_username.isEmpty()); Q_ASSERT(!m_password.isEmpty()); m_logged = false; // Rest token and qdebug filters Bugzilla::connection().setToken(QString()); s_messageFilter->clear(); s_messageFilter->insert(m_password, QStringLiteral("PASSWORD")); KJob *job = Bugzilla::login(m_username, m_password); connect(job, &KJob::finished, this, [this](KJob *job) { try { auto details = Bugzilla::login(job); m_token = details.token; if (m_token.isEmpty()) { throw Bugzilla::RuntimeException(QStringLiteral("Did not receive a token")); } s_messageFilter->insert(m_token, QStringLiteral("TOKEN")); Bugzilla::connection().setToken(m_token); m_logged = true; emit loginFinished(true); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); // Version detection problems simply mean we'll not mark the version // found and the UI will not allow reporting. emit loginError(e.whatString()); } }); } bool BugzillaManager::getLogged() const { return m_logged; } QString BugzillaManager::getUsername() const { return m_username; } void BugzillaManager::fetchBugReport(int bugnumber, QObject *jobOwner) { Bugzilla::BugSearch search; search.id = bugnumber; Bugzilla::BugClient client; auto job = m_searchJob = client.search(search); connect(job, &KJob::finished, this, [this, &client, jobOwner](KJob *job) { try { auto list = client.search(job); if (list.size() != 1) { throw Bugzilla::RuntimeException(QStringLiteral("Unexpected bug amount returned: %1").arg(list.size())); } auto bug = list.at(0); m_searchJob = nullptr; emit bugReportFetched(bug, jobOwner); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); emit bugReportError(e.whatString(), jobOwner); } }); } void BugzillaManager::fetchComments(const Bugzilla::Bug::Ptr &bug, QObject *jobOwner) { Bugzilla::CommentClient client; auto job = client.getFromBug(bug->id()); connect(job, &KJob::finished, this, [this, &client, jobOwner](KJob *job) { try { auto comments = client.getFromBug(job); emit commentsFetched(comments, jobOwner); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); emit commentsError(e.whatString(), jobOwner); } }); } // TODO: This would kinda benefit from an actual pagination class, // currently this implicitly relies on the caller to handle offests correctly. // Fortunately we only have one caller so it makes no difference. void BugzillaManager::searchBugs(const QStringList &products, const QString &severity, const QString &comment, int offset) { Bugzilla::BugSearch search; search.products = products; search.severity = severity; search.longdesc = comment; // Order descendingly by bug_id. This allows us to offset through the results // from newest to oldest. // The UI will later order our data anyway, so the order at which we receive // the data is not important for the UI (outside the fact that we want // to step through pages of data) search.order << QStringLiteral("bug_id DESC"); search.limit = 25; search.offset = offset; stopCurrentSearch(); Bugzilla::BugClient client; auto job = m_searchJob = Bugzilla::BugClient().search(search); connect(job, &KJob::finished, this, [this, &client](KJob *job) { try { auto list = client.search(job); m_searchJob = nullptr; emit searchFinished(list); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); emit searchError(e.whatString()); } }); } void BugzillaManager::sendReport(const Bugzilla::NewBug &bug) { auto job = Bugzilla::BugClient().create(bug); connect(job, &KJob::finished, this, [this](KJob *job) { try { int id = Bugzilla::BugClient().create(job); Q_ASSERT(id > 0); emit reportSent(id); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); emit sendReportError(e.whatString()); } }); } void BugzillaManager::attachTextToReport(const QString & text, const QString & filename, const QString & summary, int bugId, const QString & comment) { Bugzilla::NewAttachment attachment; attachment.ids = QList { bugId }; attachment.data = text; attachment.file_name = filename; attachment.summary = summary; attachment.comment = comment; - attachment.content_type = QLatin1Literal("text/plain"); + attachment.content_type = QLatin1String("text/plain"); auto job = Bugzilla::AttachmentClient().createAttachment(bugId, attachment); connect(job, &KJob::finished, this, [this](KJob *job) { try { QList ids = Bugzilla::AttachmentClient().createAttachment(job); Q_ASSERT(ids.size() == 1); emit attachToReportSent(ids.at(0)); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); emit attachToReportError(e.whatString()); } }); } void BugzillaManager::addMeToCC(int bugId) { Bugzilla::BugUpdate update; Q_ASSERT(!m_username.isEmpty()); update.cc->add << m_username; auto job = Bugzilla::BugClient().update(bugId, update); connect(job, &KJob::finished, this, [this](KJob *job) { try { const auto bugId = Bugzilla::BugClient().update(job); Q_ASSERT(bugId != 0); emit addMeToCCFinished(bugId); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); emit addMeToCCError(e.whatString()); } }); } void BugzillaManager::fetchProductInfo(const QString &product) { auto job = Bugzilla::ProductClient().get(product); connect(job, &KJob::finished, this, [this](KJob *job) { try { auto ptr = Bugzilla::ProductClient().get(job); Q_ASSERT(ptr); productInfoFetched(ptr); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); // This doesn't have a string because it is actually not used for // anything... emit productInfoError(); } }); } QString BugzillaManager::urlForBug(int bug_number) const { return QString(m_bugTrackerUrl) + QString::fromLatin1(showBugUrl).arg(bug_number); } void BugzillaManager::stopCurrentSearch() { if (m_searchJob) { //Stop previous searchJob m_searchJob->disconnect(); m_searchJob->kill(); m_searchJob = nullptr; } } diff --git a/src/bugzillaintegration/libbugzilla/clients/commands/jsoncommand.cpp b/src/bugzillaintegration/libbugzilla/clients/commands/jsoncommand.cpp index 00632952..43ac4a7b 100644 --- a/src/bugzillaintegration/libbugzilla/clients/commands/jsoncommand.cpp +++ b/src/bugzillaintegration/libbugzilla/clients/commands/jsoncommand.cpp @@ -1,69 +1,69 @@ /* Copyright 2019 Harald Sitter This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "jsoncommand.h" #include #include #include #include namespace Bugzilla { QByteArray JsonCommand::toJson() const { QJsonDocument doc; doc.setObject(QJsonObject::fromVariantHash(toVariantHash())); return doc.toJson(); } QVariantHash JsonCommand::toVariantHash() const { QVariantHash hash; const auto propertyCount = metaObject()->propertyCount(); for (int i = 0; i < propertyCount; ++i) { const auto property = metaObject()->property(i); const auto name = QString::fromLatin1(property.name()); const auto value = property.read(this); - if (name == QStringLiteral("objectName")) { // Builtin property. + if (name == QLatin1String("objectName")) { // Builtin property. continue; } if (value.isNull()) { continue; } // If this is a nested representation, serialize it and glue it in. if (value.canConvert()) { JsonCommand *repValue = value.value(); hash.insert(name, repValue->toVariantHash()); continue; } hash.insert(name, value); } return hash; } } // namespace Bugzilla diff --git a/src/bugzillaintegration/libbugzilla/clients/commands/querycommand.cpp b/src/bugzillaintegration/libbugzilla/clients/commands/querycommand.cpp index 03dd7026..be446432 100644 --- a/src/bugzillaintegration/libbugzilla/clients/commands/querycommand.cpp +++ b/src/bugzillaintegration/libbugzilla/clients/commands/querycommand.cpp @@ -1,69 +1,69 @@ /* Copyright 2019 Harald Sitter This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "querycommand.h" #include #include namespace Bugzilla { QUrlQuery QueryCommand::toQuery() const { QUrlQuery query; return expandQuery(query, QSet()); } QUrlQuery QueryCommand::expandQuery(QUrlQuery &query, const QSet &seen) const { const auto propertyCount = metaObject()->propertyCount(); for (int i = 0; i < propertyCount; ++i) { const auto property = metaObject()->property(i); const auto name = QString::fromLatin1(property.name()); const auto value = property.read(this); - if (query.hasQueryItem(name) || seen.contains(name) || name == QLatin1Literal("objectName")) { + if (query.hasQueryItem(name) || seen.contains(name) || name == QLatin1String("objectName")) { // The element was manually set or builtin property. continue; } if (value.toLongLong() < 0) { // Invalid value => member was not set! // This does generally also work for all integers, ulonglong of // course being the only one that can cause trouble. continue; } // Lists must be serialized manually. They could have a number of representations. Q_ASSERT_X(value.type() != QVariant::StringList, Q_FUNC_INFO, qPrintable(QStringLiteral("Trying to auto serialize string list %1").arg(name))); // Either can't serialize or not set. if (value.toString().isEmpty()) { continue; } query.addQueryItem(name, property.read(this).toString()); } return query; } } // namespace Bugzilla diff --git a/src/bugzillaintegration/reportassistantpages_bugzilla.cpp b/src/bugzillaintegration/reportassistantpages_bugzilla.cpp index f3dd93b8..5cd89226 100644 --- a/src/bugzillaintegration/reportassistantpages_bugzilla.cpp +++ b/src/bugzillaintegration/reportassistantpages_bugzilla.cpp @@ -1,771 +1,771 @@ /******************************************************************* * reportassistantpages_bugzilla.cpp * Copyright 2009, 2010, 2011 Dario Andres Rodriguez * Copyright 2019 Harald Sitter * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #include "reportassistantpages_bugzilla.h" #include #include #include #include #include #include #include #include #include #include #include "drkonqi_debug.h" #include #include #include #include /* Unhandled error dialog includes */ #include #include #include #include #include "reportinterface.h" #include "systeminformation.h" #include "crashedapplication.h" #include "bugzillalib.h" #include "statuswidget.h" #include "drkonqi.h" #include "drkonqi_globals.h" #include "applicationdetailsexamples.h" static const char kWalletEntryName[] = "drkonqi_bugzilla"; static const char kWalletEntryUsername[] = "username"; static const char kWalletEntryPassword[] = "password"; static QString konquerorKWalletEntryName = KDE_BUGZILLA_URL + QStringLiteral("index.cgi#login"); static const char konquerorKWalletEntryUsername[] = "Bugzilla_login"; static const char konquerorKWalletEntryPassword[] = "Bugzilla_password"; //BEGIN BugzillaLoginPage BugzillaLoginPage::BugzillaLoginPage(ReportAssistantDialog * parent) : ReportAssistantPage(parent), m_wallet(nullptr), m_walletWasOpenedBefore(false) { connect(bugzillaManager(), &BugzillaManager::loginFinished, this, &BugzillaLoginPage::loginFinished); connect(bugzillaManager(), &BugzillaManager::loginError, this, &BugzillaLoginPage::loginError); ui.setupUi(this); ui.m_statusWidget->setIdle(i18nc("@info:status '1' is replaced with the short URL of the bugzilla ", "You need to login with your %1 account in order to proceed.", QLatin1String(KDE_BUGZILLA_SHORT_URL))); KGuiItem::assign(ui.m_loginButton, KGuiItem2(i18nc("@action:button", "Login"), QIcon::fromTheme(QStringLiteral("network-connect")), i18nc("@info:tooltip", "Use this button to login " "to the KDE bug tracking system using the provided " "e-mail address and password."))); ui.m_loginButton->setEnabled(false); connect(ui.m_loginButton, &QPushButton::clicked, this, &BugzillaLoginPage::loginClicked); connect(ui.m_userEdit, &KLineEdit::returnPressed, this, &BugzillaLoginPage::loginClicked); connect(ui.m_passwordEdit->lineEdit(), &QLineEdit::returnPressed, this, &BugzillaLoginPage::loginClicked); connect(ui.m_userEdit, &KLineEdit::textChanged, this, &BugzillaLoginPage::updateLoginButtonStatus); connect(ui.m_passwordEdit, &KPasswordLineEdit::passwordChanged, this, &BugzillaLoginPage::updateLoginButtonStatus); ui.m_noticeLabel->setText( xi18nc("@info/rich","You need a user account on the " "KDE bug tracking system in order to " "file a bug report, because we may need to contact you later " "for requesting further information. If you do not have " "one, you can freely create one here. " "Please do not use disposable email accounts.", DrKonqi::crashedApplication()->bugReportAddress(), KDE_BUGZILLA_CREATE_ACCOUNT_URL)); // Don't advertise saving credentials when we can't save them. // https://bugs.kde.org/show_bug.cgi?id=363570 if (!KWallet::Wallet::isEnabled()) { ui.m_savePasswordCheckBox->setVisible(false); ui.m_savePasswordCheckBox->setCheckState(Qt::Unchecked); } } bool BugzillaLoginPage::isComplete() { return bugzillaManager()->getLogged(); } void BugzillaLoginPage::updateLoginButtonStatus() { ui.m_loginButton->setEnabled(canLogin()); } void BugzillaLoginPage::loginError(const QString &error) { loginFinished(false); ui.m_statusWidget->setIdle(xi18nc("@info:status","Error when trying to login: " "%1", error)); } void BugzillaLoginPage::aboutToShow() { if (bugzillaManager()->getLogged()) { ui.m_loginButton->setEnabled(false); ui.m_userEdit->setEnabled(false); ui.m_userEdit->clear(); ui.m_passwordEdit->setEnabled(false); ui.m_passwordEdit->clear(); ui.m_loginButton->setVisible(false); ui.m_userEdit->setVisible(false); ui.m_passwordEdit->setVisible(false); ui.m_userLabel->setVisible(false); ui.m_passwordLabel->setVisible(false); ui.m_savePasswordCheckBox->setVisible(false); ui.m_noticeLabel->setVisible(false); ui.m_statusWidget->setIdle(i18nc("@info:status the user is logged at the bugtracker site " "as USERNAME", "Logged in at the KDE bug tracking system (%1) as: %2.", QLatin1String(KDE_BUGZILLA_SHORT_URL), bugzillaManager()->getUsername())); } else { //Try to show wallet dialog once this dialog is shown QTimer::singleShot(100, this, &BugzillaLoginPage::walletLogin); } } bool BugzillaLoginPage::kWalletEntryExists(const QString& entryName) { return !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::FormDataFolder(), entryName); } void BugzillaLoginPage::openWallet() { //Store if the wallet was previously opened so we can know if we should close it later m_walletWasOpenedBefore = KWallet::Wallet::isOpen(KWallet::Wallet::NetworkWallet()); //Request open the wallet m_wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), static_cast(this->parent())->winId()); } void BugzillaLoginPage::walletLogin() { if (!m_wallet) { if (kWalletEntryExists(QLatin1String(kWalletEntryName))) { //Key exists! openWallet(); ui.m_savePasswordCheckBox->setCheckState(Qt::Checked); //Was the wallet opened? if (m_wallet) { m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); //Use wallet data to try login QMap values; m_wallet->readMap(QLatin1String(kWalletEntryName), values); QString username = values.value(QLatin1String(kWalletEntryUsername)); QString password = values.value(QLatin1String(kWalletEntryPassword)); if (!username.isEmpty() && !password.isEmpty()) { ui.m_userEdit->setText(username); ui.m_passwordEdit->setPassword(password); } } } else if (kWalletEntryExists(konquerorKWalletEntryName)) { //If the DrKonqi entry is empty, but a Konqueror entry exists, use and copy it. openWallet(); if (m_wallet) { m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); //Fetch Konqueror data QMap values; m_wallet->readMap(konquerorKWalletEntryName, values); QString username = values.value(QLatin1String(konquerorKWalletEntryUsername)); QString password = values.value(QLatin1String(konquerorKWalletEntryPassword)); if (!username.isEmpty() && !password.isEmpty()) { //Copy to DrKonqi own entries values.clear(); values.insert(QLatin1String(kWalletEntryUsername), username); values.insert(QLatin1String(kWalletEntryPassword), password); m_wallet->writeMap(QLatin1String(kWalletEntryName), values); ui.m_savePasswordCheckBox->setCheckState(Qt::Checked); ui.m_userEdit->setText(username); ui.m_passwordEdit->setPassword(password); } } } if (canLogin()) { loginClicked(); } } } void BugzillaLoginPage::loginClicked() { if (!canLogin()) { loginFinished(false); return; } updateWidget(false); if (ui.m_savePasswordCheckBox->checkState()==Qt::Checked) { //Wants to save data if (!m_wallet) { openWallet(); } //Got wallet open ? if (m_wallet) { m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); QMap values; values.insert(QLatin1String(kWalletEntryUsername), ui.m_userEdit->text()); values.insert(QLatin1String(kWalletEntryPassword), ui.m_passwordEdit->password()); m_wallet->writeMap(QLatin1String(kWalletEntryName), values); } } else { //User doesn't want to save or wants to remove. if (kWalletEntryExists(QLatin1String(kWalletEntryName))) { if (!m_wallet) { openWallet(); } //Got wallet open ? if (m_wallet) { m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); m_wallet->removeEntry(QLatin1String(kWalletEntryName)); } } } login(); } bool BugzillaLoginPage::canLogin() const { return (!(ui.m_userEdit->text().isEmpty() || ui.m_passwordEdit->password().isEmpty())); } void BugzillaLoginPage::login() { Q_ASSERT(canLogin()); ui.m_statusWidget->setBusy(i18nc("@info:status '1' is a url, '2' the e-mail address", "Performing login at %1 as %2...", QLatin1String(KDE_BUGZILLA_SHORT_URL), ui.m_userEdit->text())); bugzillaManager()->tryLogin(ui.m_userEdit->text(), ui.m_passwordEdit->password()); } void BugzillaLoginPage::updateWidget(bool enabled) { ui.m_loginButton->setEnabled(enabled); ui.m_userLabel->setEnabled(enabled); ui.m_passwordLabel->setEnabled(enabled); ui.m_userEdit->setEnabled(enabled); ui.m_passwordEdit->setEnabled(enabled); ui.m_savePasswordCheckBox->setEnabled(enabled); } void BugzillaLoginPage::loginFinished(bool logged) { if (logged) { emitCompleteChanged(); aboutToShow(); if (m_wallet) { if (m_wallet->isOpen() && !m_walletWasOpenedBefore) { m_wallet->lockWallet(); } } emit loggedTurnToNextPage(); } else { ui.m_statusWidget->setIdle(i18nc("@info:status", "Error: Invalid e-mail address or password")); updateWidget(true); ui.m_userEdit->setFocus(Qt::OtherFocusReason); } } BugzillaLoginPage::~BugzillaLoginPage() { //Close wallet if we close the assistant in this step if (m_wallet) { if (m_wallet->isOpen() && !m_walletWasOpenedBefore) { m_wallet->lockWallet(); } delete m_wallet; } } //END BugzillaLoginPage //BEGIN BugzillaInformationPage BugzillaInformationPage::BugzillaInformationPage(ReportAssistantDialog * parent) : ReportAssistantPage(parent) , m_textsOK(false) , m_requiredCharacters(1) { ui.setupUi(this); m_textCompleteBar = new KCapacityBar(KCapacityBar::DrawTextInline, this); ui.horizontalLayout_2->addWidget(m_textCompleteBar); connect(ui.m_titleEdit, &KLineEdit::textChanged, this, &BugzillaInformationPage::checkTexts); connect(ui.m_detailsEdit, &QTextEdit::textChanged, this, &BugzillaInformationPage::checkTexts); connect(ui.m_titleLabel, &QLabel::linkActivated, this, &BugzillaInformationPage::showTitleExamples); connect(ui.m_detailsLabel, &QLabel::linkActivated, this, &BugzillaInformationPage::showDescriptionHelpExamples); ui.m_compiledSourcesCheckBox->setChecked(DrKonqi::systemInformation()->compiledSources()); ui.m_messageWidget->setVisible(false); auto retryAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action/button retry button in error widget", "Retry"), ui.m_messageWidget); connect(retryAction, &QAction::triggered, this, [this, retryAction]{ ui.m_messageWidget->animatedHide(); loadDistroCombo(); }); ui.m_messageWidget->addAction(retryAction); loadDistroCombo(); } void BugzillaInformationPage::aboutToShow() { //Calculate the minimum number of characters required for a description //If creating a new report: minimum 40, maximum 80 //If attaching to an existent report: minimum 30, maximum 50 int multiplier = (reportInterface()->attachToBugNumber() == 0) ? 10 : 5; m_requiredCharacters = 20 + (reportInterface()->selectedOptionsRating() * multiplier); //Fill the description textedit with some headings: QString descriptionTemplate; if (ui.m_detailsEdit->toPlainText().isEmpty()) { if (reportInterface()->userCanProvideActionsAppDesktop()) { descriptionTemplate += QLatin1String("- What I was doing when the application crashed:\n\n"); } if (reportInterface()->userCanProvideUnusualBehavior()) { descriptionTemplate += QLatin1String("- Unusual behavior I noticed:\n\n"); } if (reportInterface()->userCanProvideApplicationConfigDetails()) { descriptionTemplate += QLatin1String("- Custom settings of the application:\n\n"); } ui.m_detailsEdit->setText(descriptionTemplate); } checkTexts(); //May be the options (canDetail) changed and we need to recheck } int BugzillaInformationPage::currentDescriptionCharactersCount() { QString description = ui.m_detailsEdit->toPlainText(); //Do not count template messages, and other misc chars description.remove(QStringLiteral("What I was doing when the application crashed")); description.remove(QStringLiteral("Unusual behavior I noticed")); description.remove(QStringLiteral("Custom settings of the application")); description.remove(QLatin1Char('\n')); description.remove(QLatin1Char('-')); description.remove(QLatin1Char(':')); description.remove(QLatin1Char(' ')); return description.size(); } void BugzillaInformationPage::checkTexts() { //If attaching this report to an existing one then the title is not needed bool showTitle = (reportInterface()->attachToBugNumber() == 0); ui.m_titleEdit->setVisible(showTitle); ui.m_titleLabel->setVisible(showTitle); bool ok = !((ui.m_titleEdit->isVisible() && ui.m_titleEdit->text().isEmpty()) || ui.m_detailsEdit->toPlainText().isEmpty()); QString message; int percent = currentDescriptionCharactersCount() * 100 / m_requiredCharacters; if (percent >= 100) { percent = 100; message = i18nc("the minimum required length of a text was reached", "Minimum length reached"); } else { message = i18nc("the minimum required length of a text wasn't reached yet", "Provide more information"); } m_textCompleteBar->setValue(percent); m_textCompleteBar->setText(message); if (ok != m_textsOK) { m_textsOK = ok; emitCompleteChanged(); } } void BugzillaInformationPage::loadDistroCombo() { // Alway have at least unspecified otherwise bugzilla would get to decide // the platform and that would likely be index=0 which is compiledfromsource ui.m_distroChooserCombo->addItem(QStringLiteral("unspecified")); Bugzilla::BugFieldClient client; auto job = client.getField(QStringLiteral("rep_platform")); connect(job, &KJob::finished, this, [this, &client](KJob *job) { try { Bugzilla::BugField::Ptr field = client.getField(job); if (!field) { // This is a bit flimsy but only acts as save guard. // Ideally this code path is never hit. throw Bugzilla::RuntimeException(i18nc("@info/status error", "Failed to get platform list")); } setDistros(field); } catch (Bugzilla::Exception &e) { qCWarning(DRKONQI_LOG) << e.whatString(); setDistroComboError(e.whatString()); } }); } void BugzillaInformationPage::setDistros(const Bugzilla::BugField::Ptr &field) { ui.m_distroChooserCombo->clear(); for (const auto &distro : field->values()) { ui.m_distroChooserCombo->addItem(distro->name()); } int index = 0; bool autoDetected = false; const QString detectedPlatform = DrKonqi::systemInformation()->bugzillaPlatform(); if (detectedPlatform != QLatin1String("unspecified")) { index = ui.m_distroChooserCombo->findText(detectedPlatform); if (index >= 0) { autoDetected = true; } } else { // Restore previously selected bugzilla platform (distribution) KConfigGroup config(KSharedConfig::openConfig(), "BugzillaInformationPage"); const QString entry = config.readEntry("BugzillaPlatform", "unspecified"); index = ui.m_distroChooserCombo->findText(entry); } if (index < 0) { // failed to restore value index = ui.m_distroChooserCombo->findText(QStringLiteral("unspecified")); } if (index < 0) { // also failed to find unspecified... shouldn't happen index = 0; } ui.m_distroChooserCombo->setCurrentIndex(index); ui.m_distroChooserCombo->setVisible(autoDetected); ui.m_distroChooserCombo->setDisabled(false); } void BugzillaInformationPage::setDistroComboError(const QString &error) { // NB: not being able to resolve the platform isn't a blocking issue. // You can still file a report, it'll simply default to unspecified. ui.m_messageWidget->setText(i18nc("@info error when talking to the bugzilla API", "An error occurred when talking to bugs.kde.org: %1", error)); ui.m_messageWidget->animatedShow(); ui.m_distroChooserCombo->setVisible(true); ui.m_distroChooserCombo->setDisabled(true); } bool BugzillaInformationPage::showNextPage() { checkTexts(); if (m_textsOK) { bool detailsShort = currentDescriptionCharactersCount() < m_requiredCharacters; if (detailsShort) { //The user input is less than we want.... encourage to write more QString message = i18nc("@info","The description about the crash details does not provide " "enough information yet.

"); message += QLatin1Char(' ') + i18nc("@info","The amount of required information is proportional to " "the quality of the other information like the backtrace " "or the reproducibility rate." "

"); if (reportInterface()->userCanProvideActionsAppDesktop() || reportInterface()->userCanProvideUnusualBehavior() || reportInterface()->userCanProvideApplicationConfigDetails()) { message += QLatin1Char(' ') + i18nc("@info","Previously, you told DrKonqi that you could provide some " "contextual information. Try writing more details about your situation. " "(even little ones could help us.)

"); } message += QLatin1Char(' ') + i18nc("@info","If you cannot provide more information, your report " "will probably waste developers' time. Can you tell us more?"); KGuiItem yesItem = KStandardGuiItem::yes(); yesItem.setText(i18n("Yes, let me add more information")); KGuiItem noItem = KStandardGuiItem::no(); noItem.setText(i18n("No, I cannot add any other information")); if (KMessageBox::warningYesNo(this, message, i18nc("@title:window","We need more information"), yesItem, noItem) == KMessageBox::No) { //Request the assistant to close itself (it will prompt for confirmation anyways) assistant()->close(); return false; } } else { return true; } } return false; } bool BugzillaInformationPage::isComplete() { return m_textsOK; } void BugzillaInformationPage::aboutToHide() { //Save fields data reportInterface()->setTitle(ui.m_titleEdit->text()); reportInterface()->setDetailText(ui.m_detailsEdit->toPlainText()); if (ui.m_distroChooserCombo->isVisible()) { //Save bugzilla platform (distribution) QString bugzillaPlatform = ui.m_distroChooserCombo->itemData( ui.m_distroChooserCombo->currentIndex()).toString(); KConfigGroup config(KSharedConfig::openConfig(), "BugzillaInformationPage"); config.writeEntry("BugzillaPlatform", bugzillaPlatform); DrKonqi::systemInformation()->setBugzillaPlatform(bugzillaPlatform); } bool compiledFromSources = ui.m_compiledSourcesCheckBox->checkState() == Qt::Checked; DrKonqi::systemInformation()->setCompiledSources(compiledFromSources); } void BugzillaInformationPage::showTitleExamples() { QString titleExamples = xi18nc("@info:tooltip examples of good bug report titles", "Examples of good titles:\"Plasma crashed after adding the Notes " "widget and writing on it\"\"Konqueror crashed when accessing the Facebook " "application 'X'\"\"Kopete suddenly closed after resuming the computer and " "talking to a MSN buddy\"\"Kate closed while editing a log file and pressing the " "Delete key a couple of times\""); QToolTip::showText(QCursor::pos(), titleExamples); } void BugzillaInformationPage::showDescriptionHelpExamples() { QString descriptionHelp = i18nc("@info:tooltip help and examples of good bug descriptions", "Describe in as much detail as possible the crash circumstances:"); if (reportInterface()->userCanProvideActionsAppDesktop()) { - descriptionHelp += QStringLiteral("
") + + descriptionHelp += QLatin1String("
") + i18nc("@info:tooltip help and examples of good bug descriptions", "- Detail which actions were you taking inside and outside the " "application an instant before the crash."); } if (reportInterface()->userCanProvideUnusualBehavior()) { - descriptionHelp += QStringLiteral("
") + + descriptionHelp += QLatin1String("
") + i18nc("@info:tooltip help and examples of good bug descriptions", "- Note if you noticed any unusual behavior in the application " "or in the whole environment."); } if (reportInterface()->userCanProvideApplicationConfigDetails()) { - descriptionHelp += QStringLiteral("
") + + descriptionHelp += QLatin1String("
") + i18nc("@info:tooltip help and examples of good bug descriptions", "- Note any non-default configuration in the application."); if (reportInterface()->appDetailsExamples()->hasExamples()) { descriptionHelp += QLatin1Char(' ') + i18nc("@info:tooltip examples of configuration details. " "the examples are already translated", "Examples: %1", reportInterface()->appDetailsExamples()->examples()); } } QToolTip::showText(QCursor::pos(), descriptionHelp); } //END BugzillaInformationPage //BEGIN BugzillaPreviewPage BugzillaPreviewPage::BugzillaPreviewPage(ReportAssistantDialog * parent) : ReportAssistantPage(parent) { ui.setupUi(this); } void BugzillaPreviewPage::aboutToShow() { ui.m_previewEdit->setText(reportInterface()->generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Complete)); assistant()->setAboutToSend(true); } void BugzillaPreviewPage::aboutToHide() { assistant()->setAboutToSend(false); } //END BugzillaPreviewPage //BEGIN BugzillaSendPage BugzillaSendPage::BugzillaSendPage(ReportAssistantDialog * parent) : ReportAssistantPage(parent), m_contentsDialog(nullptr) { connect(reportInterface(), &ReportInterface::reportSent, this, &BugzillaSendPage::sent); connect(reportInterface(), &ReportInterface::sendReportError, this, &BugzillaSendPage::sendError); ui.setupUi(this); KGuiItem::assign(ui.m_retryButton, KGuiItem2(i18nc("@action:button", "Retry..."), QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@info:tooltip", "Use this button to retry " "sending the crash report if it failed before."))); KGuiItem::assign(ui.m_showReportContentsButton, KGuiItem2(i18nc("@action:button", "Sho&w Contents of the Report"), QIcon::fromTheme(QStringLiteral("document-preview")), i18nc("@info:tooltip", "Use this button to show the generated " "report information about this crash."))); connect(ui.m_showReportContentsButton, &QPushButton::clicked, this, &BugzillaSendPage::openReportContents); ui.m_retryButton->setVisible(false); connect(ui.m_retryButton, &QAbstractButton::clicked, this , &BugzillaSendPage::retryClicked); ui.m_launchPageOnFinish->setVisible(false); ui.m_restartAppOnFinish->setVisible(false); connect(assistant()->finishButton(), &QPushButton::clicked, this, &BugzillaSendPage::finishClicked); } void BugzillaSendPage::retryClicked() { ui.m_retryButton->setEnabled(false); aboutToShow(); } void BugzillaSendPage::aboutToShow() { ui.m_statusWidget->setBusy(i18nc("@info:status","Sending crash report... (please wait)")); // Trigger relogin. If the user took a long time to prepare the login our // token might have gone invalid in the meantime. As a cheap way to prevent // this we'll simply refresh the token regardless. It's plenty cheap and // should reliably ensure that the token is current. // Disconnect everything first though, this function may get called a bunch // of times, so we don't want duplicated submissions. disconnect(bugzillaManager(), &BugzillaManager::loginFinished, reportInterface(), &ReportInterface::sendBugReport); disconnect(bugzillaManager(), &BugzillaManager::loginError, this, nullptr); connect(bugzillaManager(), &BugzillaManager::loginFinished, reportInterface(), &ReportInterface::sendBugReport); connect(bugzillaManager(), &BugzillaManager::loginError, this, &BugzillaSendPage::sendError); bugzillaManager()->refreshToken(); } void BugzillaSendPage::sent(int bug_id) { // Disconnect login->submit chain again. disconnect(bugzillaManager(), &BugzillaManager::loginFinished, reportInterface(), &ReportInterface::sendBugReport); disconnect(bugzillaManager(), &BugzillaManager::loginError, this, nullptr); ui.m_statusWidget->setVisible(false); ui.m_retryButton->setEnabled(false); ui.m_retryButton->setVisible(false); ui.m_showReportContentsButton->setVisible(false); ui.m_launchPageOnFinish->setVisible(true); ui.m_restartAppOnFinish->setVisible(!DrKonqi::crashedApplication()->hasBeenRestarted()); ui.m_restartAppOnFinish->setChecked(false); reportUrl = bugzillaManager()->urlForBug(bug_id); ui.m_finishedLabel->setText(xi18nc("@info/rich","Crash report sent." "URL: %1" "Thank you for being part of KDE. " "You can now close this window.", reportUrl)); emit finished(false); } void BugzillaSendPage::sendError(const QString &errorString) { ui.m_statusWidget->setIdle(xi18nc("@info:status","Error sending the crash report: " "%1.", errorString)); ui.m_retryButton->setEnabled(true); ui.m_retryButton->setVisible(true); } void BugzillaSendPage::finishClicked() { if (ui.m_launchPageOnFinish->isChecked() && !reportUrl.isEmpty()) { QDesktopServices::openUrl(QUrl(reportUrl)); } if (ui.m_restartAppOnFinish->isChecked()) { DrKonqi::crashedApplication()->restart(); } } void BugzillaSendPage::openReportContents() { if (!m_contentsDialog) { QString report = reportInterface()->generateReportFullText(ReportInterface::DrKonqiStamp::Exclude, ReportInterface::Backtrace::Complete) + QLatin1Char('\n') + i18nc("@info report to KDE bugtracker address","Report to %1", DrKonqi::crashedApplication()->bugReportAddress()); m_contentsDialog = new ReportInformationDialog(report); } m_contentsDialog->show(); m_contentsDialog->raise(); m_contentsDialog->activateWindow(); } //END BugzillaSendPage diff --git a/src/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp b/src/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp index 19760683..15db8480 100644 --- a/src/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp +++ b/src/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp @@ -1,1070 +1,1070 @@ /******************************************************************* * reportassistantpages_bugzilla_duplicates.cpp * Copyright 2009 Dario Andres Rodriguez * Copyright 2019 Harald Sitter * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #include "reportassistantpages_bugzilla_duplicates.h" #include #include #include #include #include #include #include #include #include #include "drkonqi_globals.h" #include "reportinterface.h" #include "statuswidget.h" //BEGIN BugzillaDuplicatesPage BugzillaDuplicatesPage::BugzillaDuplicatesPage(ReportAssistantDialog *parent) : ReportAssistantPage(parent) { connect(bugzillaManager(), &BugzillaManager::searchFinished, this, &BugzillaDuplicatesPage::searchFinished); connect(bugzillaManager(), &BugzillaManager::searchError, this, &BugzillaDuplicatesPage::searchError); ui.setupUi(this); ui.information->hide(); connect(ui.m_bugListWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(itemClicked(QTreeWidgetItem*,int))); connect(ui.m_bugListWidget, &QTreeWidget::itemSelectionChanged, this, &BugzillaDuplicatesPage::itemSelectionChanged); QHeaderView * header = ui.m_bugListWidget->header(); header->setSectionResizeMode(0, QHeaderView::ResizeToContents); header->setSectionResizeMode(1, QHeaderView::Interactive); //Create manual bug report entry (first one) QTreeWidgetItem * customBugItem = new QTreeWidgetItem( QStringList() << i18nc("@item:intable custom/manaul bug report number", "Manual") << i18nc("@item:intable custom bug report number description", "Manually enter a bug report ID")); customBugItem->setData(0, Qt::UserRole, QLatin1String("custom")); customBugItem->setIcon(1, QIcon::fromTheme(QStringLiteral("edit-rename"))); QString helpMessage = i18nc("@info:tooltip / whatsthis", "Select this option to manually load a specific bug report"); customBugItem->setToolTip(0, helpMessage); customBugItem->setToolTip(1, helpMessage); customBugItem->setWhatsThis(0, helpMessage); customBugItem->setWhatsThis(1, helpMessage); ui.m_bugListWidget->addTopLevelItem(customBugItem); m_searchMoreGuiItem = KGuiItem2(i18nc("@action:button", "Search for more reports"), QIcon::fromTheme(QStringLiteral("edit-find")), i18nc("@info:tooltip", "Use this button to " "search for more similar bug reports")); KGuiItem::assign(ui.m_searchMoreButton, m_searchMoreGuiItem); connect(ui.m_searchMoreButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::searchMore); m_retrySearchGuiItem = KGuiItem2(i18nc("@action:button", "Retry search"), QIcon::fromTheme(QStringLiteral("edit-find")), i18nc("@info:tooltip", "Use this button to " "retry the search that previously " "failed.")); KGuiItem::assign(ui.m_openReportButton, KGuiItem2(i18nc("@action:button", "Open selected report"), QIcon::fromTheme(QStringLiteral("document-preview")), i18nc("@info:tooltip", "Use this button to view " "the information of the selected bug report."))); connect(ui.m_openReportButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::openSelectedReport); KGuiItem::assign(ui.m_stopSearchButton, KGuiItem2(i18nc("@action:button", "Stop searching"), QIcon::fromTheme(QStringLiteral("process-stop")), i18nc("@info:tooltip", "Use this button to stop " "the current search."))); ui.m_stopSearchButton->setText(QString()); //FIXME connect(ui.m_stopSearchButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::stopCurrentSearch); //Possible duplicates list and buttons connect(ui.m_selectedDuplicatesList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(itemClicked(QListWidgetItem*))); connect(ui.m_selectedDuplicatesList, &QListWidget::itemSelectionChanged, this, &BugzillaDuplicatesPage::possibleDuplicateSelectionChanged); KGuiItem::assign(ui.m_removeSelectedDuplicateButton, KGuiItem2(i18nc("@action:button remove the selected item from a list", "Remove"), QIcon::fromTheme(QStringLiteral("list-remove")), i18nc("@info:tooltip", "Use this button to remove a selected possible duplicate"))); ui.m_removeSelectedDuplicateButton->setEnabled(false); connect(ui.m_removeSelectedDuplicateButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::removeSelectedDuplicate); ui.m_attachToReportIcon->setPixmap(QIcon::fromTheme(QStringLiteral("mail-attachment")).pixmap(16,16)); ui.m_attachToReportIcon->setFixedSize(16,16); ui.m_attachToReportIcon->setVisible(false); ui.m_attachToReportLabel->setVisible(false); ui.m_attachToReportLabel->setContextMenuPolicy(Qt::NoContextMenu); connect(ui.m_attachToReportLabel, &QLabel::linkActivated, this, &BugzillaDuplicatesPage::cancelAttachToBugReport); ui.information->setContextMenuPolicy(Qt::NoContextMenu); connect(ui.information, &QLabel::linkActivated, this, &BugzillaDuplicatesPage::informationClicked); showDuplicatesPanel(false); } BugzillaDuplicatesPage::~BugzillaDuplicatesPage() { } void BugzillaDuplicatesPage::aboutToShow() { //Perform initial search if we are not currently searching and if there are no results yet if (!m_searching && ui.m_bugListWidget->topLevelItemCount() == 1 && canSearchMore()) { searchMore(); } } void BugzillaDuplicatesPage::aboutToHide() { stopCurrentSearch(); //Save selected possible duplicates by user QStringList possibleDuplicates; int count = ui.m_selectedDuplicatesList->count(); for(int i = 0; iitem(i)->text(); } reportInterface()->setPossibleDuplicates(possibleDuplicates); //Save possible duplicates by query QStringList duplicatesByQuery; count = ui.m_bugListWidget->topLevelItemCount(); for(int i = 1; itopLevelItem(i)->text(0); } reportInterface()->setPossibleDuplicatesByQuery(duplicatesByQuery); } bool BugzillaDuplicatesPage::isComplete() { return !m_searching; } bool BugzillaDuplicatesPage::showNextPage() { //Ask the user to check all the possible duplicates... if (ui.m_bugListWidget->topLevelItemCount() != 1 && ui.m_selectedDuplicatesList->count() == 0 && reportInterface()->attachToBugNumber() == 0 && !m_foundDuplicate) { //The user didn't selected any possible duplicate nor a report to attach the new info. //Double check this, we need to reduce the duplicate count. KGuiItem noDuplicatesButton; noDuplicatesButton.setText(i18n("There are no real duplicates")); noDuplicatesButton.setWhatsThis(i18n("Press this button to declare that, in your opinion " "and according to your experience, the reports found " "as similar do not match the crash you have " "experienced, and you believe it is unlikely that a " "better match would be found after further review.")); noDuplicatesButton.setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel"))); KGuiItem letMeCheckMoreReportsButton; letMeCheckMoreReportsButton.setText(i18n("Let me check more reports")); letMeCheckMoreReportsButton.setWhatsThis(i18n("Press this button if you would rather " "review more reports in order to find a " "match for the crash you have experienced.")); letMeCheckMoreReportsButton.setIcon(QIcon::fromTheme(QStringLiteral("document-preview"))); if (KMessageBox::questionYesNo(this, i18nc("@info","You have not selected any possible duplicates, or a report to which to attach your " "crash information. Have you read all the reports, and can you confirm that there are no " "real duplicates?"), i18nc("@title:window","No selected possible duplicates"), letMeCheckMoreReportsButton, noDuplicatesButton) == KMessageBox::Yes) { return false; } } return true; } //BEGIN Search related methods void BugzillaDuplicatesPage::searchMore() { if (m_offset < 0) { m_offset = 0; // initialize, -1 means no search done yet } // This is fairly inefficient, unfortunately the API's offset/limit system // is not useful to us. The search is always sorting by lowest id, and // negative offests are not a thing. So, offset=0&limit=1 gives the first // ever reported bug in the product, while what we want is the latest. // We also cannot query all perintent bug ids by default. While the API // is reasonably fast, it'll still produce upwards of 2MiB just for the // ids of a dolphin crash (as it includes all sorts of extra products). // So we are left with somewhat shoddy time-based queries. markAsSearching(true); ui.m_statusWidget->setBusy(i18nc("@info:status", "Searching for duplicates...")); // Grab the default severity for newbugs static QString severity = reportInterface()->newBugReportTemplate().severity; bugzillaManager()->searchBugs(reportInterface()->relatedBugzillaProducts(), severity, - reportInterface()->firstBacktraceFunctions().join(QStringLiteral(" ")), + reportInterface()->firstBacktraceFunctions().join(QLatin1Char(' ')), m_offset); } void BugzillaDuplicatesPage::stopCurrentSearch() { if (m_searching) { bugzillaManager()->stopCurrentSearch(); markAsSearching(false); if (m_offset < 0) { //Never searched ui.m_statusWidget->setIdle(i18nc("@info:status", "Search stopped.")); } else { ui.m_statusWidget->setIdle(i18nc("@info:status", "Search stopped. Showing results.")); } } } void BugzillaDuplicatesPage::markAsSearching(bool searching) { m_searching = searching; emitCompleteChanged(); ui.m_bugListWidget->setEnabled(!searching); ui.m_searchMoreButton->setEnabled(!searching); ui.m_searchMoreButton->setVisible(!searching); ui.m_stopSearchButton->setEnabled(searching); ui.m_stopSearchButton->setVisible(searching); ui.m_selectedDuplicatesList->setEnabled(!searching); ui.m_selectedPossibleDuplicatesLabel->setEnabled(!searching); ui.m_removeSelectedDuplicateButton->setEnabled(!searching && !ui.m_selectedDuplicatesList->selectedItems().isEmpty()); ui.m_attachToReportLabel->setEnabled(!searching); if (!searching) { itemSelectionChanged(); } else { ui.m_openReportButton->setEnabled(false); } } bool BugzillaDuplicatesPage::canSearchMore() { return !m_atEnd; } static QString statusString(const Bugzilla::Bug::Ptr &bug) { // Generate a non-geek readable status switch(bug->status()) { case Bugzilla::Bug::Status::UNCONFIRMED: case Bugzilla::Bug::Status::CONFIRMED: case Bugzilla::Bug::Status::ASSIGNED: case Bugzilla::Bug::Status::REOPENED: return i18nc("@info bug status", "[Open]"); case Bugzilla::Bug::Status::RESOLVED: case Bugzilla::Bug::Status::VERIFIED: case Bugzilla::Bug::Status::CLOSED: switch(bug->resolution()) { case Bugzilla::Bug::Resolution::FIXED: return i18nc("@info bug resolution", "[Fixed]"); case Bugzilla::Bug::Resolution::WORKSFORME: return i18nc("@info bug resolution", "[Non-reproducible]"); case Bugzilla::Bug::Resolution::DUPLICATE: return i18nc("@info bug resolution", "[Duplicate report]"); case Bugzilla::Bug::Resolution::INVALID: return i18nc("@info bug resolution", "[Invalid]"); case Bugzilla::Bug::Resolution::UPSTREAM: case Bugzilla::Bug::Resolution::DOWNSTREAM: return i18nc("@info bug resolution", "[External problem]"); case Bugzilla::Bug::Resolution::WONTFIX: case Bugzilla::Bug::Resolution::LATER: case Bugzilla::Bug::Resolution::REMIND: case Bugzilla::Bug::Resolution::MOVED: case Bugzilla::Bug::Resolution::WAITINGFORINFO: case Bugzilla::Bug::Resolution::BACKTRACE: case Bugzilla::Bug::Resolution::UNMAINTAINED: case Bugzilla::Bug::Resolution::NONE: return QStringLiteral("[%1]").arg(QVariant::fromValue(bug->resolution()).toString()); case Bugzilla::Bug::Resolution::Unknown: break; } break; case Bugzilla::Bug::Status::NEEDSINFO: return i18nc("@info bug status", "[Incomplete]"); case Bugzilla::Bug::Status::Unknown: break; } return QString(); } void BugzillaDuplicatesPage::searchFinished(const QList &list) { KGuiItem::assign(ui.m_searchMoreButton, m_searchMoreGuiItem); int results = list.count(); m_offset += results; if (results > 0) { m_atEnd = false; markAsSearching(false); ui.m_statusWidget->setIdle(i18nc("@info:status", "Showing results.")); for (int i = 0; i < results; i++) { Bugzilla::Bug::Ptr bug = list.at(i); QString title = statusString(bug) + QLatin1Char(' ') + bug->summary(); QStringList fields = QStringList() << QString::number(bug->id()) << title; QTreeWidgetItem * item = new QTreeWidgetItem(fields); item->setToolTip(0, bug->summary()); item->setToolTip(1, bug->summary()); ui.m_bugListWidget->addTopLevelItem(item); } if (!m_foundDuplicate) { markAsSearching(true); DuplicateFinderJob *job = new DuplicateFinderJob(list, bugzillaManager(), this); connect(job, &KJob::result, this, &BugzillaDuplicatesPage::analyzedDuplicates); job->start(); } ui.m_bugListWidget->sortItems(0 , Qt::DescendingOrder); ui.m_bugListWidget->resizeColumnToContents(1); if (!canSearchMore()) { ui.m_searchMoreButton->setEnabled(false); } } else { m_atEnd = true; if (canSearchMore()) { //We don't call markAsSearching(false) to avoid flicker //Delayed call to searchMore to avoid unexpected behaviour (signal/slot) //because we are in a slot, and searchMore() will be ending calling this slot again QTimer::singleShot(0, this, &BugzillaDuplicatesPage::searchMore); } else { markAsSearching(false); ui.m_statusWidget->setIdle(i18nc("@info:status","Search Finished. " "No reports found.")); ui.m_searchMoreButton->setEnabled(false); if (ui.m_bugListWidget->topLevelItemCount() == 0) { //No reports to mark as possible duplicate ui.m_selectedDuplicatesList->setEnabled(false); } } } } static bool isStatusOpen(Bugzilla::Bug::Status status) { switch(status) { case Bugzilla::Bug::Status::UNCONFIRMED: case Bugzilla::Bug::Status::CONFIRMED: case Bugzilla::Bug::Status::ASSIGNED: case Bugzilla::Bug::Status::REOPENED: return true; case Bugzilla::Bug::Status::RESOLVED: case Bugzilla::Bug::Status::NEEDSINFO: case Bugzilla::Bug::Status::VERIFIED: case Bugzilla::Bug::Status::CLOSED: return false; case Bugzilla::Bug::Status::Unknown: break; } return false; } static bool isStatusClosed(Bugzilla::Bug::Status status) { return !isStatusOpen(status); } void BugzillaDuplicatesPage::analyzedDuplicates(KJob *j) { markAsSearching(false); DuplicateFinderJob *job = static_cast(j); m_result = job->result(); m_foundDuplicate = m_result.parentDuplicate; reportInterface()->setDuplicateId(m_result.parentDuplicate); ui.m_searchMoreButton->setEnabled(!m_foundDuplicate); ui.information->setVisible(m_foundDuplicate); auto status = m_result.status; const int duplicate = m_result.duplicate; const int parentDuplicate = m_result.parentDuplicate; if (m_foundDuplicate) { const QList items = ui.m_bugListWidget->findItems(QString::number(parentDuplicate), Qt::MatchExactly, 0); const QBrush brush = KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NeutralBackground); Q_FOREACH (QTreeWidgetItem* item, items) { for (int i = 0; i < item->columnCount(); ++i) { item->setBackground(i, brush); } } QString text; if (isStatusOpen(status) || status == Bugzilla::Bug::Status::NEEDSINFO) { text = (parentDuplicate == duplicate ? i18nc("@label", "Your crash is a duplicate and has already been reported as Bug %1.", QString::number(duplicate)) : i18nc("@label", "Your crash has already been reported as Bug %1, which is a duplicate of Bug %2", QString::number(duplicate), QString::number(parentDuplicate))) + QLatin1Char('\n') + i18nc("@label", "Only attach if you can add needed information to the bug report.", QStringLiteral("attach")); } else if (isStatusClosed(status)) { text = (parentDuplicate == duplicate ? i18nc("@label", "Your crash has already been reported as Bug %1 which has been closed.", QString::number(duplicate)) : i18nc("@label", "Your crash has already been reported as Bug %1, which is a duplicate of the closed Bug %2.", QString::number(duplicate), QString::number(parentDuplicate))); } ui.information->setText(text); } } void BugzillaDuplicatesPage::informationClicked(const QString &activatedLink) { if (activatedLink == QLatin1String("attach")) { attachToBugReport(m_result.parentDuplicate); } else { int number = activatedLink.toInt(); if (number) { showReportInformationDialog(number, false); } } } void BugzillaDuplicatesPage::searchError(QString err) { KGuiItem::assign(ui.m_searchMoreButton, m_retrySearchGuiItem); markAsSearching(false); ui.m_statusWidget->setIdle(i18nc("@info:status","Error fetching the bug report list")); KMessageBox::error(this , xi18nc("@info/rich","Error fetching the bug report list" "%1." "Please wait some time and try again.", err)); } //END Search related methods //BEGIN Duplicates list related methods void BugzillaDuplicatesPage::openSelectedReport() { QList selected = ui.m_bugListWidget->selectedItems(); if (selected.count() == 1) { itemClicked(selected.at(0), 0); } } void BugzillaDuplicatesPage::itemClicked(QTreeWidgetItem * item, int col) { Q_UNUSED(col); int bugNumber = 0; if (item->data(0, Qt::UserRole) == QLatin1String("custom")) { bool ok = false; bugNumber = QInputDialog::getInt(this, i18nc("@title:window", "Enter a custom bug report number"), i18nc("@label", "Enter the number of the bug report you want to check"), 0, 0, 1000000, 1, &ok); } else { bugNumber = item->text(0).toInt(); } showReportInformationDialog(bugNumber); } void BugzillaDuplicatesPage::itemClicked(QListWidgetItem * item) { showReportInformationDialog(item->text().toInt()); } void BugzillaDuplicatesPage::showReportInformationDialog(int bugNumber, bool relatedButtonEnabled) { if (bugNumber <= 0) { return; } BugzillaReportInformationDialog * infoDialog = new BugzillaReportInformationDialog(this); connect(infoDialog, &BugzillaReportInformationDialog::possibleDuplicateSelected, this, &BugzillaDuplicatesPage::addPossibleDuplicateNumber); connect(infoDialog, &BugzillaReportInformationDialog::attachToBugReportSelected, this, &BugzillaDuplicatesPage::attachToBugReport); infoDialog->showBugReport(bugNumber, relatedButtonEnabled); } void BugzillaDuplicatesPage::itemSelectionChanged() { ui.m_openReportButton->setEnabled(ui.m_bugListWidget->selectedItems().count() == 1); } //END Duplicates list related methods //BEGIN Selected duplicates list related methods void BugzillaDuplicatesPage::addPossibleDuplicateNumber(int bugNumber) { QString stringNumber = QString::number(bugNumber); if (ui.m_selectedDuplicatesList->findItems(stringNumber, Qt::MatchExactly).isEmpty()) { ui.m_selectedDuplicatesList->addItem(stringNumber); } showDuplicatesPanel(true); } void BugzillaDuplicatesPage::removeSelectedDuplicate() { QList items = ui.m_selectedDuplicatesList->selectedItems(); if (items.length() > 0) { delete ui.m_selectedDuplicatesList->takeItem(ui.m_selectedDuplicatesList->row(items.at(0))); } if (ui.m_selectedDuplicatesList->count() == 0) { showDuplicatesPanel(false); } } void BugzillaDuplicatesPage::showDuplicatesPanel(bool show) { ui.m_removeSelectedDuplicateButton->setVisible(show); ui.m_selectedDuplicatesList->setVisible(show); ui.m_selectedPossibleDuplicatesLabel->setVisible(show); } void BugzillaDuplicatesPage::possibleDuplicateSelectionChanged() { ui.m_removeSelectedDuplicateButton->setEnabled( !ui.m_selectedDuplicatesList->selectedItems().isEmpty()); } //END Selected duplicates list related methods //BEGIN Attach to bug related methods void BugzillaDuplicatesPage::attachToBugReport(int bugNumber) { ui.m_attachToReportLabel->setText(xi18nc("@label", "The report is going to be " "attached to bug %1. " "Cancel", QString::number(bugNumber))); ui.m_attachToReportLabel->setVisible(true); ui.m_attachToReportIcon->setVisible(true); reportInterface()->setAttachToBugNumber(bugNumber); } void BugzillaDuplicatesPage::cancelAttachToBugReport() { ui.m_attachToReportLabel->setVisible(false); ui.m_attachToReportIcon->setVisible(false); reportInterface()->setAttachToBugNumber(0); } //END Attach to bug related methods //END BugzillaDuplicatesPage //BEGIN BugzillaReportInformationDialog BugzillaReportInformationDialog::BugzillaReportInformationDialog(BugzillaDuplicatesPage * parent) : QDialog(parent), m_relatedButtonEnabled(true), m_parent(parent), m_bugNumber(0), m_duplicatesCount(0) { setWindowTitle(i18nc("@title:window","Bug Description")); ui.setupUi(this); KGuiItem::assign(ui.m_retryButton, KGuiItem2(i18nc("@action:button", "Retry..."), QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@info:tooltip", "Use this button to retry " "loading the bug report."))); connect(ui.m_retryButton, &QPushButton::clicked, this, &BugzillaReportInformationDialog::reloadReport); m_suggestButton = new QPushButton(this); ui.buttonBox->addButton(m_suggestButton, QDialogButtonBox::ActionRole); KGuiItem::assign(m_suggestButton, KGuiItem2(i18nc("@action:button", "Suggest this crash is related"), QIcon::fromTheme(QStringLiteral("list-add")), i18nc("@info:tooltip", "Use this button to suggest that " "the crash you experienced is related to this bug " "report"))); connect(m_suggestButton, &QPushButton::clicked, this, &BugzillaReportInformationDialog::relatedReportClicked); connect(ui.m_showOwnBacktraceCheckBox, &QAbstractButton::toggled, this, &BugzillaReportInformationDialog::toggleShowOwnBacktrace); //Connect bugzillalib signals connect(m_parent->bugzillaManager(), &BugzillaManager::bugReportFetched, this, &BugzillaReportInformationDialog::bugFetchFinished); connect(m_parent->bugzillaManager(), &BugzillaManager::bugReportError, this, &BugzillaReportInformationDialog::bugFetchError); connect(m_parent->bugzillaManager(), &BugzillaManager::commentsFetched, this, &BugzillaReportInformationDialog::onCommentsFetched); connect(m_parent->bugzillaManager(), &BugzillaManager::commentsError, this, &BugzillaReportInformationDialog::bugFetchError); KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog"); KWindowConfig::restoreWindowSize(windowHandle(), config); } BugzillaReportInformationDialog::~BugzillaReportInformationDialog() { KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog"); KWindowConfig::saveWindowSize(windowHandle(), config); } void BugzillaReportInformationDialog::reloadReport() { showBugReport(m_bugNumber); } void BugzillaReportInformationDialog::showBugReport(int bugNumber, bool relatedButtonEnabled) { m_relatedButtonEnabled = relatedButtonEnabled; ui.m_retryButton->setVisible(false); m_closedStateString.clear(); m_bugNumber = bugNumber; m_parent->bugzillaManager()->fetchBugReport(m_bugNumber, this); m_suggestButton->setEnabled(false); m_suggestButton->setVisible(m_relatedButtonEnabled); ui.m_infoBrowser->setText(i18nc("@info:status","Loading...")); ui.m_infoBrowser->setEnabled(false); ui.m_linkLabel->setText(xi18nc("@info","Report's webpage", m_parent->bugzillaManager()->urlForBug(m_bugNumber))); ui.m_statusWidget->setBusy(xi18nc("@info:status","Loading information about bug " "%1 from %2....", QString::number(m_bugNumber), QLatin1String(KDE_BUGZILLA_SHORT_URL))); ui.m_backtraceBrowser->setPlainText( i18nc("@info","Backtrace of the crash I experienced:\n\n") + m_parent->reportInterface()->backtrace()); KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog"); bool showOwnBacktrace = config.readEntry("ShowOwnBacktrace", false); ui.m_showOwnBacktraceCheckBox->setChecked(showOwnBacktrace); if (!showOwnBacktrace) { //setChecked(false) will not emit toggled(false) toggleShowOwnBacktrace(false); } show(); } struct Status2 { QString statusString; QString closedStateString; }; static Status2 statusString2(const Bugzilla::Bug::Ptr &bug) { // Generate a non-geek readable status switch(bug->status()) { case Bugzilla::Bug::Status::UNCONFIRMED: return { i18nc("@info bug status", "Opened (Unconfirmed)"), QString() }; case Bugzilla::Bug::Status::CONFIRMED: case Bugzilla::Bug::Status::ASSIGNED: case Bugzilla::Bug::Status::REOPENED: return { i18nc("@info bug status", "Opened (Unfixed)"), QString() }; case Bugzilla::Bug::Status::RESOLVED: case Bugzilla::Bug::Status::VERIFIED: case Bugzilla::Bug::Status::CLOSED: switch(bug->resolution()) { case Bugzilla::Bug::Resolution::FIXED: { auto fixedIn = bug->customField("cf_versionfixedin").toString(); if (!fixedIn.isEmpty()) { return { i18nc("@info bug resolution, fixed in version", "Fixed in version \"%1\"", fixedIn), i18nc("@info bug resolution, fixed by kde devs in version", "the bug was fixed by KDE developers in version \"%1\"", fixedIn) }; } return { i18nc("@info bug resolution", "Fixed"), i18nc("@info bug resolution", "the bug was fixed by KDE developers") }; } case Bugzilla::Bug::Resolution::WORKSFORME: return { i18nc("@info bug resolution", "Non-reproducible"), QString() }; case Bugzilla::Bug::Resolution::DUPLICATE: return { i18nc("@info bug resolution", "Duplicate report (Already reported before)"), QString() }; case Bugzilla::Bug::Resolution::INVALID: return { i18nc("@info bug resolution", "Not a valid report/crash"), QString() }; case Bugzilla::Bug::Resolution::UPSTREAM: case Bugzilla::Bug::Resolution::DOWNSTREAM: return { i18nc("@info bug resolution", "Not caused by a problem in the KDE's Applications or libraries"), i18nc("@info bug resolution", "the bug is caused by a problem in an external application or library, or by a distribution or packaging issue") }; case Bugzilla::Bug::Resolution::WONTFIX: case Bugzilla::Bug::Resolution::LATER: case Bugzilla::Bug::Resolution::REMIND: case Bugzilla::Bug::Resolution::MOVED: case Bugzilla::Bug::Resolution::WAITINGFORINFO: case Bugzilla::Bug::Resolution::BACKTRACE: case Bugzilla::Bug::Resolution::UNMAINTAINED: case Bugzilla::Bug::Resolution::NONE: return { QVariant::fromValue(bug->resolution()).toString(), QString() }; case Bugzilla::Bug::Resolution::Unknown: break; } return {}; case Bugzilla::Bug::Status::NEEDSINFO: return { i18nc("@info bug status", "Temporarily closed, because of a lack of information"), QString() }; case Bugzilla::Bug::Status::Unknown: break; } return {}; } void BugzillaReportInformationDialog::bugFetchFinished(Bugzilla::Bug::Ptr bug, QObject *jobOwner) { if (jobOwner != this || !isVisible()) { return; } if (!bug) { bugFetchError(i18nc("@info", "Invalid report information (malformed data). This could " "mean that the bug report does not exist, or the bug tracking site " "is experiencing a problem."), this); return; } Q_ASSERT(!m_bug); // m_bug must only be set once we've selected one! // Handle duplicate state if (bug->dupe_of() > 0) { ui.m_statusWidget->setIdle(QString()); KGuiItem yesItem = KStandardGuiItem::yes(); yesItem.setText(i18nc("@action:button let the user to choose to read the " "main report", "Yes, read the main report")); KGuiItem noItem = KStandardGuiItem::no(); noItem.setText(i18nc("@action:button let the user choose to read the original " "report", "No, let me read the report I selected")); auto ret = KMessageBox::questionYesNo( this, xi18nc("@info","The report you selected (bug %1) is already " "marked as duplicate of bug %2. " "Do you want to read that report instead? (recommended)", bug->id(), QString::number(bug->dupe_of())), i18nc("@title:window","Nested duplicate detected"), yesItem, noItem); if (ret == KMessageBox::Yes) { qDebug() << "REDIRECT"; showBugReport(bug->dupe_of()); return; } } // Process comments... m_bug = bug; m_parent->bugzillaManager()->fetchComments(m_bug, this); } void BugzillaReportInformationDialog::onCommentsFetched(QList bugComments, QObject *jobOwner) { if (jobOwner != this || !isVisible()) { return; } Q_ASSERT(m_bug); // Generate html for comments (with proper numbering) QLatin1String duplicatesMark = QLatin1String("has been marked as a duplicate of this bug."); // TODO: the way comment objects are turned into comment strings is fairly // awkward and does not particularly object-centric. May benefit from a // slight redesign. QString comments; QString description; // aka first comment if (bugComments.size() > 0) { description = bugComments.takeFirst()->text(); } for (auto it = bugComments.constBegin(); it != bugComments.constEnd(); ++it) { QString comment = (*it)->text(); // Don't add duplicates mark comments if (!comment.contains(duplicatesMark)) { comment.replace(QLatin1Char('\n'), QLatin1String("
")); const int i = it - bugComments.constBegin(); comments += i18nc("comment $number to use as subtitle", "

Comment %1:

", (i+1)) + QStringLiteral("

") + comment + QStringLiteral("


"); // Count the inline attached crashes (DrKonqi feature) QLatin1String attachedCrashMark = QLatin1String("New crash information added by DrKonqi"); if (comment.contains(attachedCrashMark)) { m_duplicatesCount++; } } else { // Count duplicate m_duplicatesCount++; } } // Generate a non-geek readable status auto str = statusString2(m_bug); QString customStatusString = str.statusString; m_closedStateString = str.closedStateString; // Generate notes QString notes = xi18n("

The bug report's title is often written by its reporter " "and may not reflect the bug's nature, root cause or other visible " "symptoms you could use to compare to your crash. Please read the " "complete report and all the comments below.

"); if (m_duplicatesCount >= 10) { //Consider a possible mass duplicate crash notes += xi18np("

This bug report has %1 duplicate report. That means this " "is probably a common crash. Please consider only " "adding a comment or a note if you can provide new valuable " "information which was not already mentioned.

", "

This bug report has %1 duplicate reports. That means this " "is probably a common crash. Please consider only " "adding a comment or a note if you can provide new valuable " "information which was not already mentioned.

", m_duplicatesCount); } // A manually entered bug ID could represent a normal bug if (m_bug->severity() != QLatin1String("crash") && m_bug->severity() != QLatin1String("major") && m_bug->severity() != QLatin1String("grave") && m_bug->severity() != QLatin1String("critical")) { notes += xi18n("

This bug report is not about a crash or about any other " "critical bug.

"); } // Generate HTML text QString text = i18nc("@info bug report title (quoted)", "

\"%1\"

", m_bug->summary()) + notes + i18nc("@info bug report status", "

Bug Report Status: %1

", customStatusString) + i18nc("@info bug report product and component", "

Affected Component: %1 (%2)

", m_bug->product(), m_bug->component()) + i18nc("@info bug report description", "

Description of the bug

%1

", description.replace(QLatin1Char('\n'), QLatin1String("
"))); if (!comments.isEmpty()) { text += i18nc("@label:textbox bug report comments (already formatted)", "

Additional Comments

%1", comments); } ui.m_infoBrowser->setText(text); ui.m_infoBrowser->setEnabled(true); m_suggestButton->setEnabled(m_relatedButtonEnabled); m_suggestButton->setVisible(m_relatedButtonEnabled); ui.m_statusWidget->setIdle(xi18nc("@info:status", "Showing bug %1", QString::number(m_bug->id()))); } void BugzillaReportInformationDialog::markAsDuplicate() { emit possibleDuplicateSelected(m_bugNumber); hide(); } void BugzillaReportInformationDialog::attachToBugReport() { emit attachToBugReportSelected(m_bugNumber); hide(); } void BugzillaReportInformationDialog::cancelAssistant() { m_parent->assistant()->close(); hide(); } void BugzillaReportInformationDialog::relatedReportClicked() { BugzillaReportConfirmationDialog * confirmation = new BugzillaReportConfirmationDialog(m_bugNumber, (m_duplicatesCount >= 10), m_closedStateString, this); confirmation->show(); } void BugzillaReportInformationDialog::bugFetchError(QString err, QObject * jobOwner) { if (jobOwner == this && isVisible()) { KMessageBox::error(this , xi18nc("@info/rich","Error fetching the bug report" "%1." "Please wait some time and try again.", err)); m_suggestButton->setEnabled(false); ui.m_infoBrowser->setText(i18nc("@info","Error fetching the bug report")); ui.m_statusWidget->setIdle(i18nc("@info:status","Error fetching the bug report")); ui.m_retryButton->setVisible(true); } } void BugzillaReportInformationDialog::toggleShowOwnBacktrace(bool show) { QList sizes; if (show) { int size = (ui.m_reportSplitter->sizeHint().width()-ui.m_reportSplitter->handleWidth())/2; sizes << size << size; } else { sizes << ui.m_reportSplitter->sizeHint().width() << 0; //Hide backtrace } ui.m_reportSplitter->setSizes(sizes); //Save the current show value KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog"); config.writeEntry("ShowOwnBacktrace", show); } //END BugzillaReportInformationDialog //BEGIN BugzillaReportConfirmationDialog BugzillaReportConfirmationDialog::BugzillaReportConfirmationDialog(int bugNumber, bool commonCrash, QString closedState, BugzillaReportInformationDialog * parent) : QDialog(parent), m_parent(parent), m_showProceedQuestion(false), m_bugNumber(bugNumber) { setAttribute(Qt::WA_DeleteOnClose, true); setModal(true); ui.setupUi(this); //Setup dialog setWindowTitle(i18nc("@title:window", "Related Bug Report")); //Setup buttons ui.buttonBox->button(QDialogButtonBox::Cancel)->setText(i18nc("@action:button", "Cancel (Go back to the report)")); ui.buttonBox->button(QDialogButtonBox::Ok)->setText(i18nc("@action:button continue with the selected option " "and close the dialog", "Continue")); ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); connect(this, &BugzillaReportConfirmationDialog::accepted, this, &BugzillaReportConfirmationDialog::proceedClicked); connect(this, &BugzillaReportConfirmationDialog::rejected, this, &BugzillaReportConfirmationDialog::hide); //Set introduction text ui.introLabel->setText(i18n("You are going to mark your crash as related to bug %1", QString::number(m_bugNumber))); if (commonCrash) { //Common ("massive") crash m_showProceedQuestion = true; ui.commonCrashIcon->setPixmap(QIcon::fromTheme(QStringLiteral("edit-bomb")).pixmap(22,22)); } else { ui.commonCrashLabel->setVisible(false); ui.commonCrashIcon->setVisible(false); } if (!closedState.isEmpty()) { //Bug report closed ui.closedReportLabel->setText( i18nc("@info", "The report is closed because %1. " "If the crash is the same, adding further information will be useless " "and will consume developers' time.", closedState)); ui.closedReportIcon->setPixmap(QIcon::fromTheme(QStringLiteral("document-close")).pixmap(22,22)); m_showProceedQuestion = true; } else { ui.closedReportLabel->setVisible(false); ui.closedReportIcon->setVisible(false); } //Disable all the radio buttons ui.proceedRadioYes->setChecked(false); ui.proceedRadioNo->setChecked(false); ui.markAsDuplicateCheck->setChecked(false); ui.attachToBugReportCheck->setChecked(false); connect(ui.buttonGroupProceed, SIGNAL(buttonClicked(int)), this, SLOT(checkProceed())); connect(ui.buttonGroupProceedQuestion, SIGNAL(buttonClicked(int)), this, SLOT(checkProceed())); // Also listen to toggle so radio buttons are covered. connect(ui.buttonGroupProceed, static_cast(&QButtonGroup::buttonToggled), this, &BugzillaReportConfirmationDialog::checkProceed); connect(ui.buttonGroupProceedQuestion, static_cast(&QButtonGroup::buttonToggled), this, &BugzillaReportConfirmationDialog::checkProceed); if (!m_showProceedQuestion) { ui.proceedLabel->setEnabled(false); ui.proceedRadioYes->setEnabled(false); ui.proceedRadioNo->setEnabled(false); ui.proceedLabel->setVisible(false); ui.proceedRadioYes->setVisible(false); ui.proceedRadioNo->setVisible(false); ui.proceedRadioYes->setChecked(true); } checkProceed(); } BugzillaReportConfirmationDialog::~BugzillaReportConfirmationDialog() { } void BugzillaReportConfirmationDialog::checkProceed() { bool yes = ui.proceedRadioYes->isChecked(); bool no = ui.proceedRadioNo->isChecked(); //Enable/disable labels and controls ui.areYouSureLabel->setEnabled(yes); ui.markAsDuplicateCheck->setEnabled(yes); ui.attachToBugReportCheck->setEnabled(yes); //Enable Continue button if valid options are selected bool possibleDupe = ui.markAsDuplicateCheck->isChecked(); bool attach = ui.attachToBugReportCheck->isChecked(); bool enableContinueButton = yes ? (possibleDupe || attach) : no; ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enableContinueButton); } void BugzillaReportConfirmationDialog::proceedClicked() { if (ui.proceedRadioYes->isChecked()) { if (ui.markAsDuplicateCheck->isChecked()) { m_parent->markAsDuplicate(); hide(); } else { m_parent->attachToBugReport(); hide(); } } else { hide(); m_parent->cancelAssistant(); } } //END BugzillaReportConfirmationDialog diff --git a/src/bugzillaintegration/reportinterface.cpp b/src/bugzillaintegration/reportinterface.cpp index 3e87a173..7434cf70 100644 --- a/src/bugzillaintegration/reportinterface.cpp +++ b/src/bugzillaintegration/reportinterface.cpp @@ -1,490 +1,490 @@ /******************************************************************* * reportinterface.cpp * Copyright 2009,2010, 2011 Dario Andres Rodriguez * Copyright 2009 George Kiagiadakis * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #include "reportinterface.h" #include #include "drkonqi.h" #include "bugzillalib.h" #include "productmapping.h" #include "systeminformation.h" #include "crashedapplication.h" #include "debuggermanager.h" #include "parser/backtraceparser.h" #include "backtracegenerator.h" #include "applicationdetailsexamples.h" // Max size a report may have. This is enforced in bugzilla, hardcoded, and // cannot be queried through the API, so handle this client-side in a hardcoded // fashion as well. static int s_maxReportSize = 65535; ReportInterface::ReportInterface(QObject *parent) : QObject(parent), m_duplicate(0) { m_bugzillaManager = new BugzillaManager(KDE_BUGZILLA_URL, this); m_productMapping = new ProductMapping(DrKonqi::crashedApplication(), m_bugzillaManager, this); m_appDetailsExamples = new ApplicationDetailsExamples(this); //Information the user can provide about the crash m_userRememberCrashSituation = false; m_reproducible = ReproducibleUnsure; m_provideActionsApplicationDesktop = false; m_provideUnusualBehavior = false; m_provideApplicationConfigurationDetails = false; //Do not attach the bug report to any other existent report (create a new one) m_attachToBugNumber = 0; } void ReportInterface::setBugAwarenessPageData(bool rememberSituation, Reproducible reproducible, bool actions, bool unusual, bool configuration) { //Save the information the user can provide about the crash from the assistant page m_userRememberCrashSituation = rememberSituation; m_reproducible = reproducible; m_provideActionsApplicationDesktop = actions; m_provideUnusualBehavior = unusual; m_provideApplicationConfigurationDetails = configuration; } bool ReportInterface::isBugAwarenessPageDataUseful() const { //Determine if the assistant should proceed, considering the amount of information //the user can provide int rating = selectedOptionsRating(); //Minimum information required even for a good backtrace. bool useful = m_userRememberCrashSituation && (rating >= 2 || (m_reproducible==ReproducibleSometimes || m_reproducible==ReproducibleEverytime)); return useful; } int ReportInterface::selectedOptionsRating() const { //Check how many information the user can provide and generate a rating int rating = 0; if (m_provideActionsApplicationDesktop) { rating += 3; } if (m_provideApplicationConfigurationDetails) { rating += 2; } if (m_provideUnusualBehavior) { rating += 1; } return rating; } QString ReportInterface::backtrace() const { return m_backtrace; } void ReportInterface::setBacktrace(const QString & backtrace) { m_backtrace = backtrace; } QStringList ReportInterface::firstBacktraceFunctions() const { return m_firstBacktraceFunctions; } void ReportInterface::setFirstBacktraceFunctions(const QStringList & functions) { m_firstBacktraceFunctions = functions; } QString ReportInterface::title() const { return m_reportTitle; } void ReportInterface::setTitle(const QString & text) { m_reportTitle = text; } void ReportInterface::setDetailText(const QString & text) { m_reportDetailText = text; } void ReportInterface::setPossibleDuplicates(const QStringList & list) { m_possibleDuplicates = list; } QString ReportInterface::generateReportFullText(DrKonqiStamp stamp, Backtrace inlineBacktrace) const { //Note: no translations should be done in this function's strings const CrashedApplication * crashedApp = DrKonqi::crashedApplication(); const SystemInformation * sysInfo = DrKonqi::systemInformation(); QString report; //Program name and versions report.append(QStringLiteral("Application: %1 (%2)\n").arg(crashedApp->fakeExecutableBaseName(), crashedApp->version())); if ( sysInfo->compiledSources() ) { report.append(QStringLiteral(" (Compiled from sources)\n")); } else { - report.append(QStringLiteral("\n")); + report.append(QLatin1Char('\n')); } report.append(QStringLiteral("Qt Version: %1\n").arg(sysInfo->qtVersion())); report.append(QStringLiteral("Frameworks Version: %1\n").arg(sysInfo->frameworksVersion())); report.append(QStringLiteral("Operating System: %1\n").arg(sysInfo->operatingSystem())); //LSB output or manually selected distro if ( !sysInfo->distributionPrettyName().isEmpty() ) { report.append(QStringLiteral("Distribution: %1\n").arg(sysInfo->distributionPrettyName())); } else if ( !sysInfo->bugzillaPlatform().isEmpty() && sysInfo->bugzillaPlatform() != QLatin1String("unspecified")) { report.append(QStringLiteral("Distribution (Platform): %1\n").arg( sysInfo->bugzillaPlatform())); } report.append(QLatin1Char('\n')); //Details of the crash situation if (isBugAwarenessPageDataUseful()) { report.append(QStringLiteral("-- Information about the crash:\n")); if (!m_reportDetailText.isEmpty()) { report.append(m_reportDetailText.trimmed()); } else { //If the user manual reports this crash, he/she should know what to put in here. //This message is the only one translated in this function report.append(xi18nc("@info/plain","In detail, tell us what you were doing " " when the application crashed.")); } report.append(QLatin1String("\n\n")); } //Crash reproducibility (only if useful) if (m_reproducible != ReproducibleUnsure) { if (m_reproducible == ReproducibleEverytime) { report.append(QStringLiteral("The crash can be reproduced every time.\n\n")); } else if (m_reproducible == ReproducibleSometimes) { report.append(QStringLiteral("The crash can be reproduced sometimes.\n\n")); } else if (m_reproducible == ReproducibleNever) { report.append(QStringLiteral("The crash does not seem to be reproducible.\n\n")); } } //Backtrace switch (inlineBacktrace) { case Backtrace::Complete: report.append(QStringLiteral("-- Backtrace:\n")); break; case Backtrace::Reduced: report.append(QStringLiteral("-- Backtrace (Reduced):\n")); break; case Backtrace::Exclude: report.append(QStringLiteral("The backtrace was excluded and likely attached as a file.\n")); break; } if (!m_backtrace.isEmpty()) { switch (inlineBacktrace) { case Backtrace::Complete: report.append(m_backtrace.trimmed() + QLatin1Char('\n')); break; case Backtrace::Reduced: report.append(DrKonqi::debuggerManager()->backtraceGenerator()->parser()->simplifiedBacktrace() + QLatin1Char('\n')); break; case Backtrace::Exclude: report.append(QStringLiteral("The backtrace is attached as a comment due to length constraints\n")); break; } } else { report.append(QStringLiteral("A useful backtrace could not be generated\n")); } //Possible duplicates (selected by the user) if (!m_possibleDuplicates.isEmpty()) { report.append(QLatin1Char('\n')); QString duplicatesString; Q_FOREACH(const QString & dupe, m_possibleDuplicates) { duplicatesString += QLatin1String("bug ") + dupe + QLatin1String(", "); } duplicatesString = duplicatesString.left(duplicatesString.length()-2) + QLatin1Char('.'); report.append(QStringLiteral("The reporter indicates this bug may be a duplicate of or related to %1\n") .arg(duplicatesString)); } //Several possible duplicates (by bugzilla query) if (!m_allPossibleDuplicatesByQuery.isEmpty()) { report.append(QLatin1Char('\n')); QString duplicatesString; int count = m_allPossibleDuplicatesByQuery.count(); for(int i=0; i < count && i < 5; i++) { duplicatesString += QLatin1String("bug ") + m_allPossibleDuplicatesByQuery.at(i) + QLatin1String(", "); } duplicatesString = duplicatesString.left(duplicatesString.length()-2) + QLatin1Char('.'); report.append(QStringLiteral("Possible duplicates by query: %1\n").arg(duplicatesString)); } switch (stamp) { case DrKonqiStamp::Include: report.append(QLatin1String("\nReported using DrKonqi")); break; case DrKonqiStamp::Exclude: break; } return report; } QString ReportInterface::generateAttachmentComment() const { //Note: no translations should be done in this function's strings const CrashedApplication * crashedApp = DrKonqi::crashedApplication(); const SystemInformation * sysInfo = DrKonqi::systemInformation(); QString comment; //Program name and versions comment.append(QStringLiteral("%1 (%2) using Qt %4\n\n") .arg(crashedApp->fakeExecutableBaseName()) .arg(crashedApp->version()) .arg(sysInfo->qtVersion())); //Details of the crash situation if (isBugAwarenessPageDataUseful()) { comment.append(QStringLiteral("%1\n\n").arg(m_reportDetailText.trimmed())); } //Backtrace (only 6 lines) comment.append(QStringLiteral("-- Backtrace (Reduced):\n")); QString reducedBacktrace = DrKonqi::debuggerManager()->backtraceGenerator()->parser()->simplifiedBacktrace(); comment.append(reducedBacktrace.trimmed()); return comment; } Bugzilla::NewBug ReportInterface::newBugReportTemplate() const { const SystemInformation *sysInfo = DrKonqi::systemInformation(); Bugzilla::NewBug bug; bug.product = m_productMapping->bugzillaProduct(); bug.component = m_productMapping->bugzillaComponent(); bug.version = m_productMapping->bugzillaVersion(); bug.op_sys = sysInfo->bugzillaOperatingSystem(); if (sysInfo->compiledSources()) { bug.platform = QLatin1String("Compiled Sources"); } else { bug.platform = sysInfo->bugzillaPlatform(); } bug.keywords = QStringList { QStringLiteral("drkonqi") }; bug.priority = QLatin1String("NOR"); bug.severity = QLatin1String("crash"); bug.summary = m_reportTitle; return bug; } void ReportInterface::sendBugReport() { if (m_attachToBugNumber > 0) { //We are going to attach the report to an existent one connect(m_bugzillaManager, &BugzillaManager::addMeToCCFinished, this, &ReportInterface::attachBacktraceWithReport); connect(m_bugzillaManager, &BugzillaManager::addMeToCCError, this, &ReportInterface::sendReportError); //First add the user to the CC list, then attach m_bugzillaManager->addMeToCC(m_attachToBugNumber); } else { //Creating a new bug report bool attach = false; Bugzilla::NewBug report = newBugReportTemplate(); report.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Complete); // If the report is too long try to reduce it, try to not include the // backtrace and eventually give up. // Bugzilla has a hard-limit on the server side, if we cannot strip the // report down enough the submission will simply not work. // Exhausting the cap with just user input is nigh impossible, so we'll // forego handling of the report being too long even without without // backtrace. // https://bugs.kde.org/show_bug.cgi?id=248807 if (report.description.size() >= s_maxReportSize) { report.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Reduced); attach = true; } if (report.description.size() >= s_maxReportSize) { report.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Exclude); attach = true; } Q_ASSERT(!report.description.isEmpty()); connect(m_bugzillaManager, &BugzillaManager::sendReportErrorInvalidValues, this, &ReportInterface::sendUsingDefaultProduct); connect(m_bugzillaManager, &BugzillaManager::reportSent, this, [=](int bugId) { if (attach) { m_attachToBugNumber = bugId; attachBacktrace(QStringLiteral("DrKonqi auto-attaching complete backtrace.")); } else { emit reportSent(bugId); } }); connect(m_bugzillaManager, &BugzillaManager::sendReportError, this, &ReportInterface::sendReportError); m_bugzillaManager->sendReport(report); } } void ReportInterface::sendUsingDefaultProduct() const { //Fallback function: if some of the custom values fail, we need to reset all the fields to the default //(and valid) bugzilla values; and try to resend Bugzilla::NewBug bug = newBugReportTemplate(); bug.product = QLatin1String("kde"); bug.component = QLatin1String("general"); bug.platform = QLatin1String("unspecified"); bug.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Complete); m_bugzillaManager->sendReport(bug); } void ReportInterface::attachBacktraceWithReport() { attachBacktrace(generateAttachmentComment()); } void ReportInterface::attachBacktrace(const QString &comment) { //The user was added to the CC list, proceed with the attachment connect(m_bugzillaManager, &BugzillaManager::attachToReportSent, this, &ReportInterface::attachSent); connect(m_bugzillaManager, &BugzillaManager::attachToReportError, this, &ReportInterface::sendReportError); QString reportText = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Complete); QString filename = getSuggestedKCrashFilename(DrKonqi::crashedApplication()); QLatin1String summary("New crash information added by DrKonqi"); //Attach the report. The comment of the attachment also includes the bug description m_bugzillaManager->attachTextToReport(reportText, filename, summary, m_attachToBugNumber, comment); } void ReportInterface::attachSent(int attachId) { Q_UNUSED(attachId); //The bug was attached, consider it "sent" emit reportSent(m_attachToBugNumber); } QStringList ReportInterface::relatedBugzillaProducts() const { return m_productMapping->relatedBugzillaProducts(); } bool ReportInterface::isWorthReporting() const { if (DrKonqi::ignoreQuality()) { return true; } //Evaluate if the provided information is useful enough to enable the automatic report bool needToReport = false; if (!m_userRememberCrashSituation) { //This should never happen... but... return false; } int rating = selectedOptionsRating(); BacktraceParser::Usefulness use = DrKonqi::debuggerManager()->backtraceGenerator()->parser()->backtraceUsefulness(); switch (use) { case BacktraceParser::ReallyUseful: { //Perfect backtrace: require at least one option or a 100%-50% reproducible crash needToReport = (rating >=2) || (m_reproducible == ReproducibleEverytime || m_reproducible == ReproducibleSometimes); break; } case BacktraceParser::MayBeUseful: { //Not perfect backtrace: require at least two options or a 100% reproducible crash needToReport = (rating >=3) || (m_reproducible == ReproducibleEverytime); break; } case BacktraceParser::ProbablyUseless: //Bad backtrace: require at least two options and always reproducible (strict) needToReport = (rating >=5) && (m_reproducible == ReproducibleEverytime); break; case BacktraceParser::Useless: case BacktraceParser::InvalidUsefulness: { needToReport = false; } } return needToReport; } void ReportInterface::setAttachToBugNumber(uint bugNumber) { //If bugNumber>0, the report is going to be attached to bugNumber m_attachToBugNumber = bugNumber; } uint ReportInterface::attachToBugNumber() const { return m_attachToBugNumber; } void ReportInterface::setDuplicateId(uint duplicate) { m_duplicate = duplicate; } uint ReportInterface::duplicateId() const { return m_duplicate; } void ReportInterface::setPossibleDuplicatesByQuery(const QStringList & list) { m_allPossibleDuplicatesByQuery = list; } BugzillaManager * ReportInterface::bugzillaManager() const { return m_bugzillaManager; } ApplicationDetailsExamples * ReportInterface::appDetailsExamples() const { return m_appDetailsExamples; } diff --git a/src/kdbgwin/mingw_generator.cpp b/src/kdbgwin/mingw_generator.cpp index 9eb0eb13..7946bff1 100644 --- a/src/kdbgwin/mingw_generator.cpp +++ b/src/kdbgwin/mingw_generator.cpp @@ -1,168 +1,168 @@ /****************************************************************** * * kdbgwin - Helper application for DrKonqi * * This file is part of the KDE project * * Copyright (C) 2010 Ilie Halip * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see *****************************************************************/ #include "mingw_generator.h" #include #include MingwGenerator::MingwGenerator(const Process& process) : AbstractBTGenerator(process), file(NULL), func(NULL), line(0) {} struct MyBFD { QString module; bfd* abfd; asymbol** syms; MyBFD() : abfd(0), syms(0) {} MyBFD(const QString& module, bfd* abfd, asymbol** syms) { this->module = module; this->abfd = abfd; this->syms = syms; } bool operator==(const MyBFD& other) { return module == other.module; } }; typedef QList TBFDList; TBFDList bfds; asection* text = NULL; bool MingwGenerator::Init() { bfd_init(); return true; } void MingwGenerator::UnInit() { } void MingwGenerator::FrameChanged() { QString modPath = GetModulePath(); bool existsSymbol = false; TSymbolsMap::const_iterator i = m_symbolsMap.constFind(modPath); if (i == m_symbolsMap.cend()) { return; } MyBFD dummy(modPath, NULL, NULL); int pos = bfds.indexOf(dummy); if (pos == -1) { return; } MyBFD bfd = bfds[pos]; text = bfd_get_section_by_name(bfd.abfd, ".text"); long offset = m_currentFrame.AddrPC.Offset - text->vma; file = DEFAULT_FILE; func = DEFAULT_FUNC; line = DEFAULT_LINE; if (offset > 0) { bfd_find_nearest_line(bfd.abfd, text, bfd.syms, offset, &file, &func, (unsigned int*) &line); } } QString MingwGenerator::GetFunctionName() { if (func != NULL) { char* realname = abi::__cxa_demangle(func, NULL, NULL, NULL); if (realname != NULL) { QString strReturn = QString::fromLatin1(realname); free(realname); return strReturn; } else { return QString::fromLatin1(func); } } return QString::fromLatin1(DEFAULT_FUNC); } QString MingwGenerator::GetFile() { if (file != NULL) { return QString::fromLatin1(file); } return QString::fromLatin1(DEFAULT_FILE); } int MingwGenerator::GetLine() { if (line > 0) { return line; } return -1; } void MingwGenerator::LoadSymbol(const QString& module, DWORD64 dwBaseAddr) { QString symbolFile = module; - symbolFile.truncate(symbolFile.length() - 4); + symbolFile.chop(4); symbolFile.append(QStringLiteral(".sym")); m_symbolsMap[module] = false; // default QString symbolType; do { bfd* abfd = bfd_openr(symbolFile.toLatin1().data(), NULL); if (abfd == NULL) { symbolType = QString::fromLatin1("no symbols loaded"); break; } bfd_check_format(abfd, bfd_object); unsigned storage_needed = bfd_get_symtab_upper_bound(abfd); assert(storage_needed > 4); if (storage_needed <= 4) { // i don't know why the minimum value for this var is 4... symbolType = QString::fromLatin1("no symbols loaded"); break; } asymbol** syms = (asymbol **) malloc(storage_needed); assert(syms); if (syms == NULL) { symbolType = QString::fromLatin1("no symbols loaded"); break; } symbolType = QString::fromLatin1("symbols loaded"); m_symbolsMap[module] = true; bfds.push_back(MyBFD(module, abfd, syms)); } while (0); QString strOutput = QString::fromLatin1("Loaded %1 (%2)") .arg(module).arg(symbolType); emit DebugLine(strOutput); } diff --git a/src/parser/backtraceparser.cpp b/src/parser/backtraceparser.cpp index c25a2c72..a9a7c810 100644 --- a/src/parser/backtraceparser.cpp +++ b/src/parser/backtraceparser.cpp @@ -1,403 +1,403 @@ /* Copyright (C) 2009-2010 George Kiagiadakis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "backtraceparser_p.h" #include "backtraceparsergdb.h" #include "backtraceparserkdbgwin.h" #include "backtraceparserlldb.h" #include "backtraceparsercdb.h" #include "backtraceparsernull.h" #include "drkonqi_parser_debug.h" #include #include //factory BacktraceParser *BacktraceParser::newParser(const QString & debuggerName, QObject *parent) { if (debuggerName == QLatin1String("gdb")) { return new BacktraceParserGdb(parent); } else if (debuggerName == QLatin1String("kdbgwin")) { return new BacktraceParserKdbgwin(parent); } else if (debuggerName == QLatin1String("lldb")) { return new BacktraceParserLldb(parent); } else if (debuggerName == QLatin1String("cdb")) { return new BacktraceParserCdb(parent); } else { return new BacktraceParserNull(parent); } } BacktraceParser::BacktraceParser(QObject *parent) : QObject(parent), d_ptr(nullptr) {} BacktraceParser::~BacktraceParser() { delete d_ptr; } void BacktraceParser::connectToGenerator(QObject *generator) { connect(generator, SIGNAL(starting()), this, SLOT(resetState())); connect(generator, SIGNAL(newLine(QString)), this, SLOT(newLine(QString))); } QString BacktraceParser::parsedBacktrace() const { Q_D(const BacktraceParser); QString result; if (d) { for (QList::const_iterator i = d->m_linesList.constBegin(), total = d->m_linesList.constEnd(); i != total; ++i) { result += i->toString(); } } return result; } QList BacktraceParser::parsedBacktraceLines() const { Q_D(const BacktraceParser); return d ? d->m_linesList : QList(); } QString BacktraceParser::simplifiedBacktrace() const { Q_D(const BacktraceParser); //if there is no cached usefulness, the data calculation function has not run yet. if (d && d->m_usefulness == InvalidUsefulness) { const_cast(this)->calculateRatingData(); } //if there is no d, the debugger has not run yet, so we have no backtrace. return d ? d->m_simplifiedBacktrace : QString(); } BacktraceParser::Usefulness BacktraceParser::backtraceUsefulness() const { Q_D(const BacktraceParser); //if there is no cached usefulness, the data calculation function has not run yet. if (d && d->m_usefulness == InvalidUsefulness) { const_cast(this)->calculateRatingData(); } //if there is no d, the debugger has not run yet, //so we can say that the (inexistent) backtrace is Useless. return d ? d->m_usefulness : Useless; } QStringList BacktraceParser::firstValidFunctions() const { Q_D(const BacktraceParser); //if there is no cached usefulness, the data calculation function has not run yet. if (d && d->m_usefulness == InvalidUsefulness) { const_cast(this)->calculateRatingData(); } //if there is no d, the debugger has not run yet, so we have no functions to return. return d ? d->m_firstUsefulFunctions : QStringList(); } QSet BacktraceParser::librariesWithMissingDebugSymbols() const { Q_D(const BacktraceParser); //if there is no cached usefulness, the data calculation function has not run yet. if (d && d->m_usefulness == InvalidUsefulness) { const_cast(this)->calculateRatingData(); } //if there is no d, the debugger has not run yet, so we have no libraries. return d ? d->m_librariesWithMissingDebugSymbols : QSet(); } void BacktraceParser::resetState() { //reset the state of the parser by getting a new instance of Private delete d_ptr; d_ptr = constructPrivate(); } BacktraceParserPrivate *BacktraceParser::constructPrivate() const { return new BacktraceParserPrivate; } /* This function returns true if the given stack frame line is the base of the backtrace and thus the parser should not rate any frames below that one. */ static bool lineIsStackBase(const BacktraceLine & line) { //optimization. if there is no function name, do not bother to check it if ( line.rating() == BacktraceLine::MissingEverything || line.rating() == BacktraceLine::MissingFunction ) return false; //this is the base frame for all threads except the main thread //FIXME that probably works only on linux if ( line.functionName() == QLatin1String("start_thread") ) return true; QRegExp regExp; regExp.setPattern(QStringLiteral("(kde)?main")); //main() or kdemain() is the base for the main thread if ( regExp.exactMatch(line.functionName()) ) return true; //HACK for better rating. we ignore all stack frames below any function that matches //the following regular expression. The functions that match this expression are usually //"QApplicationPrivate::notify_helper", "QApplication::notify" and similar, which //are used to send any kind of event to the Qt application. All stack frames below this, //with or without debug symbols, are useless to KDE developers, so we ignore them. regExp.setPattern(QStringLiteral("(Q|K)(Core)?Application(Private)?::notify.*")); if ( regExp.exactMatch(line.functionName()) ) return true; //attempt to recognize crashes that happen after main has returned (bug 200993) if ( line.functionName() == QLatin1String("~KCleanUpGlobalStatic") || line.functionName() == QLatin1String("~QGlobalStatic") || line.functionName() == QLatin1String("exit") || line.functionName() == QLatin1String("*__GI_exit") ) return true; return false; } /* This function returns true if the given stack frame line is the top of the bactrace and thus the parser should not rate any frames above that one. This is used to avoid rating the stack frames of abort(), assert(), Q_ASSERT() and qCCritical(DRKONQI_PARSER_LOG) */ static bool lineIsStackTop(const BacktraceLine & line) { //optimization. if there is no function name, do not bother to check it if ( line.rating() == BacktraceLine::MissingEverything || line.rating() == BacktraceLine::MissingFunction ) return false; if ( line.functionName().startsWith(QLatin1String("qt_assert")) //qt_assert and qt_assert_x || line.functionName() == QLatin1String("qFatal") || line.functionName() == QLatin1String("abort") || line.functionName() == QLatin1String("*__GI_abort") || line.functionName() == QLatin1String("*__GI___assert_fail") ) return true; return false; } /* This function returns true if the given stack frame line should be ignored from rating for some reason. Currently it ignores all libc/libstdc++/libpthread functions. */ static bool lineShouldBeIgnored(const BacktraceLine & line) { - if ( line.libraryName().contains(QStringLiteral("libc.so")) - || line.libraryName().contains(QStringLiteral("libstdc++.so")) + if ( line.libraryName().contains(QLatin1String("libc.so")) + || line.libraryName().contains(QLatin1String("libstdc++.so")) || line.functionName().startsWith(QLatin1String("*__GI_")) //glibc2.9 uses *__GI_ as prefix - || line.libraryName().contains(QStringLiteral("libpthread.so")) - || line.libraryName().contains(QStringLiteral("libglib-2.0.so")) + || line.libraryName().contains(QLatin1String("libpthread.so")) + || line.libraryName().contains(QLatin1String("libglib-2.0.so")) #ifdef Q_OS_MACOS - || (line.libraryName().startsWith(QStringLiteral("libsystem_")) && line.libraryName().endsWith(QStringLiteral(".dylib"))) - || line.libraryName().contains(QStringLiteral("Foundation`")) + || (line.libraryName().startsWith(QLatin1String("libsystem_")) && line.libraryName().endsWith(QLatin1String(".dylib"))) + || line.libraryName().contains(QLatin1String("Foundation`")) #endif - || line.libraryName().contains(QStringLiteral("ntdll.dll")) - || line.libraryName().contains(QStringLiteral("kernel32.dll")) - || line.functionName().contains(QStringLiteral("_tmain")) + || line.libraryName().contains(QLatin1String("ntdll.dll")) + || line.libraryName().contains(QLatin1String("kernel32.dll")) + || line.functionName().contains(QLatin1String("_tmain")) || line.functionName() == QLatin1String("WinMain") ) return true; return false; } static bool isFunctionUseful(const BacktraceLine & line) { //We need the function name if ( line.rating() == BacktraceLine::MissingEverything || line.rating() == BacktraceLine::MissingFunction ) { return false; } //Misc ignores if ( line.functionName() == QLatin1String("__kernel_vsyscall") || line.functionName() == QLatin1String("raise") || line.functionName() == QLatin1String("abort") || line.functionName() == QLatin1String("__libc_message") || line.functionName() == QLatin1String("thr_kill") /* *BSD */) { return false; } //Ignore core Qt functions //(QObject can be useful in some cases) if ( line.functionName().startsWith(QLatin1String("QBasicAtomicInt::")) || line.functionName().startsWith(QLatin1String("QBasicAtomicPointer::")) || line.functionName().startsWith(QLatin1String("QAtomicInt::")) || line.functionName().startsWith(QLatin1String("QAtomicPointer::")) || line.functionName().startsWith(QLatin1String("QMetaObject::")) || line.functionName().startsWith(QLatin1String("QPointer::")) || line.functionName().startsWith(QLatin1String("QWeakPointer::")) || line.functionName().startsWith(QLatin1String("QSharedPointer::")) || line.functionName().startsWith(QLatin1String("QScopedPointer::")) || line.functionName().startsWith(QLatin1String("QMetaCallEvent::")) ) { return false; } //Ignore core Qt containers misc functions if ( line.functionName().endsWith(QLatin1String("detach")) || line.functionName().endsWith(QLatin1String("detach_helper")) || line.functionName().endsWith(QLatin1String("node_create")) || line.functionName().endsWith(QLatin1String("deref")) || line.functionName().endsWith(QLatin1String("ref")) || line.functionName().endsWith(QLatin1String("node_copy")) || line.functionName().endsWith(QLatin1String("d_func")) ) { return false; } //Misc Qt stuff if ( line.functionName() == QLatin1String("qt_message_output") || line.functionName() == QLatin1String("qt_message") || line.functionName() == QLatin1String("qFatal") || line.functionName().startsWith(QLatin1String("qGetPtrHelper")) || line.functionName().startsWith(QLatin1String("qt_meta_")) ) { return false; } return true; } static bool isFunctionUsefulForSearch(const BacktraceLine & line) { //Ignore Qt containers (and iterators Q*Iterator) if ( line.functionName().startsWith(QLatin1String("QList")) || line.functionName().startsWith(QLatin1String("QLinkedList")) || line.functionName().startsWith(QLatin1String("QVector")) || line.functionName().startsWith(QLatin1String("QStack")) || line.functionName().startsWith(QLatin1String("QQueue")) || line.functionName().startsWith(QLatin1String("QSet")) || line.functionName().startsWith(QLatin1String("QMap")) || line.functionName().startsWith(QLatin1String("QMultiMap")) || line.functionName().startsWith(QLatin1String("QMapData")) || line.functionName().startsWith(QLatin1String("QHash")) || line.functionName().startsWith(QLatin1String("QMultiHash")) || line.functionName().startsWith(QLatin1String("QHashData")) ) { return false; } return true; } void BacktraceParser::calculateRatingData() { Q_D(BacktraceParser); uint rating = 0, bestPossibleRating = 0, counter = 0; bool haveSeenStackBase = false; QListIterator i(d->m_linesToRate); i.toBack(); //start from the end of the list while( i.hasPrevious() ) { const BacktraceLine & line = i.previous(); if ( !i.hasPrevious() && line.rating() == BacktraceLine::MissingEverything ) { //Under some circumstances, the very first stack frame is invalid (ex, calling a function //at an invalid address could result in a stack frame like "0x00000000 in ?? ()"), //which however does not necessarily mean that the backtrace has a missing symbol on //the first line. Here we make sure to ignore this line from rating. (bug 190882) break; //there are no more items anyway, just break the loop } if ( lineIsStackBase(line) ) { rating = bestPossibleRating = counter = 0; //restart rating ignoring any previous frames haveSeenStackBase = true; } else if ( lineIsStackTop(line) ) { break; //we have reached the top, no need to inspect any more frames } if ( lineShouldBeIgnored(line) ) { continue; } if ( line.rating() == BacktraceLine::MissingFunction || line.rating() == BacktraceLine::MissingSourceFile) { d->m_librariesWithMissingDebugSymbols.insert(line.libraryName().trimmed()); } uint multiplier = ++counter; //give weight to the first lines rating += static_cast(line.rating()) * multiplier; bestPossibleRating += static_cast(BacktraceLine::BestRating) * multiplier; qCDebug(DRKONQI_PARSER_LOG) << line.rating() << line.toString(); } //Generate a simplified backtrace //- Starts from the first useful function //- Max of 5 lines //- Replaces garbage with [...] //At the same time, grab the first three useful functions for search queries i.toFront(); //Reuse the list iterator int functionIndex = 0; int usefulFunctionsCount = 0; bool firstUsefulFound = false; while( i.hasNext() && functionIndex < 5 ) { const BacktraceLine & line = i.next(); if ( !lineShouldBeIgnored(line) && isFunctionUseful(line) ) { //Line is not garbage to use if (!firstUsefulFound) { firstUsefulFound = true; } //Save simplified backtrace line d->m_simplifiedBacktrace += line.toString(); //Fetch three useful functions (only functionName) for search queries if (usefulFunctionsCount < 3 && isFunctionUsefulForSearch(line) && !d->m_firstUsefulFunctions.contains(line.functionName())) { d->m_firstUsefulFunctions.append(line.functionName()); usefulFunctionsCount++; } functionIndex++; } else if (firstUsefulFound) { //Add "[...]" if there are invalid functions in the middle if (!d->m_simplifiedBacktrace.endsWith(QLatin1String("[...]\n"))) { d->m_simplifiedBacktrace += QLatin1String("[...]\n"); } } } //calculate rating d->m_usefulness = Useless; if (rating >= (bestPossibleRating*0.90)) { d->m_usefulness = ReallyUseful; } else if (rating >= (bestPossibleRating*0.70)) { d->m_usefulness = MayBeUseful; } else if (rating >= (bestPossibleRating*0.40)) { d->m_usefulness = ProbablyUseless; } //if there is no stack base, the executable is probably stripped, //so we need to be more strict with rating if ( !haveSeenStackBase ) { //less than 4 stack frames is useless if ( counter < 4 ) { d->m_usefulness = Useless; //more than 4 stack frames might have some value, so let's not be so strict, just lower the rating } else if ( d->m_usefulness > Useless ) { d->m_usefulness = (Usefulness) (d->m_usefulness - 1); } } qCDebug(DRKONQI_PARSER_LOG) << "Rating:" << rating << "out of" << bestPossibleRating << "Usefulness:" << staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("Usefulness")).valueToKey(d->m_usefulness); qCDebug(DRKONQI_PARSER_LOG) << "90%:" << (bestPossibleRating*0.90) << "70%:" << (bestPossibleRating*0.70) << "40%:" << (bestPossibleRating*0.40); qCDebug(DRKONQI_PARSER_LOG) << "Have seen stack base:" << haveSeenStackBase << "Lines counted:" << counter; } diff --git a/src/parser/backtraceparsergdb.cpp b/src/parser/backtraceparsergdb.cpp index 1a58c006..82f660ce 100644 --- a/src/parser/backtraceparsergdb.cpp +++ b/src/parser/backtraceparsergdb.cpp @@ -1,294 +1,294 @@ /* Copyright (C) 2009-2010 George Kiagiadakis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "backtraceparsergdb.h" #include "backtraceparser_p.h" #include "drkonqi_parser_debug.h" #include //BEGIN BacktraceLineGdb class BacktraceLineGdb : public BacktraceLine { public: BacktraceLineGdb(const QString & line); private: void parse(); void rate(); }; BacktraceLineGdb::BacktraceLineGdb(const QString & lineStr) : BacktraceLine() { d->m_line = lineStr; d->m_functionName = QLatin1String("??"); parse(); if (d->m_type == StackFrame) { rate(); } } void BacktraceLineGdb::parse() { QRegExp regExp; - if (d->m_line == QLatin1String("\n")) { + if (d->m_line == QLatin1Char('\n')) { d->m_type = EmptyLine; return; } else if (d->m_line == QLatin1String("[KCrash Handler]\n")) { d->m_type = KCrash; return; - } else if (d->m_line.contains(QStringLiteral(""))) { + } else if (d->m_line.contains(QLatin1String(""))) { d->m_type = SignalHandlerStart; return; } regExp.setPattern(QStringLiteral("^#([0-9]+)" //matches the stack frame number, ex. "#0" "[\\s]+(0x[0-9a-f]+[\\s]+in[\\s]+)?" // matches " 0x0000dead in " (optionally) "((\\(anonymous namespace\\)::)?[^\\(]+)" //matches the function name //(anything except left parenthesis, which is the start of the arguments section) //and optionally the prefix "(anonymous namespace)::" "(\\(.*\\))?" //matches the function arguments //(when the app doesn't have debugging symbols) "[\\s]+(const[\\s]+)?" //matches a traling const, if it exists "\\(.*\\)" //matches the arguments of the function with their values //(when the app has debugging symbols) "([\\s]+" //beginning of optional file information "(from|at)[\\s]+" //matches "from " or "at " "(.+)" //matches the filename (source file or shared library file) ")?\n$")); //matches trailing newline. //the )? at the end closes the parenthesis before [\\s]+(from|at) and //notes that the whole expression from there is optional. if (regExp.exactMatch(d->m_line)) { d->m_type = StackFrame; d->m_stackFrameNumber = regExp.cap(1).toInt(); d->m_functionName = regExp.cap(3).trimmed(); if (!regExp.cap(7).isEmpty()) { //we have file information (stuff after from|at) if (regExp.cap(8) == QLatin1String("at")) { //'at' means we have a source file d->m_file = regExp.cap(9); } else { //'from' means we have a library d->m_library = regExp.cap(9); } } qCDebug(DRKONQI_PARSER_LOG) << d->m_stackFrameNumber << d->m_functionName << d->m_file << d->m_library; return; } regExp.setPattern(QStringLiteral(".*\\(no debugging symbols found\\).*|" ".*\\[Thread debugging using libthread_db enabled\\].*|" ".*\\[New .*|" "0x[0-9a-f]+.*|" "Current language:.*")); if (regExp.exactMatch(d->m_line)) { qCDebug(DRKONQI_PARSER_LOG) << "garbage detected:" << d->m_line; d->m_type = Crap; return; } regExp.setPattern(QStringLiteral("Thread [0-9]+\\s+\\(Thread [0-9a-fx]+\\s+\\(.*\\)\\):\n")); if (regExp.exactMatch(d->m_line)) { qCDebug(DRKONQI_PARSER_LOG) << "thread start detected:" << d->m_line; d->m_type = ThreadStart; return; } regExp.setPattern(QStringLiteral("\\[Current thread is [0-9]+ \\(.*\\)\\]\n")); if (regExp.exactMatch(d->m_line)) { qCDebug(DRKONQI_PARSER_LOG) << "thread indicator detected:" << d->m_line; d->m_type = ThreadIndicator; return; } qCDebug(DRKONQI_PARSER_LOG) << "line" << d->m_line << "did not match"; } void BacktraceLineGdb::rate() { LineRating r; //for explanations, see the LineRating enum definition if (!fileName().isEmpty()) { r = Good; } else if (!libraryName().isEmpty()) { if (functionName() == QLatin1String("??")) { r = MissingFunction; } else { r = MissingSourceFile; } } else { if (functionName() == QLatin1String("??")) { r = MissingEverything; } else { r = MissingLibrary; } } d->m_rating = r; } //END BacktraceLineGdb //BEGIN BacktraceParserGdb class BacktraceParserGdbPrivate : public BacktraceParserPrivate { public: BacktraceParserGdbPrivate() : BacktraceParserPrivate(), m_possibleKCrashStart(0), m_threadsCount(0), m_isBelowSignalHandler(false), m_frameZeroAppeared(false) {} QString m_lineInputBuffer; int m_possibleKCrashStart; int m_threadsCount; bool m_isBelowSignalHandler; bool m_frameZeroAppeared; }; BacktraceParserGdb::BacktraceParserGdb(QObject *parent) : BacktraceParser(parent) { } BacktraceParserPrivate* BacktraceParserGdb::constructPrivate() const { return new BacktraceParserGdbPrivate; } void BacktraceParserGdb::newLine(const QString & lineStr) { Q_D(BacktraceParserGdb); //when the line is too long, gdb splits it into two lines. //This breaks parsing and results in two Unknown lines instead of a StackFrame one. //Here we workaround this by joining the two lines when such a scenario is detected. if (d->m_lineInputBuffer.isEmpty()) { d->m_lineInputBuffer = lineStr; } else if (lineStr.startsWith(QLatin1Char(' ')) || lineStr.startsWith(QLatin1Char('\t'))) { //gdb always adds some whitespace at the beginning of the second line d->m_lineInputBuffer.append(lineStr); } else { parseLine(d->m_lineInputBuffer); d->m_lineInputBuffer = lineStr; } } void BacktraceParserGdb::parseLine(const QString & lineStr) { Q_D(BacktraceParserGdb); BacktraceLineGdb line(lineStr); switch (line.type()) { case BacktraceLine::Crap: break; //we don't want crap in the backtrace ;) case BacktraceLine::ThreadStart: d->m_linesList.append(line); d->m_possibleKCrashStart = d->m_linesList.size(); d->m_threadsCount++; //reset the state of the flags that need to be per-thread d->m_isBelowSignalHandler = false; d->m_frameZeroAppeared = false; // gdb bug workaround flag, see below break; case BacktraceLine::SignalHandlerStart: if (!d->m_isBelowSignalHandler) { //replace the stack frames of KCrash with a nice message d->m_linesList.erase(d->m_linesList.begin() + d->m_possibleKCrashStart, d->m_linesList.end()); d->m_linesList.insert(d->m_possibleKCrashStart, BacktraceLineGdb(QStringLiteral("[KCrash Handler]\n"))); d->m_isBelowSignalHandler = true; //next line is the first below the signal handler } else { //this is not the first time we see a crash handler frame on the same thread, //so we just add it to the list d->m_linesList.append(line); } break; case BacktraceLine::StackFrame: // gdb workaround - (v6.8 at least) - 'thread apply all bt' writes // the #0 stack frame again at the end. // Here we ignore this frame by using a flag that tells us whether // this is the first or the second time that the #0 frame appears in this thread. // The flag is cleared on each thread start. if (line.frameNumber() == 0) { if (d->m_frameZeroAppeared) { break; //break from the switch so that the frame is not added to the list. } else { d->m_frameZeroAppeared = true; } } //rate the stack frame if we are below the signal handler if (d->m_isBelowSignalHandler) { d->m_linesToRate.append(line); } Q_FALLTHROUGH(); //fall through and append the line to the list default: d->m_linesList.append(line); break; } } QString BacktraceParserGdb::parsedBacktrace() const { Q_D(const BacktraceParserGdb); QString result; if (d) { QList::const_iterator i; for (i = d->m_linesList.constBegin(); i != d->m_linesList.constEnd(); ++i) { //if there is only one thread, we can omit the thread indicator, //the thread header and all the empty lines. if (d->m_threadsCount == 1 && ((*i).type() == BacktraceLine::ThreadIndicator || (*i).type() == BacktraceLine::ThreadStart || (*i).type() == BacktraceLine::EmptyLine)) { continue; } result += i->toString(); } } return result; } QList BacktraceParserGdb::parsedBacktraceLines() const { Q_D(const BacktraceParserGdb); QList result; if (d) { QList::const_iterator i; for (i = d->m_linesList.constBegin(); i != d->m_linesList.constEnd(); ++i) { //if there is only one thread, we can omit the thread indicator, //the thread header and all the empty lines. if (d->m_threadsCount == 1 && ((*i).type() == BacktraceLine::ThreadIndicator || (*i).type() == BacktraceLine::ThreadStart || (*i).type() == BacktraceLine::EmptyLine)) { continue; } result.append(*i); } } return result; } //END BacktraceParserGdb diff --git a/src/parser/backtraceparserkdbgwin.cpp b/src/parser/backtraceparserkdbgwin.cpp index 4de2ec25..75708ef7 100644 --- a/src/parser/backtraceparserkdbgwin.cpp +++ b/src/parser/backtraceparserkdbgwin.cpp @@ -1,127 +1,127 @@ /* Copyright (C) 2010 George Kiagiadakis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "backtraceparserkdbgwin.h" #include "backtraceparser_p.h" #include "drkonqi_parser_debug.h" //BEGIN BacktraceLineKdbgwin class BacktraceLineKdbgwin : public BacktraceLine { public: BacktraceLineKdbgwin(const QString & line); private: void parse(); void rate(); }; BacktraceLineKdbgwin::BacktraceLineKdbgwin(const QString & line) : BacktraceLine() { d->m_line = line; parse(); if (d->m_type == StackFrame) { rate(); } } void BacktraceLineKdbgwin::parse() { - if (d->m_line == QLatin1String("\n")) { + if (d->m_line == QLatin1Char('\n')) { d->m_type = EmptyLine; return; } else if (d->m_line == QLatin1String("[KCrash Handler]\n")) { d->m_type = KCrash; return; } else if (d->m_line.startsWith(QLatin1String("Loaded"))) { d->m_type = Crap; //FIXME that's not exactly crap return; } QRegExp regExp; regExp.setPattern(QStringLiteral("([^!]+)!" //match the module name, followed by ! "([^\\(]+)\\(\\) " //match the function name, followed by () "\\[([^@]+)@ [\\-\\d]+\\] " // [filename @ line] "at 0x.*")); //at 0xdeadbeef if (regExp.exactMatch(d->m_line)) { d->m_type = StackFrame; d->m_library = regExp.cap(1); d->m_functionName = regExp.cap(2); d->m_file = regExp.cap(3).trimmed(); qCDebug(DRKONQI_PARSER_LOG) << d->m_functionName << d->m_file << d->m_library; return; } qCDebug(DRKONQI_PARSER_LOG) << "line" << d->m_line << "did not match"; } void BacktraceLineKdbgwin::rate() { LineRating r; //for explanations, see the LineRating enum definition if (fileName() != QLatin1String("[unknown]")) { r = Good; } else if (libraryName() != QLatin1String("[unknown]")) { if (functionName() == QLatin1String("[unknown]")) { r = MissingFunction; } else { r = MissingSourceFile; } } else { if (functionName() == QLatin1String("[unknown]")) { r = MissingEverything; } else { r = MissingLibrary; } } d->m_rating = r; } //END BacktraceLineKdbgwin //BEGIN BacktraceParserKdbgwin BacktraceParserKdbgwin::BacktraceParserKdbgwin(QObject *parent) : BacktraceParser(parent) { } void BacktraceParserKdbgwin::newLine(const QString & lineStr) { Q_D(BacktraceParser); BacktraceLineKdbgwin line(lineStr); switch(line.type()) { case BacktraceLine::Crap: break; //we don't want crap in the backtrace ;) case BacktraceLine::StackFrame: d->m_linesToRate.append(line); Q_FALLTHROUGH(); default: d->m_linesList.append(line); } } //END BacktraceParserKdbgwin diff --git a/src/systeminformation.cpp b/src/systeminformation.cpp index 1a7c8b11..bd0b5adc 100644 --- a/src/systeminformation.cpp +++ b/src/systeminformation.cpp @@ -1,291 +1,291 @@ /******************************************************************* * systeminformation.cpp * Copyright 2009 Dario Andres Rodriguez * Copyright 2009 George Kiagiadakis * Copyright 2019 Harald Sitter * * modify it under the terms of the GNU General Public License as * This program is free software; you can redistribute it and/or * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #include #include "systeminformation.h" #if HAVE_UNAME # include # include #endif #include #include "drkonqi_debug.h" #include #include #include #include #include #include #include static const QString OS_UNSPECIFIED = QStringLiteral("unspecified"); static const QString PLATFORM_UNSPECIFIED = QStringLiteral("unspecified"); // This function maps the operating system to an OS value that is // accepted by bugs.kde.org. If the values change on the server // side, they need to be updated here as well. static QString fetchBasicOperatingSystem() { // krazy:excludeall=cpp // Get the base OS string (bugzillaOS) #if defined(Q_OS_LINUX) return QStringLiteral("Linux"); #elif defined(Q_OS_FREEBSD) return QStringLiteral("FreeBSD"); #elif defined(Q_OS_NETBSD) return QStringLiteral("NetBSD"); #elif defined(Q_OS_OPENBSD) return QStringLiteral("OpenBSD"); #elif defined(Q_OS_AIX) return QStringLiteral("AIX"); #elif defined(Q_OS_HPUX) return QStringLiteral("HP-UX"); #elif defined(Q_OS_IRIX) return QStringLiteral("IRIX"); #elif defined(Q_OS_OSF) return QStringLiteral("Tru64"); #elif defined(Q_OS_SOLARIS) return QStringLiteral("Solaris"); #elif defined(Q_OS_CYGWIN) return QStringLiteral("Cygwin"); #elif defined(Q_OS_DARWIN) return QStringLiteral("OS X"); #elif defined(Q_OS_WIN32) return QStringLiteral("MS Windows"); #else return OS_UNSPECIFIED; #endif } SystemInformation::Config::Config() : basicOperatingSystem(fetchBasicOperatingSystem()) , lsbReleasePath(QStandardPaths::findExecutable(QStringLiteral("lsb_release"))) , osReleasePath(/* Use KOSRelease default */) { } SystemInformation::SystemInformation(Config infoConfig, QObject *parent) : QObject(parent) , m_bugzillaOperatingSystem(infoConfig.basicOperatingSystem) , m_bugzillaPlatform(PLATFORM_UNSPECIFIED) , m_complete(false) , m_infoConfig(infoConfig) { // NOTE: order matters. These require m_bugzillaOperatingSystem to be set! m_operatingSystem = fetchOSDetailInformation(); tryToSetBugzillaPlatform(); KConfigGroup config(KSharedConfig::openConfig(), "SystemInformation"); m_compiledSources = config.readEntry("CompiledSources", false); } SystemInformation::~SystemInformation() { KConfigGroup config(KSharedConfig::openConfig(), "SystemInformation"); config.writeEntry("CompiledSources", m_compiledSources); config.sync(); } void SystemInformation::tryToSetBugzillaPlatform() { QString platform = PLATFORM_UNSPECIFIED; // first, try to guess bugzilla platfrom from the internal OS information // this should work for BSDs, solaris and windows. platform = guessBugzillaPlatform(m_bugzillaOperatingSystem); // if the internal information is not enough, refer to external information if (platform == PLATFORM_UNSPECIFIED) { tryToSetBugzillaPlatformFromExternalInfo(); } else { setBugzillaPlatform(platform); } } void SystemInformation::tryToSetBugzillaPlatformFromExternalInfo() { //Run lsb_release async QString lsb_release = m_infoConfig.lsbReleasePath; if (!lsb_release.isEmpty()) { qCDebug(DRKONQI_LOG) << "found lsb_release"; KProcess *process = new KProcess(); process->setOutputChannelMode(KProcess::OnlyStdoutChannel); process->setEnv(QStringLiteral("LC_ALL"), QStringLiteral("C")); *process << lsb_release << QStringLiteral("-sd"); connect(process, static_cast(&KProcess::finished), this, &SystemInformation::lsbReleaseFinished); process->start(); } else { // when lsb_release is unavailable, turn to /etc/os-release const QString& osReleaseInfo = fetchOSReleaseInformation(); const QString& platform = guessBugzillaPlatform(osReleaseInfo); setBugzillaPlatform(platform); m_complete = true; } } void SystemInformation::lsbReleaseFinished() { KProcess *process = qobject_cast(sender()); Q_ASSERT(process); m_distributionPrettyName = QString::fromLocal8Bit(process->readAllStandardOutput().trimmed()); process->deleteLater(); //Guess distro string QString platform = guessBugzillaPlatform(m_distributionPrettyName); // if lsb_release doesn't work well, turn to the /etc/os-release file if (platform == PLATFORM_UNSPECIFIED) { const QString& osReleaseInfo = fetchOSReleaseInformation(); platform = guessBugzillaPlatform(osReleaseInfo); } setBugzillaPlatform(platform); m_complete = true; } //this function maps the distribution information to an "Hardware Platform" . //value that is accepted by bugs.kde.org. If the values change on the server . //side, they need to be updated here as well . QString SystemInformation::guessBugzillaPlatform(const QString& distroInfo) const { static QHash platforms { { QStringLiteral("suse"), QStringLiteral("openSUSE RPMs") }, { QStringLiteral("mint"), QStringLiteral("Mint (Ubuntu Based)") }, { QStringLiteral("lmde"), QStringLiteral("Mint (Debian Based)") }, { QStringLiteral("ubuntu"), QStringLiteral("Ubuntu Packages") }, { QStringLiteral("fedora"), QStringLiteral("Fedora RPMs") }, { QStringLiteral("redhat"), QStringLiteral("RedHat RPMs") }, { QStringLiteral("gentoo"), QStringLiteral("Gentoo Packages") }, { QStringLiteral("mandriva"), QStringLiteral("Mandriva RPMs") }, { QStringLiteral("mageia"), QStringLiteral("Mageia RPMs") }, { QStringLiteral("slack"), QStringLiteral("Slackware Packages") }, { QStringLiteral("pclinuxos"), QStringLiteral("PCLinuxOS") }, { QStringLiteral("pardus"), QStringLiteral("Pardus Packages") }, { QStringLiteral("freebsd"), QStringLiteral("FreeBSD Ports") }, { QStringLiteral("netbsd"), QStringLiteral("NetBSD pkgsrc") }, { QStringLiteral("openbsd"), QStringLiteral("OpenBSD Packages") }, { QStringLiteral("solaris"), QStringLiteral("Solaris Packages") }, { QStringLiteral("chakra"), QStringLiteral("Chakra") }, { QStringLiteral("ms windows"), QStringLiteral("MS Windows") }, { QStringLiteral("arch"), QStringLiteral("Archlinux Packages") } }; for (auto it = platforms.constBegin(); it != platforms.constEnd(); ++it) { if (distroInfo.contains(it.key(), Qt::CaseInsensitive)) { return it.value(); } } // Debian has multiple platforms. - if (distroInfo.contains(QStringLiteral("debian"), Qt::CaseInsensitive)) { - if (distroInfo.contains(QStringLiteral("unstable"), Qt::CaseInsensitive)) { + if (distroInfo.contains(QLatin1String("debian"), Qt::CaseInsensitive)) { + if (distroInfo.contains(QLatin1String("unstable"), Qt::CaseInsensitive)) { return QStringLiteral("Debian unstable"); - } else if (distroInfo.contains(QStringLiteral("testing"), Qt::CaseInsensitive)) { + } else if (distroInfo.contains(QLatin1String("testing"), Qt::CaseInsensitive)) { return QStringLiteral("Debian testing"); } else { return QStringLiteral("Debian stable"); } } return PLATFORM_UNSPECIFIED; } QString SystemInformation::fetchOSDetailInformation() const { //Get complete OS string (and fallback to base string) QString operatingSystem = m_bugzillaOperatingSystem; #if HAVE_UNAME struct utsname buf; auto unameFunc = &uname; if (m_infoConfig.unameFunc) { unameFunc = (int (*)(utsname*))m_infoConfig.unameFunc; } if ((*unameFunc)(&buf) == -1) { qCDebug(DRKONQI_LOG) << "call to uname failed" << errno; } else { operatingSystem = QString::fromLocal8Bit(buf.sysname) + QLatin1Char(' ') + QString::fromLocal8Bit(buf.release) + QLatin1Char(' ') + QString::fromLocal8Bit(buf.machine); } #endif return operatingSystem; } QString SystemInformation::fetchOSReleaseInformation() { KOSRelease os(m_infoConfig.osReleasePath); return m_distributionPrettyName = os.prettyName(); } QString SystemInformation::operatingSystem() const { return m_operatingSystem; } QString SystemInformation::bugzillaOperatingSystem() const { return m_bugzillaOperatingSystem; } QString SystemInformation::bugzillaPlatform() const { return m_bugzillaPlatform; } void SystemInformation::setBugzillaPlatform(const QString & platform) { m_bugzillaPlatform = platform; } QString SystemInformation::distributionPrettyName() const { return m_distributionPrettyName; } bool SystemInformation::compiledSources() const { return m_compiledSources; } void SystemInformation::setCompiledSources(bool compiled) { m_compiledSources = compiled; } QString SystemInformation::qtVersion() const { return QString::fromLatin1(qVersion()); } QString SystemInformation::frameworksVersion() const { return KCoreAddons::versionString(); } bool SystemInformation::complete() const { return m_complete; } diff --git a/src/tests/backtraceparsertest/backtraceparsertest.cpp b/src/tests/backtraceparsertest/backtraceparsertest.cpp index 6ee9c2a3..9857574f 100644 --- a/src/tests/backtraceparsertest/backtraceparsertest.cpp +++ b/src/tests/backtraceparsertest/backtraceparsertest.cpp @@ -1,124 +1,124 @@ /* Copyright (C) 2009 George Kiagiadakis This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "backtraceparsertest.h" #include #include #define DATA_DIR QFINDTESTDATA("backtraceparsertest_data") BacktraceParserTest::BacktraceParserTest(QObject *parent) : QObject(parent), m_settings(DATA_DIR + QLatin1Char('/') + QStringLiteral("data.ini"), QSettings::IniFormat), m_generator(new FakeBacktraceGenerator(this)) { } void BacktraceParserTest::fetchData(const QString & group) { QTest::addColumn("filename"); QTest::addColumn("result"); QTest::addColumn("debugger"); m_settings.beginGroup(group); QStringList keys = m_settings.allKeys(); m_settings.endGroup(); foreach(const QString & key, keys) { QTest::newRow(qPrintable(key)) << QString(DATA_DIR + QLatin1Char('/') + key) << m_settings.value(group + QLatin1Char('/') + key).toString() << m_settings.value(QStringLiteral("debugger/") + key).toString(); } } void BacktraceParserTest::btParserUsefulnessTest_data() { fetchData(QStringLiteral("usefulness")); } void BacktraceParserTest::btParserUsefulnessTest() { QFETCH(QString, filename); QFETCH(QString, result); QFETCH(QString, debugger); //parse QSharedPointer parser(BacktraceParser::newParser(debugger)); parser->connectToGenerator(m_generator); m_generator->sendData(filename); //convert usefulness to string QMetaEnum metaUsefulness = BacktraceParser::staticMetaObject.enumerator( BacktraceParser::staticMetaObject.indexOfEnumerator("Usefulness")); QString btUsefulness = QString::fromLatin1(metaUsefulness.valueToKey(parser->backtraceUsefulness())); //compare QEXPECT_FAIL("test_e", "Working on it", Continue); QCOMPARE(btUsefulness, result); } void BacktraceParserTest::btParserFunctionsTest_data() { fetchData(QStringLiteral("firstValidFunctions")); } void BacktraceParserTest::btParserFunctionsTest() { QFETCH(QString, filename); QFETCH(QString, result); QFETCH(QString, debugger); //parse QSharedPointer parser(BacktraceParser::newParser(debugger)); parser->connectToGenerator(m_generator); m_generator->sendData(filename); //compare - QString functions = parser->firstValidFunctions().join(QStringLiteral("|")); + QString functions = parser->firstValidFunctions().join(QLatin1Char('|')); QCOMPARE(functions, result); } void BacktraceParserTest::btParserBenchmark_data() { QTest::addColumn("filename"); QTest::addColumn("debugger"); m_settings.beginGroup(QStringLiteral("debugger")); QStringList keys = m_settings.allKeys(); foreach(const QString & key, keys) { QTest::newRow(qPrintable(key)) << QString(DATA_DIR + QLatin1Char('/') + key) << m_settings.value(key).toString(); } m_settings.endGroup(); } void BacktraceParserTest::btParserBenchmark() { QFETCH(QString, filename); QFETCH(QString, debugger); QSharedPointer parser(BacktraceParser::newParser(debugger)); parser->connectToGenerator(m_generator); QBENCHMARK_ONCE { m_generator->sendData(filename); } } QTEST_GUILESS_MAIN(BacktraceParserTest) diff --git a/src/tests/backtraceparsertest/backtraceparsertest_manual.cpp b/src/tests/backtraceparsertest/backtraceparsertest_manual.cpp index 3569cb60..bb1d2a1b 100644 --- a/src/tests/backtraceparsertest/backtraceparsertest_manual.cpp +++ b/src/tests/backtraceparsertest/backtraceparsertest_manual.cpp @@ -1,67 +1,67 @@ /* Copyright (C) 2010 George Kiagiadakis This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "fakebacktracegenerator.h" #include "../../parser/backtraceparser.h" #include #include #include #include #include #include #include #include int main(int argc, char **argv) { QCoreApplication app(argc, argv); KAboutData aboutData(QStringLiteral("backtraceparsertest_manual"), i18n("backtraceparsertest_manual"), QStringLiteral("1.0")); KAboutData::setApplicationData(aboutData); QCommandLineParser parser; parser.addOption(QCommandLineOption(QStringLiteral("debugger"), i18n("The debugger name passed to the parser factory"), QStringLiteral("name"), QStringLiteral("gdb"))); parser.addPositionalArgument(QStringLiteral("file"), i18n("A file containing the backtrace."), QStringLiteral("[file]")); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); QString debugger = parser.value(QStringLiteral("debugger")); if(parser.positionalArguments().isEmpty()) { parser.showHelp(1); return 1; } QString file = parser.positionalArguments().first(); if (!QFile::exists(file)) { QTextStream(stderr) << "The specified file does not exist" << endl; return 1; } FakeBacktraceGenerator generator; QSharedPointer btparser(BacktraceParser::newParser(debugger)); btparser->connectToGenerator(&generator); generator.sendData(file); QMetaEnum metaUsefulness = BacktraceParser::staticMetaObject.enumerator( BacktraceParser::staticMetaObject.indexOfEnumerator("Usefulness")); QTextStream(stdout) << "Usefulness: " << metaUsefulness.valueToKey(btparser->backtraceUsefulness()) << endl; - QTextStream(stdout) << "First valid functions: " << btparser->firstValidFunctions().join(QStringLiteral(" ")) << endl; + QTextStream(stdout) << "First valid functions: " << btparser->firstValidFunctions().join(QLatin1Char(' ')) << endl; QTextStream(stdout) << "Simplified backtrace:\n" << btparser->simplifiedBacktrace() << endl; QStringList l = static_cast(btparser->librariesWithMissingDebugSymbols().toList()); - QTextStream(stdout) << "Missing dbgsym libs: " << l.join(QStringLiteral(" ")) << endl; + QTextStream(stdout) << "Missing dbgsym libs: " << l.join(QLatin1Char(' ')) << endl; return 0; }