File indexing completed on 2024-04-28 05:26:49

0001 /*******************************************************************
0002  * reportinterface.cpp
0003  * SPDX-FileCopyrightText: 2009, 2010, 2011 Dario Andres Rodriguez <andresbajotierra@gmail.com>
0004  * SPDX-FileCopyrightText: 2009 George Kiagiadakis <gkiagia@users.sourceforge.net>
0005  * SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
0006  *
0007  * SPDX-License-Identifier: GPL-2.0-or-later
0008  *
0009  ******************************************************************/
0010 
0011 #include "reportinterface.h"
0012 
0013 #include <chrono>
0014 
0015 #include <QGuiApplication>
0016 
0017 #include <KIO/TransferJob>
0018 #include <KLocalizedString>
0019 
0020 #include "backtracegenerator.h"
0021 #include "bugzillalib.h"
0022 #include "config-drkonqi.h"
0023 #include "crashedapplication.h"
0024 #include "debuggermanager.h"
0025 #include "drkonqi.h"
0026 #include "drkonqi_debug.h"
0027 #include "parser/backtraceparser.h"
0028 #include "productmapping.h"
0029 #include "sentryconnection.h"
0030 #include "settings.h"
0031 #include "systeminformation.h"
0032 
0033 using namespace std::chrono_literals;
0034 using namespace Qt::StringLiterals;
0035 
0036 // Max size a report may have. This is enforced in bugzilla, hardcoded, and
0037 // cannot be queried through the API, so handle this client-side in a hardcoded
0038 // fashion as well.
0039 static const int s_maxReportSize = 65535;
0040 
0041 ReportInterface::ReportInterface(QObject *parent)
0042     : QObject(parent)
0043     , m_sentryPostbox(DrKonqi::crashedApplication()->fakeExecutableBaseName(), std::make_shared<SentryNetworkConnection>())
0044 {
0045     m_bugzillaManager = new BugzillaManager(KDE_BUGZILLA_URL, this);
0046 
0047     m_productMapping = new ProductMapping(DrKonqi::crashedApplication(), m_bugzillaManager, this);
0048 
0049     // Information the user can provide about the crash
0050     m_userRememberCrashSituation = false;
0051     m_reproducible = ReproducibleUnsure;
0052     m_provideActionsApplicationDesktop = false;
0053     m_provideUnusualBehavior = false;
0054     m_provideApplicationConfigurationDetails = false;
0055 
0056     // Do not attach the bug report to any other existent report (create a new one)
0057     m_attachToBugNumber = 0;
0058 
0059     connect(&m_sentryPostbox, &SentryPostbox::hasDeliveredChanged, this, [this] {
0060         maybeDone();
0061     });
0062 
0063     m_sentryStartTimer.setInterval(5s);
0064     m_sentryStartTimer.setSingleShot(true);
0065     connect(&m_sentryStartTimer, &QTimer::timeout, this, &ReportInterface::trySentry);
0066     connect(Settings::self(), &Settings::SentryChanged, &m_sentryStartTimer, QOverload<>::of(&QTimer::start));
0067     trySentry();
0068 }
0069 
0070 void ReportInterface::setBugAwarenessPageData(bool rememberSituation, Reproducible reproducible, bool actions, bool unusual, bool configuration)
0071 {
0072     // Save the information the user can provide about the crash from the assistant page
0073     m_userRememberCrashSituation = rememberSituation;
0074     m_reproducible = reproducible;
0075     m_provideActionsApplicationDesktop = actions;
0076     m_provideUnusualBehavior = unusual;
0077     m_provideApplicationConfigurationDetails = configuration;
0078 }
0079 
0080 bool ReportInterface::isBugAwarenessPageDataUseful() const
0081 {
0082     // Determine if the assistant should proceed, considering the amount of information
0083     // the user can provide
0084     int rating = selectedOptionsRating();
0085 
0086     // Minimum information required even for a good backtrace.
0087     bool useful = m_userRememberCrashSituation && (rating >= 2 || (m_reproducible == ReproducibleSometimes || m_reproducible == ReproducibleEverytime));
0088     return useful;
0089 }
0090 
0091 int ReportInterface::selectedOptionsRating() const
0092 {
0093     // Check how many information the user can provide and generate a rating
0094     int rating = 0;
0095     if (m_provideActionsApplicationDesktop) {
0096         rating += 3;
0097     }
0098     if (m_provideApplicationConfigurationDetails) {
0099         rating += 2;
0100     }
0101     if (m_provideUnusualBehavior) {
0102         rating += 1;
0103     }
0104     return rating;
0105 }
0106 
0107 QString ReportInterface::backtrace() const
0108 {
0109     return m_backtrace;
0110 }
0111 
0112 void ReportInterface::setBacktrace(const QString &backtrace)
0113 {
0114     m_backtrace = backtrace;
0115     Q_EMIT backtraceChanged();
0116 }
0117 
0118 QString ReportInterface::title() const
0119 {
0120     return m_reportTitle;
0121 }
0122 
0123 void ReportInterface::setTitle(const QString &text)
0124 {
0125     m_reportTitle = text;
0126     Q_EMIT titleChanged();
0127 }
0128 
0129 void ReportInterface::setDetailText(const QString &text)
0130 {
0131     m_reportDetailText = text;
0132     Q_EMIT detailTextChanged();
0133 }
0134 
0135 QString ReportInterface::generateReportFullText(DrKonqiStamp stamp, Backtrace inlineBacktrace) const
0136 {
0137     // Note: no translations should be done in this function's strings
0138 
0139     const CrashedApplication *crashedApp = DrKonqi::crashedApplication();
0140     const SystemInformation *sysInfo = DrKonqi::systemInformation();
0141 
0142     QString report;
0143 
0144     // Program name and versions
0145     report.append(QStringLiteral("Application: %1 (%2)\n").arg(crashedApp->fakeExecutableBaseName(), crashedApp->version()));
0146     if (sysInfo->compiledSources()) {
0147         report.append(QStringLiteral(" (Compiled from sources)\n"));
0148     } else {
0149         report.append(QLatin1Char('\n'));
0150     }
0151     report.append(QStringLiteral("Qt Version: %1\n").arg(sysInfo->qtVersion()));
0152     report.append(QStringLiteral("Frameworks Version: %1\n").arg(sysInfo->frameworksVersion()));
0153 
0154     report.append(QStringLiteral("Operating System: %1\n").arg(sysInfo->operatingSystem()));
0155     report.append(QStringLiteral("Windowing System: %1\n").arg(sysInfo->windowSystem()));
0156 
0157     // LSB output or manually selected distro
0158     if (!sysInfo->distributionPrettyName().isEmpty()) {
0159         report.append(QStringLiteral("Distribution: %1\n").arg(sysInfo->distributionPrettyName()));
0160     } else if (!sysInfo->bugzillaPlatform().isEmpty() && sysInfo->bugzillaPlatform() != QLatin1String("unspecified")) {
0161         report.append(QStringLiteral("Distribution (Platform): %1\n").arg(sysInfo->bugzillaPlatform()));
0162     }
0163 
0164     report.append(QStringLiteral("DrKonqi: %1 [%2]\n").arg(QString::fromLatin1(PROJECT_VERSION), DrKonqi::backendClassName()));
0165     report.append(QLatin1Char('\n'));
0166 
0167     // Details of the crash situation
0168     if (isBugAwarenessPageDataUseful()) {
0169         report.append(QStringLiteral("-- Information about the crash:\n"));
0170         if (!m_reportDetailText.isEmpty()) {
0171             report.append(m_reportDetailText.trimmed());
0172         } else {
0173             // If the user manual reports this crash, he/she should know what to put in here.
0174             // This message is the only one translated in this function
0175             report.append(xi18nc("@info/plain",
0176                                  "<placeholder>In detail, tell us what you were doing "
0177                                  " when the application crashed.</placeholder>"));
0178         }
0179         report.append(QLatin1String("\n\n"));
0180     }
0181 
0182     // Crash reproducibility
0183     switch (m_reproducible) {
0184     case ReproducibleUnsure:
0185         report.append(QStringLiteral("The reporter is unsure if this crash is reproducible.\n\n"));
0186         break;
0187     case ReproducibleNever:
0188         report.append(QStringLiteral("The crash does not seem to be reproducible.\n\n"));
0189         break;
0190     case ReproducibleSometimes:
0191         report.append(QStringLiteral("The crash can be reproduced sometimes.\n\n"));
0192         break;
0193     case ReproducibleEverytime:
0194         report.append(QStringLiteral("The crash can be reproduced every time.\n\n"));
0195         break;
0196     }
0197 
0198     // Backtrace
0199     switch (inlineBacktrace) {
0200     case Backtrace::Complete:
0201         report.append(QStringLiteral("-- Backtrace:\n"));
0202         break;
0203     case Backtrace::Reduced:
0204         report.append(QStringLiteral("-- Backtrace (Reduced):\n"));
0205         break;
0206     case Backtrace::Exclude:
0207         report.append(QStringLiteral("The backtrace was excluded and likely attached as a file.\n"));
0208         break;
0209     }
0210     if (!m_backtrace.isEmpty()) {
0211         switch (inlineBacktrace) {
0212         case Backtrace::Complete:
0213             report.append(m_backtrace.trimmed() + QLatin1Char('\n'));
0214             break;
0215         case Backtrace::Reduced:
0216             report.append(DrKonqi::debuggerManager()->backtraceGenerator()->parser()->simplifiedBacktrace() + QLatin1Char('\n'));
0217             break;
0218         case Backtrace::Exclude:
0219             report.append(QStringLiteral("The backtrace is attached as a comment due to length constraints\n"));
0220             break;
0221         }
0222     } else {
0223         report.append(QStringLiteral("A useful backtrace could not be generated\n"));
0224     }
0225 
0226     switch (stamp) {
0227     case DrKonqiStamp::Include: {
0228         report.append(QLatin1String("\nReported using DrKonqi"));
0229         const QString product = m_productMapping->bugzillaProduct();
0230         const QString originalProduct = m_productMapping->bugzillaProductOriginal();
0231         if (!originalProduct.isEmpty()) {
0232             report.append(
0233                 QStringLiteral(
0234                     "\nThis report was filed against '%1' because the product '%2' could not be located in Bugzilla. Add it to drkonqi's mappings file!")
0235                     .arg(product, originalProduct));
0236         }
0237         break;
0238     }
0239     case DrKonqiStamp::Exclude:
0240         break;
0241     }
0242 
0243     return report;
0244 }
0245 
0246 QString ReportInterface::generateAttachmentComment() const
0247 {
0248     // Note: no translations should be done in this function's strings
0249 
0250     const CrashedApplication *crashedApp = DrKonqi::crashedApplication();
0251     const SystemInformation *sysInfo = DrKonqi::systemInformation();
0252 
0253     QString comment;
0254 
0255     // Program name and versions
0256     comment.append(QStringLiteral("%1 (%2) using Qt %4\n\n").arg(crashedApp->fakeExecutableBaseName(), crashedApp->version(), sysInfo->qtVersion()));
0257 
0258     // Details of the crash situation
0259     if (isBugAwarenessPageDataUseful()) {
0260         comment.append(QStringLiteral("%1\n\n").arg(m_reportDetailText.trimmed()));
0261     }
0262 
0263     // Backtrace (only 6 lines)
0264     comment.append(QStringLiteral("-- Backtrace (Reduced):\n"));
0265     QString reducedBacktrace = DrKonqi::debuggerManager()->backtraceGenerator()->parser()->simplifiedBacktrace();
0266     comment.append(reducedBacktrace.trimmed());
0267 
0268     return comment;
0269 }
0270 
0271 Bugzilla::NewBug ReportInterface::newBugReportTemplate() const
0272 {
0273     const SystemInformation *sysInfo = DrKonqi::systemInformation();
0274 
0275     Bugzilla::NewBug bug;
0276     bug.product = m_productMapping->bugzillaProduct();
0277     bug.component = m_productMapping->bugzillaComponent();
0278     bug.version = m_productMapping->bugzillaVersion();
0279     bug.op_sys = sysInfo->bugzillaOperatingSystem();
0280     if (sysInfo->compiledSources()) {
0281         bug.platform = QLatin1String("Compiled Sources");
0282     } else {
0283         bug.platform = sysInfo->bugzillaPlatform();
0284     }
0285     bug.keywords = QStringList{QStringLiteral("drkonqi")};
0286     bug.priority = QLatin1String("NOR");
0287     bug.severity = QLatin1String("crash");
0288     bug.summary = m_reportTitle;
0289 
0290     return bug;
0291 }
0292 
0293 QByteArray journalPriorityToSentryLevel(const QByteArray &priorityBytes)
0294 {
0295     bool ok = false;
0296     auto priority = priorityBytes.toInt(&ok);
0297     Q_ASSERT(ok);
0298     if (!ok) {
0299         return "info"_ba;
0300     }
0301     // https://www.freedesktop.org/software/systemd/man/latest/systemd.journal-fields.html#PRIORITY=
0302     switch (priority) {
0303     case 0:
0304     case 1:
0305     case 2:
0306         return "fatal"_ba;
0307     case 3:
0308         return "error"_ba;
0309     case 4:
0310         return "warning"_ba;
0311     case 5:
0312     case 6:
0313         return "info"_ba;
0314     case 7:
0315         return "debug"_ba;
0316     };
0317     return "info"_ba;
0318 }
0319 
0320 void ReportInterface::prepareEventPayload()
0321 {
0322     auto eventPayload = DrKonqi::debuggerManager()->backtraceGenerator()->sentryPayload();
0323     auto document = QJsonDocument::fromJson(eventPayload);
0324     auto hash = document.object().toVariantMap();
0325 
0326     // replace the timestamp with the real timestamp (possibly originating in journald)
0327     hash.insert(u"timestamp"_s, DrKonqi::crashedApplication()->datetime().toUTC().toString(Qt::ISODateWithMs));
0328 
0329     {
0330         QList<QVariant> breadcrumbs;
0331         const auto logs = DrKonqi::crashedApplication()->m_logs;
0332         for (const auto &logEntry : logs) {
0333             QVariantHash breadcrumb;
0334 
0335             breadcrumb.insert(u"type"_s, u"debug"_s);
0336             breadcrumb.insert(u"category"_s, logEntry.value("QT_CATEGORY"_ba, "default"_ba));
0337             breadcrumb.insert(u"message"_s, logEntry.value("MESSAGE"_ba));
0338             breadcrumb.insert(u"level"_s, journalPriorityToSentryLevel(logEntry.value("PRIORITY"_ba)));
0339             auto ok = false;
0340             auto realtime = logEntry.value("_SOURCE_REALTIME_TIMESTAMP"_ba).toULongLong(&ok);
0341             if (ok && realtime > 0) {
0342                 std::chrono::microseconds timestamp(realtime);
0343                 std::chrono::duration<double> timestampDouble(timestamp);
0344                 breadcrumb.insert(u"timestamp"_s, QJsonValue(timestampDouble.count()));
0345             }
0346 
0347             breadcrumbs.push_back(breadcrumb);
0348         }
0349         std::reverse(breadcrumbs.begin(), breadcrumbs.end());
0350         hash.insert(u"breadcrumbs"_s, breadcrumbs);
0351     }
0352 
0353     {
0354         constexpr auto CONTEXTS_KEY = "contexts"_L1;
0355         auto context = hash.take(CONTEXTS_KEY).toHash();
0356         context.insert(u"gpu"_s,
0357                        QVariantHash{
0358                            {u"name"_s, DrKonqi::instance()->m_glRenderer}, //
0359                            {u"version"_s, QGuiApplication::platformName()},
0360                        });
0361         hash.insert(CONTEXTS_KEY, context);
0362     }
0363 
0364     if (!DrKonqi::instance()->m_exceptionName.isEmpty()) {
0365         constexpr auto EXCEPTION_KEY = "exception"_L1;
0366         constexpr auto VALUES_KEY = "values"_L1;
0367         auto exception = hash.take(EXCEPTION_KEY).toHash();
0368         auto values = exception.take(VALUES_KEY).toJsonArray();
0369         // Preprend since the exception is likely the root of the problem stack.
0370         values.prepend(QJsonObject({
0371             {u"type"_s, DrKonqi::instance()->m_exceptionName},
0372             {u"value"_s, DrKonqi::instance()->m_exceptionWhat},
0373             {u"mechanism"_s,
0374              QJsonObject{
0375                  {u"handled"_s, false},
0376                  {u"synthetic"_s, false},
0377                  {u"type"_s, u"drkonqi"_s},
0378              }},
0379         }));
0380         exception.insert(VALUES_KEY, values);
0381         hash.insert(EXCEPTION_KEY, exception);
0382     }
0383 
0384     m_sentryPostbox.addEventPayload(QJsonDocument::fromVariant(hash));
0385     maybePickUpPostbox();
0386 }
0387 
0388 void ReportInterface::prepareCrashEvent()
0389 {
0390     switch (DrKonqi::debuggerManager()->backtraceGenerator()->state()) {
0391     case BacktraceGenerator::Loaded:
0392         return prepareEventPayload();
0393     case BacktraceGenerator::Loading:
0394     case BacktraceGenerator::NotLoaded:
0395     case BacktraceGenerator::Failed:
0396     case BacktraceGenerator::FailedToStart:
0397         break;
0398     }
0399     static bool connected = false;
0400     if (!connected) {
0401         connected = true;
0402         connect(DrKonqi::debuggerManager()->backtraceGenerator(), &BacktraceGenerator::done, this, [this] {
0403             prepareEventPayload();
0404         });
0405     }
0406     if (DrKonqi::debuggerManager()->backtraceGenerator()->state() != BacktraceGenerator::Loading) {
0407         DrKonqi::debuggerManager()->backtraceGenerator()->start();
0408     }
0409 }
0410 
0411 void ReportInterface::prepareCrashComment()
0412 {
0413     m_sentryPostbox.addUserFeedback(m_reportTitle + QLatin1Char('\n') + m_reportDetailText + QLatin1Char('\n') + DrKonqi::kdeBugzillaURL()
0414                                     + QLatin1String("show_bug.cgi?id=%1").arg(QString::number(m_sentReport)));
0415     maybePickUpPostbox();
0416 }
0417 
0418 void ReportInterface::createCrashMessage(const QString &message)
0419 {
0420     m_sentryPostbox.addUserFeedback(message);
0421     maybePickUpPostbox();
0422 }
0423 
0424 void ReportInterface::sendBugReport()
0425 {
0426     prepareCrashEvent();
0427 
0428     bool attach = false;
0429     Bugzilla::NewBug report = newBugReportTemplate();
0430     report.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Complete);
0431 
0432     // If the report is too long try to reduce it, try to not include the
0433     // backtrace and eventually give up.
0434     // Bugzilla has a hard-limit on the server side, if we cannot strip the
0435     // report down enough the submission will simply not work.
0436     // Exhausting the cap with just user input is nigh impossible, so we'll
0437     // forego handling of the report being too long even without without
0438     // backtrace.
0439     // https://bugs.kde.org/show_bug.cgi?id=248807
0440     if (report.description.size() >= s_maxReportSize) {
0441         report.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Reduced);
0442         attach = true;
0443     }
0444     if (report.description.size() >= s_maxReportSize) {
0445         report.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Exclude);
0446         attach = true;
0447     }
0448     Q_ASSERT(!report.description.isEmpty());
0449 
0450     connect(m_bugzillaManager, &BugzillaManager::sendReportErrorInvalidValues, this, &ReportInterface::sendUsingDefaultProduct);
0451     connect(m_bugzillaManager, &BugzillaManager::reportSent, this, [this, attach](int bugId) {
0452         if (attach) {
0453             m_attachToBugNumber = bugId;
0454             attachBacktrace(QStringLiteral("DrKonqi auto-attaching complete backtrace."));
0455         } else {
0456             m_sentReport = bugId;
0457             prepareCrashComment();
0458             m_sentryPostbox.deliver();
0459             maybeDone();
0460         }
0461     });
0462     connect(m_bugzillaManager, &BugzillaManager::sendReportError, this, &ReportInterface::sendReportError);
0463     m_bugzillaManager->sendReport(report);
0464 }
0465 
0466 void ReportInterface::sendUsingDefaultProduct() const
0467 {
0468     // Fallback function: if some of the custom values fail, we need to reset all the fields to the default
0469     //(and valid) bugzilla values; and try to resend
0470     Bugzilla::NewBug bug = newBugReportTemplate();
0471     bug.product = QLatin1String("kde");
0472     bug.component = QLatin1String("general");
0473     bug.platform = QLatin1String("unspecified");
0474     bug.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Complete);
0475     m_bugzillaManager->sendReport(bug);
0476 }
0477 
0478 void ReportInterface::attachBacktraceWithReport()
0479 {
0480     attachBacktrace(generateAttachmentComment());
0481 }
0482 
0483 void ReportInterface::attachBacktrace(const QString &comment)
0484 {
0485     // The user was added to the CC list, proceed with the attachment
0486     connect(m_bugzillaManager, &BugzillaManager::attachToReportSent, this, &ReportInterface::attachSent);
0487     connect(m_bugzillaManager, &BugzillaManager::attachToReportError, this, &ReportInterface::sendReportError);
0488 
0489     QString reportText = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Complete);
0490     QString filename = getSuggestedKCrashFilename(DrKonqi::crashedApplication());
0491     QLatin1String summary("New crash information added by DrKonqi");
0492 
0493     // Attach the report. The comment of the attachment also includes the bug description
0494     m_bugzillaManager->attachTextToReport(reportText, filename, summary, m_attachToBugNumber, comment);
0495 }
0496 
0497 void ReportInterface::attachSent(int attachId)
0498 {
0499     Q_UNUSED(attachId);
0500 
0501     // The bug was attached, consider it "sent"
0502     m_sentReport = attachId;
0503     prepareCrashComment();
0504     m_sentryPostbox.deliver();
0505     maybeDone();
0506 }
0507 
0508 QStringList ReportInterface::relatedBugzillaProducts() const
0509 {
0510     return m_productMapping->relatedBugzillaProducts();
0511 }
0512 
0513 bool ReportInterface::isWorthReporting() const
0514 {
0515     if (DrKonqi::ignoreQuality()) {
0516         return true;
0517     }
0518 
0519     // Evaluate if the provided information is useful enough to enable the automatic report
0520     bool needToReport = false;
0521 
0522     if (!m_userRememberCrashSituation) {
0523         // This should never happen... but...
0524         return false;
0525     }
0526 
0527     int rating = selectedOptionsRating();
0528 
0529     BacktraceParser::Usefulness use = DrKonqi::debuggerManager()->backtraceGenerator()->parser()->backtraceUsefulness();
0530 
0531     switch (use) {
0532     case BacktraceParser::ReallyUseful: {
0533         // Perfect backtrace: require at least one option or a 100%-50% reproducible crash
0534         needToReport = (rating >= 2) || (m_reproducible == ReproducibleEverytime || m_reproducible == ReproducibleSometimes);
0535         break;
0536     }
0537     case BacktraceParser::MayBeUseful: {
0538         // Not perfect backtrace: require at least two options or a 100% reproducible crash
0539         needToReport = (rating >= 3) || (m_reproducible == ReproducibleEverytime);
0540         break;
0541     }
0542     case BacktraceParser::ProbablyUseless:
0543         // Bad backtrace: require at least two options and always reproducible (strict)
0544         needToReport = (rating >= 5) && (m_reproducible == ReproducibleEverytime);
0545         break;
0546     case BacktraceParser::Useless:
0547     case BacktraceParser::InvalidUsefulness: {
0548         needToReport = false;
0549     }
0550     }
0551 
0552     return needToReport;
0553 }
0554 
0555 void ReportInterface::setAttachToBugNumber(uint bugNumber)
0556 {
0557     // If bugNumber>0, the report is going to be attached to bugNumber
0558     m_attachToBugNumber = bugNumber;
0559     Q_EMIT attachToBugNumberChanged();
0560 }
0561 
0562 uint ReportInterface::attachToBugNumber() const
0563 {
0564     return m_attachToBugNumber;
0565 }
0566 
0567 BugzillaManager *ReportInterface::bugzillaManager() const
0568 {
0569     return m_bugzillaManager;
0570 }
0571 
0572 ProductMapping *ReportInterface::productMapping() const
0573 {
0574     return m_productMapping;
0575 }
0576 
0577 void ReportInterface::maybeDone()
0578 {
0579     if (m_sentReport != 0 && m_sentryPostbox.hasDelivered()) {
0580         Q_EMIT done();
0581     }
0582 };
0583 
0584 ReportInterface *ReportInterface::self()
0585 {
0586     static ReportInterface interface;
0587     return &interface;
0588 }
0589 
0590 bool ReportInterface::hasCrashEventSent() const
0591 {
0592     return !isCrashEventSendingEnabled() || m_sentryPostbox.hasDelivered() || m_skipSentry;
0593 }
0594 
0595 bool ReportInterface::isCrashEventSendingEnabled() const
0596 {
0597     qCDebug(DRKONQI_LOG) << "sentry:" << Settings::self()->sentry() //
0598                          << "forceSentry:" << m_forceSentry //
0599                          << "hasDeletedFiles:" << DrKonqi::crashedApplication()->hasDeletedFiles() //
0600                          << "skipSentry:" << m_skipSentry;
0601     const auto enabled = Settings::self()->sentry() || m_forceSentry;
0602     return enabled && !DrKonqi::crashedApplication()->hasDeletedFiles();
0603 }
0604 
0605 void ReportInterface::setSendWhenReady(bool send)
0606 {
0607     m_sendWhenReady = send;
0608     maybePickUpPostbox();
0609 }
0610 
0611 void ReportInterface::maybePickUpPostbox()
0612 {
0613     qWarning() << Q_FUNC_INFO;
0614     if (m_sendWhenReady && !m_sentryPostbox.hasDelivered() && m_sentryPostbox.isReadyToDeliver()) {
0615         m_sentryPostbox.deliver();
0616     }
0617 }
0618 void ReportInterface::sendSentryReport()
0619 {
0620     m_forceSentry = true;
0621     trySentry();
0622     // sendWhenReady is set by the quit handling in main.cpp
0623 }
0624 
0625 void ReportInterface::trySentry()
0626 {
0627     qCDebug(DRKONQI_LOG) << "trying sentry?" << isCrashEventSendingEnabled() << !m_tryingSentry;
0628     if (isCrashEventSendingEnabled() && !m_tryingSentry) {
0629         m_tryingSentry = true;
0630         metaObject()->invokeMethod(this, [this] {
0631             connect(&m_sentryPostbox, &SentryPostbox::hasDeliveredChanged, this, &ReportInterface::crashEventSent);
0632             // Send crash event ASAP, if applicable. Trace quality doesn't matter for it.
0633             prepareCrashEvent();
0634         });
0635     }
0636 }
0637 
0638 QString ReportInterface::sentryEventId() const
0639 {
0640     return m_sentryPostbox.eventId();
0641 }
0642 
0643 #include "moc_reportinterface.cpp"