File indexing completed on 2024-04-28 05:26:49
0001 /******************************************************************* 0002 * productmapping.cpp 0003 * SPDX-FileCopyrightText: 2009 Dario Andres Rodriguez <andresbajotierra@gmail.com> 0004 * SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 * 0008 ******************************************************************/ 0009 0010 #include "productmapping.h" 0011 0012 #include "drkonqi_debug.h" 0013 #include <KConfig> 0014 #include <KConfigGroup> 0015 #include <QStandardPaths> 0016 0017 #include "bugzillalib.h" 0018 #include "crashedapplication.h" 0019 0020 ProductMapping::ProductMapping(const CrashedApplication *crashedApp, BugzillaManager *bzManager, QObject *parent) 0021 : QObject(parent) 0022 , m_crashedAppPtr(crashedApp) 0023 , m_bugzillaManagerPtr(bzManager) 0024 , m_bugzillaProductDisabled(false) 0025 , m_bugzillaVersionDisabled(false) 0026 0027 { 0028 // Default "fallback" values 0029 m_bugzillaProduct = crashedApp->fakeExecutableBaseName(); 0030 m_bugzillaComponent = QStringLiteral("general"); 0031 m_bugzillaVersionString = QStringLiteral("unspecified"); 0032 m_relatedBugzillaProducts = QStringList() << m_bugzillaProduct; 0033 0034 if (!crashedApp->productName().isEmpty()) { 0035 const auto l = crashedApp->productName().split(QLatin1Char('/'), Qt::SkipEmptyParts); 0036 if (l.size() == 2) { 0037 m_bugzillaProduct = l[0]; 0038 m_bugzillaComponent = l[1]; 0039 } else { 0040 m_bugzillaProduct = crashedApp->productName(); 0041 } 0042 m_hasExternallyProvidedProductName = true; 0043 } 0044 0045 map(crashedApp->fakeExecutableBaseName()); 0046 0047 // Get valid versions 0048 connect(m_bugzillaManagerPtr, &BugzillaManager::productInfoFetched, this, &ProductMapping::checkProductInfo); 0049 // Holding the connection so we can easily disconnect in the fallback logic. 0050 m_productInfoErrorConnection = connect(m_bugzillaManagerPtr, &BugzillaManager::productInfoError, this, &ProductMapping::fallBackToKDE); 0051 0052 m_bugzillaManagerPtr->fetchProductInfo(m_bugzillaProduct); 0053 } 0054 0055 void ProductMapping::map(const QString &appName) 0056 { 0057 mapUsingInternalFile(appName); 0058 getRelatedProductsUsingInternalFile(m_bugzillaProduct); 0059 } 0060 0061 void ProductMapping::mapUsingInternalFile(const QString &appName) 0062 { 0063 KConfig mappingsFile(QString::fromLatin1("mappings"), KConfig::NoGlobals, QStandardPaths::AppDataLocation); 0064 const KConfigGroup mappings = mappingsFile.group(QStringLiteral("Mappings")); 0065 if (mappings.hasKey(appName)) { 0066 if (m_hasExternallyProvidedProductName) { 0067 qCWarning(DRKONQI_LOG) << "Mapping found despite product information being provided by the application. Consider removing the mapping entry" 0068 << appName; 0069 } 0070 QString mappingString = mappings.readEntry(appName); 0071 if (!mappingString.isEmpty()) { 0072 QStringList list = mappingString.split(QLatin1Char('|'), Qt::SkipEmptyParts); 0073 if (list.count() == 2) { 0074 m_bugzillaProduct = list.at(0); 0075 m_bugzillaComponent = list.at(1); 0076 m_relatedBugzillaProducts = QStringList() << m_bugzillaProduct; 0077 } else { 0078 qCWarning(DRKONQI_LOG) << "Error while reading mapping entry. Sections found " << list.count(); 0079 } 0080 } else { 0081 qCWarning(DRKONQI_LOG) << "Error while reading mapping entry. Entry exists but it is empty " 0082 "(or there was an error when reading)"; 0083 } 0084 } 0085 } 0086 0087 void ProductMapping::getRelatedProductsUsingInternalFile(const QString &bugzillaProduct) 0088 { 0089 // ProductGroup -> kontact=kdepim 0090 // Groups -> kdepim=kontact|kmail|korganizer|akonadi|pimlibs..etc 0091 0092 KConfig mappingsFile(QString::fromLatin1("mappings"), KConfig::NoGlobals, QStandardPaths::AppDataLocation); 0093 const KConfigGroup productGroup = mappingsFile.group(QStringLiteral("ProductGroup")); 0094 0095 // Get groups of the application 0096 QStringList groups; 0097 if (productGroup.hasKey(bugzillaProduct)) { 0098 QString group = productGroup.readEntry(bugzillaProduct); 0099 if (group.isEmpty()) { 0100 qCWarning(DRKONQI_LOG) << "Error while reading mapping entry. Entry exists but it is empty " 0101 "(or there was an error when reading)"; 0102 return; 0103 } 0104 groups = group.split(QLatin1Char('|'), Qt::SkipEmptyParts); 0105 } 0106 0107 // All KDE apps use the KDE Platform (basic libs) 0108 groups << QLatin1String("kdeplatform"); 0109 0110 // Add the product itself 0111 m_relatedBugzillaProducts = QStringList() << m_bugzillaProduct; 0112 0113 // Get related products of each related group 0114 for (const QString &group : std::as_const(groups)) { 0115 const KConfigGroup bzGroups = mappingsFile.group(QStringLiteral("BZGroups")); 0116 if (bzGroups.hasKey(group)) { 0117 QString bzGroup = bzGroups.readEntry(group); 0118 if (!bzGroup.isEmpty()) { 0119 const QStringList relatedGroups = bzGroup.split(QLatin1Char('|'), Qt::SkipEmptyParts); 0120 if (!relatedGroups.isEmpty()) { 0121 m_relatedBugzillaProducts.append(relatedGroups); 0122 } 0123 } else { 0124 qCWarning(DRKONQI_LOG) << "Error while reading mapping entry. Entry exists but it is empty " 0125 "(or there was an error when reading)"; 0126 } 0127 } 0128 } 0129 } 0130 0131 void ProductMapping::checkProductInfo(const Bugzilla::Product::Ptr product) 0132 { 0133 // check whether the product itself is disabled for new reports, 0134 // which usually means that product/application is unmaintained. 0135 m_bugzillaProductDisabled = !product->isActive(); 0136 0137 // check whether the product on bugzilla contains the expected component 0138 if (!product->componentNames().contains(m_bugzillaComponent)) { 0139 m_bugzillaComponent = QLatin1String("general"); 0140 } 0141 0142 // find the appropriate version to use on bugzilla 0143 const QString version = m_crashedAppPtr->version(); 0144 const QStringList &allVersions = product->allVersions(); 0145 0146 if (allVersions.contains(version)) { 0147 // The version the crash application provided is a valid bugzilla version: use it ! 0148 m_bugzillaVersionString = version; 0149 } else if (version.endsWith(QLatin1String(".00"))) { 0150 // check if there is a version on bugzilla with just ".0" 0151 const QString shorterVersion = version.left(version.size() - 1); 0152 if (allVersions.contains(shorterVersion)) { 0153 m_bugzillaVersionString = shorterVersion; 0154 } 0155 } else if (!allVersions.contains(m_bugzillaVersionString)) { 0156 // No good match found, make sure the default is sound... 0157 // If our hardcoded fallback is not in bugzilla it was likely 0158 // renamed so we'll find the version with the lowest id instead 0159 // and that should technically have been the "default" version. 0160 Bugzilla::ProductVersion *lowestVersion = nullptr; 0161 const QList<Bugzilla::ProductVersion *> versions = product->versions(); 0162 for (const auto &version : versions) { 0163 if (!lowestVersion || lowestVersion->id() > version->id()) { 0164 lowestVersion = version; 0165 } 0166 } 0167 if (lowestVersion) { 0168 m_bugzillaVersionString = lowestVersion->name(); 0169 } 0170 } 0171 0172 // check whether that versions is disabled for new reports, which 0173 // usually means that version is outdated and not supported anymore. 0174 const QStringList &inactiveVersions = product->inactiveVersions(); 0175 m_bugzillaVersionDisabled = inactiveVersions.contains(m_bugzillaVersionString); 0176 0177 Q_EMIT resolved(); 0178 } 0179 0180 void ProductMapping::fallBackToKDE() 0181 { 0182 // Fall back to the generic kde product when we couldn't find an explicit mapping. 0183 // This is in an effort to make it as easy as possible to file a bug, unfortunately it means someone will 0184 // have to triage it accordingly. 0185 // Disconnect to safe-guard against infinite loop should kde also fail for some reason.... 0186 // An argument could be made that we should raise a user error if this fails again, 0187 // 'kde' not resolving shouldn't ever happen and points at a huge problem somewhere. 0188 disconnect(m_productInfoErrorConnection); 0189 m_bugzillaProductOriginal = m_bugzillaProduct; 0190 m_bugzillaProduct = QStringLiteral("kde"); 0191 m_bugzillaManagerPtr->fetchProductInfo(m_bugzillaProduct); 0192 0193 Q_EMIT resolved(); 0194 } 0195 0196 QStringList ProductMapping::relatedBugzillaProducts() const 0197 { 0198 return m_relatedBugzillaProducts; 0199 } 0200 0201 QString ProductMapping::bugzillaProduct() const 0202 { 0203 return m_bugzillaProduct; 0204 } 0205 0206 QString ProductMapping::bugzillaComponent() const 0207 { 0208 return m_bugzillaComponent; 0209 } 0210 0211 QString ProductMapping::bugzillaVersion() const 0212 { 0213 return m_bugzillaVersionString; 0214 } 0215 0216 bool ProductMapping::bugzillaProductDisabled() const 0217 { 0218 return m_bugzillaProductDisabled; 0219 } 0220 0221 bool ProductMapping::bugzillaVersionDisabled() const 0222 { 0223 return m_bugzillaVersionDisabled; 0224 } 0225 0226 QString ProductMapping::bugzillaProductOriginal() const 0227 { 0228 return m_bugzillaProductOriginal; 0229 } 0230 0231 #include "moc_productmapping.cpp"