diff --git a/src/backtracegenerator.cpp b/src/backtracegenerator.cpp index 11296bb6..176538c3 100644 --- a/src/backtracegenerator.cpp +++ b/src/backtracegenerator.cpp @@ -1,197 +1,197 @@ /***************************************************************** * drkonqi - The KDE Crash Handler * * Copyright (C) 2000-2003 Hans Petter Bieker * Copyright (C) 2009 George Kiagiadakis * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************/ #include "backtracegenerator.h" #include #include "drkonqi_debug.h" #include #include #include "parser/backtraceparser.h" BacktraceGenerator::BacktraceGenerator(const Debugger & debugger, QObject *parent) : QObject(parent), m_debugger(debugger), m_proc(nullptr), m_temp(nullptr), m_state(NotLoaded) { m_parser = BacktraceParser::newParser(m_debugger.codeName(), this); m_parser->connectToGenerator(this); #ifdef BACKTRACE_PARSER_DEBUG m_debugParser = BacktraceParser::newParser(QString(), this); //uses the null parser m_debugParser->connectToGenerator(this); #endif } BacktraceGenerator::~BacktraceGenerator() { if (m_proc && m_proc->state() == QProcess::Running) { qCWarning(DRKONQI_LOG) << "Killing running debugger instance"; m_proc->disconnect(this); m_proc->terminate(); if (!m_proc->waitForFinished(10000)) { m_proc->kill(); // lldb can become "stuck" on OS X; just mark m_proc as to be deleted later rather // than waiting a potentially very long time for it to heed the kill() request. m_proc->deleteLater(); } else { delete m_proc; } delete m_temp; } } bool BacktraceGenerator::start() { //they should always be null before entering this function. Q_ASSERT(!m_proc); Q_ASSERT(!m_temp); m_parsedBacktrace.clear(); m_state = Loading; emit starting(); if (!m_debugger.isValid() || !m_debugger.isInstalled()) { m_state = FailedToStart; emit failedToStart(); return false; } m_proc = new KProcess; m_proc->setEnv(QStringLiteral("LC_ALL"), QStringLiteral("C")); // force C locale m_temp = new QTemporaryFile; m_temp->open(); m_temp->write(m_debugger.backtraceBatchCommands().toLatin1()); m_temp->write("\n", 1); m_temp->flush(); // start the debugger QString str = m_debugger.command(); Debugger::expandString(str, Debugger::ExpansionUsageShell, m_temp->fileName()); *m_proc << KShell::splitArgs(str); m_proc->setOutputChannelMode(KProcess::OnlyStdoutChannel); m_proc->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Text); // check if the debugger should take its input from a file we'll generate, // and take the appropriate steps if so QString stdinFile = m_debugger.backendValueOfParameter(QStringLiteral("ExecInputFile")); Debugger::expandString(stdinFile, Debugger::ExpansionUsageShell, m_temp->fileName()); if (!stdinFile.isEmpty() && QFile::exists(stdinFile)) { m_proc->setStandardInputFile(stdinFile); } connect(m_proc, &KProcess::readyReadStandardOutput, this, &BacktraceGenerator::slotReadInput); connect(m_proc, static_cast(&KProcess::finished), this, &BacktraceGenerator::slotProcessExited); m_proc->start(); if (!m_proc->waitForStarted()) { //we mustn't keep these around... m_proc->deleteLater(); m_temp->deleteLater(); m_proc = nullptr; m_temp = nullptr; m_state = FailedToStart; emit failedToStart(); return false; } return true; } void BacktraceGenerator::slotReadInput() { if (!m_proc) { // this can happen with lldb after we detected that it detached from the debuggee. return; } // we do not know if the output array ends in the middle of an utf-8 sequence m_output += m_proc->readAllStandardOutput(); int pos; while ((pos = m_output.indexOf('\n')) != -1) { QString line = QString::fromLocal8Bit(m_output.constData(), pos + 1); m_output.remove(0, pos + 1); emit newLine(line); line = line.simplified(); if (line.startsWith(QLatin1String("Process ")) && line.endsWith(QLatin1String(" detached"))) { // lldb is acting on a detach command (in lldbrc) // Anything following this line doesn't interest us, and lldb has been known - // to turn into a zombie instead of exitting, thereby blocking us. + // to turn into a zombie instead of exiting, thereby blocking us. // Tell the process to quit if it's still running, and pretend it did. if (m_proc && m_proc->state() == QProcess::Running) { m_proc->terminate(); if (!m_proc->waitForFinished(500)) { m_proc->kill(); } if (m_proc) { slotProcessExited(0, QProcess::NormalExit); } } return; } } } void BacktraceGenerator::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { //these are useless now m_proc->deleteLater(); m_temp->deleteLater(); m_proc = nullptr; m_temp = nullptr; //mark the end of the backtrace for the parser emit newLine(QString()); if (exitStatus != QProcess::NormalExit || exitCode != 0) { m_state = Failed; emit someError(); return; } //no translation, string appears in the report QString tmp(QStringLiteral("Application: %progname (%execname), signal: %signame\n")); Debugger::expandString(tmp); m_parsedBacktrace = tmp + m_parser->parsedBacktrace(); m_state = Loaded; #ifdef BACKTRACE_PARSER_DEBUG //append the raw unparsed backtrace m_parsedBacktrace += "\n------------ Unparsed Backtrace ------------\n"; m_parsedBacktrace += m_debugParser->parsedBacktrace(); //it's not really parsed, it's from the null parser. #endif emit done(); } diff --git a/src/backtracewidget.cpp b/src/backtracewidget.cpp index c29db082..b09c5afe 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 lenght expecations + // 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 += 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 80ef6478..f31b82e0 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. Syncronize for good meassure. + // 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 descedingly by bug_id. This allows us to offset through the results + // 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"); 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/exceptions.h b/src/bugzillaintegration/libbugzilla/exceptions.h index 78713962..a15b48eb 100644 --- a/src/bugzillaintegration/libbugzilla/exceptions.h +++ b/src/bugzillaintegration/libbugzilla/exceptions.h @@ -1,116 +1,116 @@ /* 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 . */ #ifndef EXCEPTIONS_H #define EXCEPTIONS_H #include class QJsonDocument; class QJsonObject; namespace KIO { class TransferJob; } namespace Bugzilla { class APIJob; /** * Root class for exceptions. Simply a QException which has the what backed * by a QString. */ class Exception : public QException { public: using QException::QException; virtual ~Exception(); virtual QString whatString() const = 0; virtual const char *what() const noexcept override; private: char *m_what = nullptr; }; /** * Generic runtime exception. */ class RuntimeException : public Exception { public: RuntimeException(const QString &reason); virtual RuntimeException *clone() const override { return new RuntimeException(*this); } virtual QString whatString() const override; private: QString m_reason; }; /** - * Translates an API error into an excpetion for easy handling. + * Translates an API error into an exception for easy handling. * Specifically when the API sends an error object in the body attempting to * access the JSON blob through one of the convenience accessors * (e.g. job.object()) will instead raise an exception. */ class APIException : public Exception { public: APIException(const QJsonDocument &document); APIException(const QJsonObject &object); APIException(const APIException &other); virtual void raise() const override { throw *this; } virtual APIException *clone() const override { return new APIException(*this); } virtual QString whatString() const override; bool isError() const { return m_isError; } static void maybeThrow(const QJsonDocument &document); private: bool m_isError = false; QString m_message; int m_code = -1; }; /** * Translates an KJob/APIJob error into an excpetion for easy handling. */ class ProtocolException : public Exception { public: ProtocolException(const APIJob *job); ProtocolException(const ProtocolException &other); virtual void raise() const override { throw *this; } virtual ProtocolException *clone() const override { return new ProtocolException(*this); } virtual QString whatString() const override; static void maybeThrow(const APIJob *job); private: const APIJob *m_job = nullptr; }; } // namespace Bugzilla #endif // EXCEPTIONS_H diff --git a/src/bugzillaintegration/libbugzilla/models/bug.h b/src/bugzillaintegration/libbugzilla/models/bug.h index a0e62be8..d992e72a 100644 --- a/src/bugzillaintegration/libbugzilla/models/bug.h +++ b/src/bugzillaintegration/libbugzilla/models/bug.h @@ -1,150 +1,150 @@ /* 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 . */ #ifndef BUG_H #define BUG_H #include #include #include #include "comment.h" namespace Bugzilla { // Models a bugzilla bug. class Bug : public QObject { public: enum class Status { Unknown, // First value is default if QMetaEnum can't map the key. UNCONFIRMED, CONFIRMED, ASSIGNED, REOPENED, RESOLVED, NEEDSINFO, VERIFIED, CLOSED }; Q_ENUM(Status) enum class Resolution { Unknown, // First value is default if QMetaEnum can't map the key. - NONE, // Fake value, expresses unresoled. On the REST side this is an empty string. + NONE, // Fake value, expresses unresolved. On the REST side this is an empty string. FIXED, INVALID, WONTFIX, LATER, REMIND, DUPLICATE, WORKSFORME, MOVED, UPSTREAM, DOWNSTREAM, WAITINGFORINFO, BACKTRACE, UNMAINTAINED }; Q_ENUM(Resolution) private: Q_OBJECT Q_PROPERTY(qint64 id READ id WRITE setId) Q_PROPERTY(QString product READ product WRITE setProduct) Q_PROPERTY(QString component READ component WRITE setComponent) Q_PROPERTY(QString summary READ summary WRITE setSummary) Q_PROPERTY(QString version READ version WRITE setVersion) Q_PROPERTY(bool is_open READ is_open WRITE setIs_open) // maybe should be camel mapped, who knows Q_PROPERTY(QString op_sys READ op_sys WRITE setOp_sys) Q_PROPERTY(QString priority READ priority WRITE setPriority) Q_PROPERTY(QString severity READ severity WRITE setSeverity) Q_PROPERTY(Status status READ status WRITE setStatus) Q_PROPERTY(Resolution resolution READ resolution WRITE setResolution) Q_PROPERTY(qint64 dupe_of READ dupe_of WRITE setDupe_of) // Custom fields (versionfixedin etc) are only available via customField(). public: typedef QPointer Ptr; explicit Bug(const QVariantHash &object, QObject *parent = nullptr); qint64 id() const; void setId(qint64 id); QVariant customField(const char *key); Status status() const; void setStatus(Status status); Resolution resolution() const; void setResolution(Resolution resolution); QString summary() const; void setSummary(const QString &summary); QString version() const; void setVersion(const QString &version); QString product() const; void setProduct(const QString &product); QString component() const; void setComponent(const QString &component); QString op_sys() const; void setOp_sys(const QString &op_sys); QString priority() const; void setPriority(const QString &priority); QString severity() const; void setSeverity(const QString &severity); bool is_open() const; void setIs_open(bool is_open); qint64 dupe_of() const; void setDupe_of(qint64 dupe_of); Q_SIGNALS: void commentsChanged(); private: qint64 m_id = -1; QString m_product; QString m_component; QString m_summary; QString m_version; bool m_is_open = false; QString m_op_sys; QString m_priority; QString m_severity; Status m_status = Status::Unknown; Resolution m_resolution = Resolution::Unknown; qint64 m_dupe_of = -1; }; } // namespace Bugzilla #endif // BUG_H diff --git a/src/bugzillaintegration/productmapping.cpp b/src/bugzillaintegration/productmapping.cpp index 70833aa3..4ecf3208 100644 --- a/src/bugzillaintegration/productmapping.cpp +++ b/src/bugzillaintegration/productmapping.cpp @@ -1,185 +1,185 @@ /******************************************************************* * productmapping.cpp * Copyright 2009 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 "productmapping.h" #include #include #include "drkonqi_debug.h" #include #include "bugzillalib.h" #include "crashedapplication.h" ProductMapping::ProductMapping(const CrashedApplication * crashedApp, BugzillaManager * bzManager, QObject * parent) : QObject(parent) , m_crashedAppPtr(crashedApp) , m_bugzillaManagerPtr(bzManager) , m_bugzillaProductDisabled(false) , m_bugzillaVersionDisabled(false) { //Default "fallback" values m_bugzillaProduct = crashedApp->fakeExecutableBaseName(); m_bugzillaComponent = QStringLiteral("general"); m_bugzillaVersionString = QStringLiteral("unspecified"); m_relatedBugzillaProducts = QStringList() << m_bugzillaProduct; map(crashedApp->fakeExecutableBaseName()); //Get valid versions connect(m_bugzillaManagerPtr, &BugzillaManager::productInfoFetched, this, &ProductMapping::checkProductInfo); m_bugzillaManagerPtr->fetchProductInfo(m_bugzillaProduct); } void ProductMapping::map(const QString & appName) { mapUsingInternalFile(appName); getRelatedProductsUsingInternalFile(m_bugzillaProduct); } void ProductMapping::mapUsingInternalFile(const QString & appName) { KConfig mappingsFile(QString::fromLatin1("mappings"), KConfig::NoGlobals, QStandardPaths::DataLocation); const KConfigGroup mappings = mappingsFile.group("Mappings"); if (mappings.hasKey(appName)) { QString mappingString = mappings.readEntry(appName); if (!mappingString.isEmpty()) { QStringList list = mappingString.split(QLatin1Char('|'), QString::SkipEmptyParts); if (list.count()==2) { m_bugzillaProduct = list.at(0); m_bugzillaComponent = list.at(1); m_relatedBugzillaProducts = QStringList() << m_bugzillaProduct; } else { qCWarning(DRKONQI_LOG) << "Error while reading mapping entry. Sections found " << list.count(); } } else { qCWarning(DRKONQI_LOG) << "Error while reading mapping entry. Entry exists but it is empty " "(or there was an error when reading)"; } } } void ProductMapping::getRelatedProductsUsingInternalFile(const QString & bugzillaProduct) { //ProductGroup -> kontact=kdepim //Groups -> kdepim=kontact|kmail|korganizer|akonadi|pimlibs..etc KConfig mappingsFile(QString::fromLatin1("mappings"), KConfig::NoGlobals, QStandardPaths::DataLocation); const KConfigGroup productGroup = mappingsFile.group("ProductGroup"); //Get groups of the application QStringList groups; if (productGroup.hasKey(bugzillaProduct)) { QString group = productGroup.readEntry(bugzillaProduct); if (group.isEmpty()) { qCWarning(DRKONQI_LOG) << "Error while reading mapping entry. Entry exists but it is empty " "(or there was an error when reading)"; return; } groups = group.split(QLatin1Char('|'), QString::SkipEmptyParts); } //All KDE apps use the KDE Platform (basic libs) groups << QLatin1String("kdeplatform"); //Add the product itself m_relatedBugzillaProducts = QStringList() << m_bugzillaProduct; //Get related products of each related group Q_FOREACH( const QString & group, groups ) { const KConfigGroup bzGroups = mappingsFile.group("BZGroups"); if (bzGroups.hasKey(group)) { QString bzGroup = bzGroups.readEntry(group); if (!bzGroup.isEmpty()) { QStringList relatedGroups = bzGroup.split(QLatin1Char('|'), QString::SkipEmptyParts); if (relatedGroups.size()>0) { m_relatedBugzillaProducts.append(relatedGroups); } } else { qCWarning(DRKONQI_LOG) << "Error while reading mapping entry. Entry exists but it is empty " "(or there was an error when reading)"; } } } } void ProductMapping::checkProductInfo(const Bugzilla::Product::Ptr product) { // check whether the product itself is disabled for new reports, // which usually means that product/application is unmaintained. m_bugzillaProductDisabled = !product->isActive(); // check whether the product on bugzilla contains the expected component if (!product->componentNames().contains(m_bugzillaComponent)) { m_bugzillaComponent = QLatin1String("general"); } // find the appropriate version to use on bugzilla const QString version = m_crashedAppPtr->version(); const QStringList &allVersions = product->allVersions(); if (allVersions.contains(version)) { //The version the crash application provided is a valid bugzilla version: use it ! m_bugzillaVersionString = version; } else if (version.endsWith(QLatin1String(".00"))) { //check if there is a version on bugzilla with just ".0" const QString shorterVersion = version.left(version.size() - 1); if (allVersions.contains(shorterVersion)) { m_bugzillaVersionString = shorterVersion; } } - // check whether that verions is disabled for new reports, which + // check whether that versions is disabled for new reports, which // usually means that version is outdated and not supported anymore. const QStringList &inactiveVersions = product->inactiveVersions(); m_bugzillaVersionDisabled = inactiveVersions.contains(m_bugzillaVersionString); } QStringList ProductMapping::relatedBugzillaProducts() const { return m_relatedBugzillaProducts; } QString ProductMapping::bugzillaProduct() const { return m_bugzillaProduct; } QString ProductMapping::bugzillaComponent() const { return m_bugzillaComponent; } QString ProductMapping::bugzillaVersion() const { return m_bugzillaVersionString; } bool ProductMapping::bugzillaProductDisabled() const { return m_bugzillaProductDisabled; } bool ProductMapping::bugzillaVersionDisabled() const { return m_bugzillaVersionDisabled; } diff --git a/src/bugzillaintegration/reportassistantpages_bugzilla.cpp b/src/bugzillaintegration/reportassistantpages_bugzilla.cpp index 1f9a3769..f3dd93b8 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 occured when talking to bugs.kde.org: %1", + "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("
") + 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("
") + 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("
") + 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/drkonqi.cpp b/src/drkonqi.cpp index 84a9ba04..ee2e6e5a 100644 --- a/src/drkonqi.cpp +++ b/src/drkonqi.cpp @@ -1,425 +1,425 @@ /* 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 . Parts of this code were originally under the following license: * Copyright (C) 2000-2003 Hans Petter Bieker * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "drkonqi.h" #include "drkonqi_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include "systeminformation.h" #include "crashedapplication.h" #include "drkonqibackends.h" #include "debuggermanager.h" #include "backtracegenerator.h" DrKonqi::DrKonqi() : m_signal(0) , m_pid(0) , m_kdeinit(false) , m_safer(false) , m_restarted(false) , m_keepRunning(false) , m_thread(0) { m_backend = new KCrashBackend(); m_systemInformation = new SystemInformation(); } DrKonqi::~DrKonqi() { delete m_systemInformation; delete m_backend; } //static DrKonqi *DrKonqi::instance() { static DrKonqi drKonqiInstance; return &drKonqiInstance; } //based on KCrashDelaySetHandler from kdeui/util/kcrash.cpp class EnableCrashCatchingDelayed : public QObject { public: EnableCrashCatchingDelayed() { startTimer(10000); // 10 s } protected: void timerEvent(QTimerEvent *event) override { qCDebug(DRKONQI_LOG) << "Enabling drkonqi crash catching"; KCrash::setDrKonqiEnabled(true); killTimer(event->timerId()); this->deleteLater(); } }; bool DrKonqi::init() { if (!instance()->m_backend->init()) { return false; } else { //all ok, continue initialization // Set drkonqi to handle its own crashes, but only if the crashed app // is not drkonqi already. If it is drkonqi, delay enabling crash catching // to prevent recursive crashes (in case it crashes at startup) if (crashedApplication()->fakeExecutableBaseName() != QLatin1String("drkonqi")) { qCDebug(DRKONQI_LOG) << "Enabling drkonqi crash catching"; KCrash::setDrKonqiEnabled(true); } else { new EnableCrashCatchingDelayed; } return true; } } //static SystemInformation *DrKonqi::systemInformation() { return instance()->m_systemInformation; } //static DebuggerManager* DrKonqi::debuggerManager() { return instance()->m_backend->debuggerManager(); } //static CrashedApplication *DrKonqi::crashedApplication() { return instance()->m_backend->crashedApplication(); } //static void DrKonqi::saveReport(const QString & reportText, QWidget *parent) { if (isSafer()) { QTemporaryFile tf; tf.setFileTemplate(QStringLiteral("XXXXXX.kcrash.txt")); tf.setAutoRemove(false); if (tf.open()) { QTextStream textStream(&tf); textStream << reportText; textStream.flush(); KMessageBox::information(parent, xi18nc("@info", "Report saved to %1.", tf.fileName())); } else { KMessageBox::sorry(parent, i18nc("@info","Could not create a file in which to save the report.")); } } else { QString defname = getSuggestedKCrashFilename(crashedApplication()); QPointer dlg(new QFileDialog(parent, defname)); dlg->selectFile(defname); dlg->setWindowTitle(i18nc("@title:window","Select Filename")); dlg->setAcceptMode(QFileDialog::AcceptSave); dlg->setFileMode(QFileDialog::AnyFile); dlg->setConfirmOverwrite(true); if (dlg->exec() != QDialog::Accepted) { return; } if (!dlg) { //Dialog is invalid, it was probably deleted (ex. via DBus call) //return and do not crash return; } QUrl fileUrl; if(!dlg->selectedUrls().isEmpty()) fileUrl = dlg->selectedUrls().first(); delete dlg; if (fileUrl.isValid()) { QTemporaryFile tf; if (tf.open()) { QTextStream ts(&tf); ts << reportText; ts.flush(); } else { KMessageBox::sorry(parent, xi18nc("@info","Cannot open file %1 " "for writing.", tf.fileName())); return; } // QFileDialog was run with confirmOverwrite, so we can safely - // overwrite as necesssary. + // overwrite as necessary. KIO::FileCopyJob* job = KIO::file_copy(QUrl::fromLocalFile(tf.fileName()), fileUrl, -1, KIO::DefaultFlags | KIO::Overwrite); KJobWidgets::setWindow(job, parent); if (!job->exec()) { KMessageBox::sorry(parent, job->errorString()); } } } } // Helper functions for the shutdownSaveReport class ShutdownHelper : public QObject { Q_OBJECT public: QString shutdownSaveString; void removeOldFilesIn(QDir &dir) { auto fileList = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::SortFlag::Time | QDir::Reversed); for(int i = fileList.size(); i >= 10; i--) { auto currentFile = fileList.takeFirst(); dir.remove(currentFile.fileName()); } } void saveReportAndQuit() { const QString dirname = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); // Try to create the directory to save the logs, if we can't open the directory, // just bail out. no need to hold the shutdown process. QDir dir(dirname); if (!dir.mkpath(dirname)) { qApp->quit(); } removeOldFilesIn(dir); const QString defname = dirname + QLatin1Char('/') + QStringLiteral("pid-") + QString::number(DrKonqi::pid()) + QLatin1Char('-') + getSuggestedKCrashFilename(DrKonqi::crashedApplication()); QFile shutdownSaveFile(defname); if (shutdownSaveFile.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream ts(&shutdownSaveFile); ts << shutdownSaveString; ts.flush(); shutdownSaveFile.close(); } deleteLater(); qApp->quit(); } void appendNewLine(const QString& newLine) { shutdownSaveString += newLine; } }; void DrKonqi::shutdownSaveReport() { auto btGenerator = instance()->debuggerManager()->backtraceGenerator(); auto shutdownHelper = new ShutdownHelper(); QObject::connect(btGenerator, &BacktraceGenerator::done, shutdownHelper, &ShutdownHelper::saveReportAndQuit); QObject::connect(btGenerator, &BacktraceGenerator::someError, shutdownHelper, &ShutdownHelper::saveReportAndQuit); QObject::connect(btGenerator, &BacktraceGenerator::failedToStart, shutdownHelper, &ShutdownHelper::saveReportAndQuit); QObject::connect(btGenerator, &BacktraceGenerator::newLine, shutdownHelper, &ShutdownHelper::appendNewLine); btGenerator->start(); } void DrKonqi::setSignal(int signal) { instance()->m_signal = signal; } void DrKonqi::setAppName(const QString &appName) { instance()->m_appName = appName; } void DrKonqi::setAppPath(const QString &appPath) { instance()->m_appPath = appPath; } void DrKonqi::setAppVersion(const QString &appVersion) { instance()->m_appVersion = appVersion; } void DrKonqi::setBugAddress(const QString &bugAddress) { instance()->m_bugAddress = bugAddress; } void DrKonqi::setProgramName(const QString &programName) { instance()->m_programName = programName; } void DrKonqi::setPid(int pid) { instance()->m_pid = pid; } void DrKonqi::setKdeinit(bool kdeinit) { instance()->m_kdeinit = kdeinit; } void DrKonqi::setSafer(bool safer) { instance()->m_safer = safer; } void DrKonqi::setRestarted(bool restarted) { instance()->m_restarted = restarted; } void DrKonqi::setKeepRunning(bool keepRunning) { instance()->m_keepRunning = keepRunning; } void DrKonqi::setThread(int thread) { instance()->m_thread = thread; } int DrKonqi::signal() { return instance()->m_signal; } const QString &DrKonqi::appName() { return instance()->m_appName; } const QString &DrKonqi::appPath() { return instance()->m_appPath; } const QString &DrKonqi::appVersion() { return instance()->m_appVersion; } const QString &DrKonqi::bugAddress() { return instance()->m_bugAddress; } const QString &DrKonqi::programName() { return instance()->m_programName; } int DrKonqi::pid() { return instance()->m_pid; } bool DrKonqi::isKdeinit() { return instance()->m_kdeinit; } bool DrKonqi::isSafer() { return instance()->m_safer; } bool DrKonqi::isRestarted() { return instance()->m_restarted; } bool DrKonqi::isKeepRunning() { return instance()->m_keepRunning; } int DrKonqi::thread() { return instance()->m_thread; } bool DrKonqi::ignoreQuality() { static bool ignore = qEnvironmentVariableIsSet("DRKONQI_IGNORE_QUALITY") || qEnvironmentVariableIsSet("DRKONQI_TEST_MODE"); return ignore; } const QString &DrKonqi::kdeBugzillaURL() { // WARNING: for practical reasons this cannot use the shared instance // Initing the instances requires knowing the URL already, so we'd have // an init loop. Use a local static instead. Otherwise we'd crash on // initialization of global statics derived from our return value. // Always copy into the local static and return that! static QString url; if (!url.isEmpty()) { return url; } url = QString::fromLocal8Bit(qgetenv("DRKONQI_KDE_BUGZILLA_URL")); if (!url.isEmpty()) { return url; } if (qEnvironmentVariableIsSet("DRKONQI_TEST_MODE")) { url = QStringLiteral("https://bugstest.kde.org/"); } else { url = QStringLiteral("https://bugs.kde.org/"); } return url; } #include "drkonqi.moc" diff --git a/src/gdbhighlighter.cpp b/src/gdbhighlighter.cpp index 454d6fc5..948a7b31 100644 --- a/src/gdbhighlighter.cpp +++ b/src/gdbhighlighter.cpp @@ -1,137 +1,137 @@ /* Copyright (C) 2010 Milian Wolff 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 "gdbhighlighter.h" #include #include GdbHighlighter::GdbHighlighter(QTextDocument* parent, const QList & gdbLines) : QSyntaxHighlighter(parent) { // setup line lookup int l = 0; foreach(const BacktraceLine& line, gdbLines) { lines.insert(l, line); l += line.toString().count(QLatin1Char('\n')); } - // setup formates + // setup formats KColorScheme scheme(QPalette::Active); crashFormat.setForeground(scheme.foreground(KColorScheme::NegativeText)); nullptrFormat.setForeground(scheme.foreground(KColorScheme::NegativeText)); nullptrFormat.setFontWeight(QFont::Bold); assertFormat = nullptrFormat; threadFormat.setForeground(scheme.foreground(KColorScheme::NeutralText)); urlFormat.setForeground(scheme.foreground(KColorScheme::LinkText)); funcFormat.setForeground(scheme.foreground(KColorScheme::VisitedText)); funcFormat.setFontWeight(QFont::Bold); otheridFormat.setForeground(scheme.foreground(KColorScheme::PositiveText)); crapFormat.setForeground(scheme.foreground(KColorScheme::InactiveText)); } void GdbHighlighter::highlightBlock(const QString& text) { int cur = 0; int next; int diff; const QRegExp hexptrPattern(QStringLiteral("0x[0-9a-f]+"), Qt::CaseSensitive, QRegExp::RegExp2); int lineNr = currentBlock().firstLineNumber(); while ( cur < text.length() ) { next = text.indexOf(QLatin1Char('\n'), cur); if (next == -1) { next = text.length(); } if (lineNr == 0) { // line that contains 'Application: ...' ++lineNr; cur = next; continue; } diff = next - cur; const QString lineStr = text.mid(cur, diff).append(QLatin1Char('\n')); // -1 since we skip the first line QMap< int, BacktraceLine >::iterator it = lines.lowerBound(lineNr - 1); Q_ASSERT(it != lines.end()); // lowerbound would return the next higher item, even though we want the former one if (it.key() > lineNr - 1) { --it; } const BacktraceLine& line = it.value(); if (line.type() == BacktraceLine::KCrash) { setFormat(cur, diff, crashFormat); } else if (line.type() == BacktraceLine::ThreadStart || line.type() == BacktraceLine::ThreadIndicator) { setFormat(cur, diff, threadFormat); } else if (line.type() == BacktraceLine::Crap) { setFormat(cur, diff, crapFormat); } else if (line.type() == BacktraceLine::StackFrame) { if (!line.fileName().isEmpty()) { int colonPos = line.fileName().lastIndexOf(QLatin1Char(':')); setFormat(lineStr.indexOf(line.fileName()), colonPos == -1 ? line.fileName().length() : colonPos, urlFormat); } if (!line.libraryName().isEmpty()) { setFormat(lineStr.indexOf(line.libraryName()), line.libraryName().length(), urlFormat); } if (!line.functionName().isEmpty()) { int idx = lineStr.indexOf(line.functionName()); if (idx != -1) { // highlight Id::Id::Id::Func // Id should have otheridFormat, :: no format and Func funcFormat int i = idx; int from = idx; while (i < idx + line.functionName().length()) { if (lineStr.at(i) == QLatin1Char(':')) { setFormat(from, i - from, otheridFormat); // skip :: i += 2; from = i; continue; } else if (lineStr.at(i) == QLatin1Char('<') || lineStr.at(i) == QLatin1Char('>')) { setFormat(from, i - from, otheridFormat); ++i; from = i; continue; } ++i; } if (line.functionName() == QLatin1String("qFatal") || line.functionName() == QLatin1String("abort") || line.functionName() == QLatin1String("__assert_fail") || line.functionName() == QLatin1String("*__GI___assert_fail") || line.functionName() == QLatin1String("*__GI_abort")) { setFormat(from, i - from, assertFormat); } else { setFormat(from, i - from, funcFormat); } } } // highlight hexadecimal ptrs int idx = 0; while ((idx = hexptrPattern.indexIn(lineStr, idx)) != -1) { if (hexptrPattern.cap() == QLatin1String("0x0")) { setFormat(idx, hexptrPattern.matchedLength(), nullptrFormat); } idx += hexptrPattern.matchedLength(); } } cur = next; ++lineNr; } }