File indexing completed on 2024-04-14 15:32:41

0001 /*******************************************************************
0002  * duplicatefinderjob.cpp
0003  * SPDX-FileCopyrightText: 2011 Matthias Fuchs <mat69@gmx.net>
0004  * SPDX-FileCopyrightText: 2019-2021 Harald Sitter <sitter@kde.org>
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  *
0008  ******************************************************************/
0009 
0010 #include "duplicatefinderjob.h"
0011 
0012 #include <QtConcurrent>
0013 
0014 #include "backtracegenerator.h"
0015 #include "debuggermanager.h"
0016 #include "drkonqi.h"
0017 #include "drkonqi_debug.h"
0018 #include "parser/backtraceparser.h"
0019 
0020 DuplicateFinderJob::DuplicateFinderJob(const QList<Bugzilla::Bug::Ptr> &bugs, BugzillaManager *manager, QObject *parent)
0021     : KJob(parent)
0022     , m_manager(manager)
0023     , m_bugs(bugs)
0024 {
0025     qCDebug(DRKONQI_LOG) << "Possible duplicates:" << m_bugs.size();
0026     connect(m_manager, &BugzillaManager::bugReportFetched, this, &DuplicateFinderJob::slotBugReportFetched);
0027     connect(m_manager, &BugzillaManager::bugReportError, this, &DuplicateFinderJob::slotError);
0028 
0029     connect(m_manager, &BugzillaManager::commentsFetched, this, &DuplicateFinderJob::slotCommentsFetched);
0030     connect(m_manager, &BugzillaManager::commentsError, this, &DuplicateFinderJob::slotError);
0031 }
0032 
0033 DuplicateFinderJob::~DuplicateFinderJob() = default;
0034 
0035 void DuplicateFinderJob::start()
0036 {
0037     if (qEnvironmentVariableIntValue("DRKONQI_SKIP_DUPES") > 0) {
0038         metaObject()->invokeMethod(this, &DuplicateFinderJob::emitResult);
0039         return;
0040     }
0041 
0042     analyzeNextBug();
0043 }
0044 
0045 DuplicateFinderJob::Result DuplicateFinderJob::result() const
0046 {
0047     return m_result;
0048 }
0049 
0050 void DuplicateFinderJob::analyzeNextBug()
0051 {
0052     if (m_bugs.isEmpty()) {
0053         emitResult();
0054         return;
0055     }
0056 
0057     m_bug = m_bugs.takeFirst();
0058     qCDebug(DRKONQI_LOG) << "Fetching:" << m_bug->id();
0059     m_manager->fetchComments(m_bug, this);
0060 }
0061 
0062 void DuplicateFinderJob::fetchBug(int bugId)
0063 {
0064     if (bugId > 0) {
0065         qCDebug(DRKONQI_LOG) << "Fetching:" << bugId;
0066         m_manager->fetchBugReport(bugId, this);
0067     } else {
0068         qCDebug(DRKONQI_LOG) << "Bug id not valid:" << bugId;
0069         analyzeNextBug();
0070     }
0071 }
0072 
0073 void DuplicateFinderJob::slotBugReportFetched(const Bugzilla::Bug::Ptr &bug, QObject *owner)
0074 {
0075     if (this != owner) {
0076         return;
0077     }
0078 
0079     m_bug = bug;
0080     qCDebug(DRKONQI_LOG) << "Fetching:" << m_bug->id();
0081     m_manager->fetchComments(m_bug, this);
0082 }
0083 
0084 void DuplicateFinderJob::slotCommentsFetched(const QList<Bugzilla::Comment::Ptr> &comments, QObject *owner)
0085 {
0086     if (this != owner) {
0087         return;
0088     }
0089 
0090     // NOTE: we do not hold the comments in our bug object, once they go out
0091     //   of scope they are gone again. We have no use for keeping them in memory
0092     //   a user might look at 3 out of 20 bugs, and for those we can simply
0093     //   request the comments again instead of holding the potentially very large
0094     //   comments in memory.
0095 
0096     BacktraceGenerator *btGenerator = DrKonqi::debuggerManager()->backtraceGenerator();
0097     const QList<BacktraceLine> ourTraceLines = btGenerator->parser()->parsedBacktraceLines();
0098 
0099     // QFuture the parsing. We'll not want to block the GUI thread with this nonesense.
0100     auto watcher = new QFutureWatcher<ParseBugBacktraces::DuplicateRating>(this);
0101     connect(watcher, &std::remove_pointer_t<decltype(watcher)>::finished, this, [this, watcher] {
0102         // runs on our thread again
0103         watcher->deleteLater();
0104         commentsParsed(watcher->result());
0105     });
0106     auto future = QtConcurrent::run([comments, ourTraceLines]() -> ParseBugBacktraces::DuplicateRating {
0107         ParseBugBacktraces parse(comments);
0108         parse.parse();
0109         return parse.findDuplicate(ourTraceLines);
0110     });
0111     watcher->setFuture(future);
0112 }
0113 
0114 void DuplicateFinderJob::commentsParsed(ParseBugBacktraces::DuplicateRating rating)
0115 {
0116     qCDebug(DRKONQI_LOG) << "Duplicate rating:" << rating;
0117 
0118     // TODO handle more cases here
0119     if (rating != ParseBugBacktraces::PerfectDuplicate) {
0120         qCDebug(DRKONQI_LOG) << "Bug" << m_bug->id() << "most likely not a duplicate:" << rating;
0121         analyzeNextBug();
0122         return;
0123     }
0124 
0125     bool unknownStatus = (m_bug->status() == Bugzilla::Bug::Status::Unknown);
0126     bool unknownResolution = (m_bug->resolution() == Bugzilla::Bug::Resolution::Unknown);
0127 
0128     // The Bug is a duplicate, now find out the status and resolution of the existing report
0129     if (m_bug->resolution() == Bugzilla::Bug::Resolution::DUPLICATE) {
0130         qCDebug(DRKONQI_LOG) << "Found duplicate is a duplicate itself.";
0131         if (!m_result.duplicate) {
0132             m_result.duplicate = m_bug->id();
0133         }
0134         fetchBug(m_bug->dupe_of());
0135     } else if (unknownStatus || unknownResolution) {
0136         // A resolution is unknown when the bug is unresolved.
0137         // Status generally is never unknown.
0138         qCDebug(DRKONQI_LOG) << "Either the status or the resolution is unknown.";
0139         qCDebug(DRKONQI_LOG) << "Status \"" << m_bug->status() << "\" known:" << !unknownStatus;
0140         qCDebug(DRKONQI_LOG) << "Resolution \"" << m_bug->resolution() << "\" known:" << !unknownResolution;
0141         analyzeNextBug();
0142     } else {
0143         if (!m_result.duplicate) {
0144             m_result.duplicate = m_bug->id();
0145         }
0146         m_result.parentDuplicate = m_bug->id();
0147         m_result.status = m_bug->status();
0148         m_result.resolution = m_bug->resolution();
0149         qCDebug(DRKONQI_LOG) << "Found duplicate information (id/status/resolution):" << m_bug->id() << m_bug->status() << m_bug->resolution();
0150         emitResult();
0151     }
0152 }
0153 
0154 void DuplicateFinderJob::slotError(const QString &message, QObject *owner)
0155 {
0156     if (this != owner) {
0157         return;
0158     }
0159     qCDebug(DRKONQI_LOG) << "Error fetching bug:" << message;
0160     analyzeNextBug();
0161 }