File indexing completed on 2024-12-15 03:45:04
0001 /* 0002 SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: MIT 0005 */ 0006 0007 #include <kuserfeedback_version.h> 0008 0009 #include "logging_p.h" 0010 #include "provider.h" 0011 #include "provider_p.h" 0012 #include "abstractdatasource.h" 0013 #include "startcountsource.h" 0014 #include "surveyinfo.h" 0015 #include "usagetimesource.h" 0016 0017 #include <common/surveytargetexpressionparser.h> 0018 #include <common/surveytargetexpressionevaluator.h> 0019 0020 #include <QCoreApplication> 0021 #include <QDebug> 0022 #include <QDir> 0023 #include <QJsonArray> 0024 #include <QJsonDocument> 0025 #include <QJsonObject> 0026 #include <QStandardPaths> 0027 #include <QMetaEnum> 0028 #include <QNetworkAccessManager> 0029 #include <QNetworkReply> 0030 #include <QNetworkRequest> 0031 #include <QSettings> 0032 #include <QUuid> 0033 0034 #include <algorithm> 0035 #include <numeric> 0036 0037 using namespace KUserFeedback; 0038 0039 ProviderPrivate::ProviderPrivate(Provider *qq) 0040 : q(qq) 0041 , networkAccessManager(nullptr) 0042 , redirectCount(0) 0043 , submissionInterval(-1) 0044 , telemetryMode(Provider::NoTelemetry) 0045 , surveyInterval(-1) 0046 , startCount(0) 0047 , usageTime(0) 0048 , encouragementStarts(-1) 0049 , encouragementTime(-1) 0050 , encouragementDelay(300) 0051 , encouragementInterval(-1) 0052 , backoffIntervalMinutes(-1) 0053 { 0054 submissionTimer.setSingleShot(true); 0055 QObject::connect(&submissionTimer, &QTimer::timeout, q, &Provider::submit); 0056 0057 startTime.start(); 0058 0059 encouragementTimer.setSingleShot(true); 0060 QObject::connect(&encouragementTimer, &QTimer::timeout, q, [this]() { emitShowEncouragementMessage(); }); 0061 } 0062 0063 ProviderPrivate::~ProviderPrivate() 0064 { 0065 qDeleteAll(dataSources); 0066 } 0067 0068 int ProviderPrivate::currentApplicationTime() const 0069 { 0070 return usageTime + (startTime.elapsed() / 1000); 0071 } 0072 0073 static QMetaEnum telemetryModeEnum() 0074 { 0075 const auto idx = Provider::staticMetaObject.indexOfEnumerator("TelemetryMode"); 0076 Q_ASSERT(idx >= 0); 0077 return Provider::staticMetaObject.enumerator(idx); 0078 } 0079 0080 std::unique_ptr<QSettings> ProviderPrivate::makeSettings() const 0081 { 0082 // attempt to put our settings next to the application ones, 0083 // so replicate how QSettings handles this 0084 auto org = 0085 #ifdef Q_OS_MAC 0086 QCoreApplication::organizationDomain().isEmpty() ? QCoreApplication::organizationName() : QCoreApplication::organizationDomain(); 0087 #else 0088 QCoreApplication::organizationName().isEmpty() ? QCoreApplication::organizationDomain() : QCoreApplication::organizationName(); 0089 #endif 0090 if (org.isEmpty()) 0091 org = QLatin1String("Unknown Organization"); 0092 0093 std::unique_ptr<QSettings> s(new QSettings(org, QStringLiteral("UserFeedback.") + productId)); 0094 return s; 0095 } 0096 0097 std::unique_ptr<QSettings> ProviderPrivate::makeGlobalSettings() const 0098 { 0099 const auto org = 0100 #ifdef Q_OS_MAC 0101 QStringLiteral("kde.org"); 0102 #else 0103 QStringLiteral("KDE"); 0104 #endif 0105 std::unique_ptr<QSettings> s(new QSettings(org, QStringLiteral("UserFeedback"))); 0106 return s; 0107 } 0108 0109 void ProviderPrivate::load() 0110 { 0111 auto s = makeSettings(); 0112 s->beginGroup(QStringLiteral("UserFeedback")); 0113 lastSubmitTime = s->value(QStringLiteral("LastSubmission")).toDateTime(); 0114 0115 const auto modeStr = s->value(QStringLiteral("StatisticsCollectionMode")).toByteArray(); 0116 telemetryMode = static_cast<Provider::TelemetryMode>(std::max(telemetryModeEnum().keyToValue(modeStr.constData()), 0)); 0117 0118 surveyInterval = s->value(QStringLiteral("SurveyInterval"), -1).toInt(); 0119 lastSurveyTime = s->value(QStringLiteral("LastSurvey")).toDateTime(); 0120 completedSurveys = s->value(QStringLiteral("CompletedSurveys"), QStringList()).toStringList(); 0121 0122 startCount = std::max(s->value(QStringLiteral("ApplicationStartCount"), 0).toInt(), 0); 0123 usageTime = std::max(s->value(QStringLiteral("ApplicationTime"), 0).toInt(), 0); 0124 0125 lastEncouragementTime = s->value(QStringLiteral("LastEncouragement")).toDateTime(); 0126 0127 s->endGroup(); 0128 0129 // ensure consistent times if settings is corrupt, to avoid overflows later in the code 0130 const auto now = QDateTime::currentDateTime(); 0131 if (now < lastSubmitTime) { 0132 lastSubmitTime = now; 0133 } 0134 if (now < lastSurveyTime) { 0135 lastSurveyTime = now; 0136 } 0137 if (now < lastEncouragementTime) { 0138 lastEncouragementTime = now; 0139 } 0140 0141 foreach (auto source, dataSources) { 0142 s->beginGroup(QStringLiteral("Source-") + source->id()); 0143 source->load(s.get()); 0144 s->endGroup(); 0145 } 0146 0147 auto g = makeGlobalSettings(); 0148 g->beginGroup(QStringLiteral("UserFeedback")); 0149 lastSurveyTime = std::max(g->value(QStringLiteral("LastSurvey")).toDateTime(), lastSurveyTime); 0150 lastEncouragementTime = std::max(g->value(QStringLiteral("LastEncouragement")).toDateTime(), lastEncouragementTime); 0151 } 0152 0153 void ProviderPrivate::store() 0154 { 0155 auto s = makeSettings(); 0156 s->beginGroup(QStringLiteral("UserFeedback")); 0157 0158 // another process might have changed this, so read the base value first before writing 0159 usageTime = std::max(s->value(QStringLiteral("ApplicationTime"), 0).toInt(), usageTime); 0160 s->setValue(QStringLiteral("ApplicationTime"), currentApplicationTime()); 0161 usageTime = currentApplicationTime(); 0162 startTime.restart(); 0163 0164 s->endGroup(); 0165 0166 foreach (auto source, dataSources) { 0167 s->beginGroup(QStringLiteral("Source-") + source->id()); 0168 source->store(s.get()); 0169 s->endGroup(); 0170 } 0171 } 0172 0173 void ProviderPrivate::storeOne(const QString &key, const QVariant &value) 0174 { 0175 auto s = makeSettings(); 0176 s->beginGroup(QStringLiteral("UserFeedback")); 0177 s->setValue(key, value); 0178 } 0179 0180 void ProviderPrivate::storeOneGlobal(const QString &key, const QVariant &value) 0181 { 0182 auto s = makeGlobalSettings(); 0183 s->beginGroup(QStringLiteral("UserFeedback")); 0184 s->setValue(key, value); 0185 } 0186 0187 void ProviderPrivate::aboutToQuit() 0188 { 0189 store(); 0190 } 0191 0192 bool ProviderPrivate::isValidSource(AbstractDataSource *source) const 0193 { 0194 if (source->id().isEmpty()) { 0195 qCWarning(Log) << "Skipping data source with empty name!"; 0196 return false; 0197 } 0198 if (source->telemetryMode() == Provider::NoTelemetry) { 0199 qCWarning(Log) << "Source" << source->id() << "attempts to report data unconditionally, ignoring!"; 0200 return false; 0201 } 0202 if (source->description().isEmpty()) { 0203 qCWarning(Log) << "Source" << source->id() << "has no description, ignoring!"; 0204 return false; 0205 } 0206 0207 Q_ASSERT(!source->id().isEmpty()); 0208 Q_ASSERT(source->telemetryMode() != Provider::NoTelemetry); 0209 Q_ASSERT(!source->description().isEmpty()); 0210 return true; 0211 } 0212 0213 QByteArray ProviderPrivate::jsonData(Provider::TelemetryMode mode) const 0214 { 0215 QJsonObject obj; 0216 if (mode != Provider::NoTelemetry) { 0217 foreach (auto source, dataSources) { 0218 if (!isValidSource(source) || !source->isActive()) 0219 continue; 0220 if (mode < source->telemetryMode()) 0221 continue; 0222 const auto data = source->data(); 0223 if (data.canConvert<QVariantMap>()) 0224 obj.insert(source->id(), QJsonObject::fromVariantMap(data.toMap())); 0225 else if (data.canConvert<QVariantList>()) 0226 obj.insert(source->id(), QJsonArray::fromVariantList(data.value<QVariantList>())); 0227 else 0228 qCWarning(Log) << "wrong type for" << source->id() << data; 0229 } 0230 } 0231 0232 QJsonDocument doc(obj); 0233 return doc.toJson(); 0234 } 0235 0236 void ProviderPrivate::scheduleNextSubmission(qint64 minTime) 0237 { 0238 submissionTimer.stop(); 0239 if (!q->isEnabled()) 0240 return; 0241 if (submissionInterval <= 0 || (telemetryMode == Provider::NoTelemetry && surveyInterval < 0)) 0242 return; 0243 0244 if (minTime == 0) { 0245 // If this is a regularly scheduled submission reset the backoff 0246 backoffIntervalMinutes = -1; 0247 } 0248 0249 Q_ASSERT(submissionInterval > 0); 0250 0251 const auto nextSubmission = lastSubmitTime.addDays(submissionInterval); 0252 const auto now = QDateTime::currentDateTime(); 0253 submissionTimer.start(std::max(minTime, now.msecsTo(nextSubmission))); 0254 } 0255 0256 void ProviderPrivate::submitFinished(QNetworkReply *reply) 0257 { 0258 reply->deleteLater(); 0259 0260 if (reply->error() != QNetworkReply::NoError) { 0261 if (backoffIntervalMinutes == -1) { 0262 backoffIntervalMinutes = 2; 0263 } else { 0264 backoffIntervalMinutes = backoffIntervalMinutes * 2; 0265 } 0266 qCWarning(Log) << "failed to submit user feedback:" << reply->errorString() << reply->readAll() << ". Calling scheduleNextSubmission with minTime" << backoffIntervalMinutes << "minutes"; 0267 scheduleNextSubmission(backoffIntervalMinutes * 60000ll); 0268 return; 0269 } 0270 0271 const auto redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); 0272 if (redirectTarget.isValid()) { 0273 if (++redirectCount >= 20) { 0274 qCWarning(Log) << "Redirect loop on" << reply->url().resolved(redirectTarget).toString(); 0275 return; 0276 } 0277 submit(reply->url().resolved(redirectTarget)); 0278 return; 0279 } 0280 0281 lastSubmitTime = QDateTime::currentDateTime(); 0282 0283 auto s = makeSettings(); 0284 s->beginGroup(QStringLiteral("UserFeedback")); 0285 s->setValue(QStringLiteral("LastSubmission"), lastSubmitTime); 0286 s->endGroup(); 0287 0288 writeAuditLog(lastSubmitTime); 0289 0290 // reset source counters 0291 foreach (auto source, dataSources) { 0292 s->beginGroup(QStringLiteral("Source-") + source->id()); 0293 source->reset(s.get()); 0294 s->endGroup(); 0295 } 0296 0297 const auto obj = QJsonDocument::fromJson(reply->readAll()).object(); 0298 const auto it = obj.find(QLatin1String("surveys")); 0299 if (it != obj.end() && surveyInterval >= 0) { 0300 const auto a = it.value().toArray(); 0301 qCDebug(Log) << "received" << a.size() << "surveys"; 0302 foreach(const auto &s, a) { 0303 const auto survey = SurveyInfo::fromJson(s.toObject()); 0304 if (selectSurvey(survey)) 0305 break; 0306 } 0307 } 0308 0309 scheduleNextSubmission(); 0310 } 0311 0312 QVariant ProviderPrivate::sourceData(const QString& sourceId) const 0313 { 0314 foreach (auto src, dataSources) { 0315 if (src->id() == sourceId) 0316 return src->data(); 0317 } 0318 return QVariant(); 0319 } 0320 0321 bool ProviderPrivate::selectSurvey(const SurveyInfo &survey) const 0322 { 0323 qCDebug(Log) << "got survey:" << survey.url() << survey.target(); 0324 if (!q->isEnabled() || !survey.isValid() || completedSurveys.contains(survey.uuid().toString())) 0325 return false; 0326 0327 if (surveyInterval != 0 && lastSurveyTime.addDays(surveyInterval) > QDateTime::currentDateTime()) 0328 return false; 0329 0330 if (!survey.target().isEmpty()) { 0331 SurveyTargetExpressionParser parser; 0332 if (!parser.parse(survey.target())) { 0333 qCDebug(Log) << "failed to parse target expression"; 0334 return false; 0335 } 0336 0337 SurveyTargetExpressionEvaluator eval; 0338 eval.setDataProvider(this); 0339 if (!eval.evaluate(parser.expression())) 0340 return false; 0341 } 0342 0343 qCDebug(Log) << "picked survey:" << survey.url(); 0344 Q_EMIT q->surveyAvailable(survey); 0345 return true; 0346 } 0347 0348 Provider::TelemetryMode ProviderPrivate::highestTelemetryMode() const 0349 { 0350 auto mode = Provider::NoTelemetry; 0351 foreach (auto src, dataSources) 0352 mode = std::max(mode, src->telemetryMode()); 0353 return mode; 0354 } 0355 0356 void ProviderPrivate::scheduleEncouragement() 0357 { 0358 encouragementTimer.stop(); 0359 if (!q->isEnabled()) 0360 return; 0361 0362 // already done, not repetition 0363 if (lastEncouragementTime.isValid() && encouragementInterval <= 0) 0364 return; 0365 0366 if (encouragementStarts < 0 && encouragementTime < 0) // encouragement disabled 0367 return; 0368 0369 if (encouragementStarts > startCount) // we need more starts 0370 return; 0371 0372 if (telemetryMode >= highestTelemetryMode() && surveyInterval == 0) // already everything enabled 0373 return; 0374 // no repetition if some feedback is enabled 0375 if (lastEncouragementTime.isValid() && (telemetryMode > Provider::NoTelemetry || surveyInterval >= 0)) 0376 return; 0377 0378 Q_ASSERT(encouragementDelay >= 0); 0379 int timeToEncouragement = encouragementDelay; 0380 if (encouragementTime > 0) 0381 timeToEncouragement = std::max(timeToEncouragement, encouragementTime - currentApplicationTime()); 0382 if (lastEncouragementTime.isValid()) { 0383 Q_ASSERT(encouragementInterval > 0); 0384 const auto targetTime = lastEncouragementTime.addDays(encouragementInterval); 0385 timeToEncouragement = std::max(timeToEncouragement, (int)QDateTime::currentDateTime().secsTo(targetTime)); 0386 } 0387 encouragementTimer.start(timeToEncouragement * 1000); 0388 } 0389 0390 void ProviderPrivate::emitShowEncouragementMessage() 0391 { 0392 lastEncouragementTime = QDateTime::currentDateTime(); // TODO make this explicit, in case the host application decides to delay? 0393 storeOne(QStringLiteral("LastEncouragement"), lastEncouragementTime); 0394 storeOneGlobal(QStringLiteral("LastEncouragement"), lastEncouragementTime); 0395 Q_EMIT q->showEncouragementMessage(); 0396 } 0397 0398 0399 Provider::Provider(QObject *parent) : 0400 QObject(parent), 0401 d(new ProviderPrivate(this)) 0402 { 0403 qCDebug(Log); 0404 0405 connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() { d->aboutToQuit(); }); 0406 0407 auto domain = QCoreApplication::organizationDomain().split(QLatin1Char('.')); 0408 std::reverse(domain.begin(), domain.end()); 0409 auto id = domain.join(QLatin1String(".")); 0410 if (!id.isEmpty()) 0411 id += QLatin1Char('.'); 0412 id += QCoreApplication::applicationName(); 0413 setProductIdentifier(id); 0414 } 0415 0416 Provider::~Provider() 0417 { 0418 delete d; 0419 } 0420 0421 bool Provider::isEnabled() const 0422 { 0423 auto s = d->makeGlobalSettings(); 0424 s->beginGroup(QStringLiteral("UserFeedback")); 0425 return s->value(QStringLiteral("Enabled"), true).toBool(); 0426 } 0427 0428 void Provider::setEnabled(bool enabled) 0429 { 0430 if (enabled == isEnabled()) 0431 return; 0432 d->storeOneGlobal(QStringLiteral("Enabled"), enabled); 0433 Q_EMIT enabledChanged(); 0434 } 0435 0436 void Provider::restoreDefaults() 0437 { 0438 setTelemetryMode(NoTelemetry); 0439 setSurveyInterval(-1); 0440 } 0441 0442 QString Provider::productIdentifier() const 0443 { 0444 return d->productId; 0445 } 0446 0447 void Provider::setProductIdentifier(const QString &productId) 0448 { 0449 Q_ASSERT(!productId.isEmpty()); 0450 if (productId == d->productId) 0451 return; 0452 d->productId = productId; 0453 0454 d->load(); 0455 d->startCount++; 0456 d->storeOne(QStringLiteral("ApplicationStartCount"), d->startCount); 0457 0458 Q_EMIT providerSettingsChanged(); 0459 0460 d->scheduleEncouragement(); 0461 d->scheduleNextSubmission(); 0462 } 0463 0464 QUrl Provider::feedbackServer() const 0465 { 0466 return d->serverUrl; 0467 } 0468 0469 void Provider::setFeedbackServer(const QUrl &url) 0470 { 0471 if (d->serverUrl == url) 0472 return; 0473 d->serverUrl = url; 0474 Q_EMIT providerSettingsChanged(); 0475 } 0476 0477 int Provider::submissionInterval() const 0478 { 0479 return d->submissionInterval; 0480 } 0481 0482 void Provider::setSubmissionInterval(int days) 0483 { 0484 if (d->submissionInterval == days) 0485 return; 0486 d->submissionInterval = days; 0487 Q_EMIT providerSettingsChanged(); 0488 d->scheduleNextSubmission(); 0489 } 0490 0491 Provider::TelemetryMode Provider::telemetryMode() const 0492 { 0493 return d->telemetryMode; 0494 } 0495 0496 void Provider::setTelemetryMode(TelemetryMode mode) 0497 { 0498 if (d->telemetryMode == mode) 0499 return; 0500 0501 d->telemetryMode = mode; 0502 d->storeOne(QStringLiteral("StatisticsCollectionMode"), QString::fromLatin1(telemetryModeEnum().valueToKey(d->telemetryMode))); 0503 d->scheduleNextSubmission(); 0504 d->scheduleEncouragement(); 0505 Q_EMIT telemetryModeChanged(); 0506 } 0507 0508 void Provider::addDataSource(AbstractDataSource *source) 0509 { 0510 // special cases for sources where we track the data here, as it's needed even if we don't report it 0511 if (auto countSrc = dynamic_cast<StartCountSource*>(source)) 0512 countSrc->setProvider(d); 0513 if (auto timeSrc = dynamic_cast<UsageTimeSource*>(source)) 0514 timeSrc->setProvider(d); 0515 0516 d->dataSources.push_back(source); 0517 d->dataSourcesById[source->id()] = source; 0518 0519 auto s = d->makeSettings(); 0520 s->beginGroup(QStringLiteral("Source-") + source->id()); 0521 source->load(s.get()); 0522 0523 Q_EMIT dataSourcesChanged(); 0524 } 0525 0526 QVector<AbstractDataSource*> Provider::dataSources() const 0527 { 0528 return d->dataSources; 0529 } 0530 0531 AbstractDataSource *Provider::dataSource(const QString &id) const 0532 { 0533 auto it = d->dataSourcesById.find(id); 0534 return it != std::end(d->dataSourcesById) ? *it : nullptr; 0535 } 0536 0537 int Provider::surveyInterval() const 0538 { 0539 return d->surveyInterval; 0540 } 0541 0542 void Provider::setSurveyInterval(int days) 0543 { 0544 if (d->surveyInterval == days) 0545 return; 0546 0547 d->surveyInterval = days; 0548 d->storeOne(QStringLiteral("SurveyInterval"), d->surveyInterval); 0549 0550 d->scheduleNextSubmission(); 0551 d->scheduleEncouragement(); 0552 Q_EMIT surveyIntervalChanged(); 0553 } 0554 0555 int Provider::applicationStartsUntilEncouragement() const 0556 { 0557 return d->encouragementStarts; 0558 } 0559 0560 void Provider::setApplicationStartsUntilEncouragement(int starts) 0561 { 0562 if (d->encouragementStarts == starts) 0563 return; 0564 d->encouragementStarts = starts; 0565 Q_EMIT providerSettingsChanged(); 0566 d->scheduleEncouragement(); 0567 } 0568 0569 int Provider::applicationUsageTimeUntilEncouragement() const 0570 { 0571 return d->encouragementTime; 0572 } 0573 0574 void Provider::setApplicationUsageTimeUntilEncouragement(int secs) 0575 { 0576 if (d->encouragementTime == secs) 0577 return; 0578 d->encouragementTime = secs; 0579 Q_EMIT providerSettingsChanged(); 0580 d->scheduleEncouragement(); 0581 } 0582 0583 int Provider::encouragementDelay() const 0584 { 0585 return d->encouragementDelay; 0586 } 0587 0588 void Provider::setEncouragementDelay(int secs) 0589 { 0590 if (d->encouragementDelay == secs) 0591 return; 0592 d->encouragementDelay = std::max(0, secs); 0593 Q_EMIT providerSettingsChanged(); 0594 d->scheduleEncouragement(); 0595 } 0596 0597 int Provider::encouragementInterval() const 0598 { 0599 return d->encouragementInterval; 0600 } 0601 0602 void Provider::setEncouragementInterval(int days) 0603 { 0604 if (d->encouragementInterval == days) 0605 return; 0606 d->encouragementInterval = days; 0607 Q_EMIT providerSettingsChanged(); 0608 d->scheduleEncouragement(); 0609 } 0610 0611 void Provider::surveyCompleted(const SurveyInfo &info) 0612 { 0613 d->completedSurveys.push_back(info.uuid().toString()); 0614 d->lastSurveyTime = QDateTime::currentDateTime(); 0615 0616 auto s = d->makeSettings(); 0617 s->beginGroup(QStringLiteral("UserFeedback")); 0618 s->setValue(QStringLiteral("LastSurvey"), d->lastSurveyTime); 0619 s->setValue(QStringLiteral("CompletedSurveys"), d->completedSurveys); 0620 0621 d->storeOneGlobal(QStringLiteral("LastSurvey"), d->lastSurveyTime); 0622 } 0623 0624 void Provider::load() 0625 { 0626 d->load(); 0627 } 0628 0629 void Provider::store() 0630 { 0631 d->store(); 0632 } 0633 0634 void Provider::submit() 0635 { 0636 if (!isEnabled()) { 0637 qCWarning(Log) << "Global kill switch is enabled"; 0638 return; 0639 } 0640 if (d->productId.isEmpty()) { 0641 qCWarning(Log) << "No productId specified!"; 0642 return; 0643 } 0644 if (!d->serverUrl.isValid()) { 0645 qCWarning(Log) << "No feedback server URL specified!"; 0646 return; 0647 } 0648 0649 if (!d->networkAccessManager) 0650 d->networkAccessManager = new QNetworkAccessManager(this); 0651 0652 auto url = d->serverUrl; 0653 auto path = d->serverUrl.path(); 0654 if (!path.endsWith(QLatin1Char('/'))) 0655 path += QLatin1Char('/'); 0656 path += QStringLiteral("receiver/submit/") + d->productId; 0657 url.setPath(path); 0658 d->submitProbe(url); 0659 } 0660 0661 void ProviderPrivate::submit(const QUrl &url) 0662 { 0663 QNetworkRequest request(url); 0664 request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); 0665 request.setHeader(QNetworkRequest::UserAgentHeader, QString(QStringLiteral("KUserFeedback/") + QStringLiteral(KUSERFEEDBACK_VERSION_STRING))); 0666 auto reply = networkAccessManager->post(request, jsonData(telemetryMode)); 0667 QObject::connect(reply, &QNetworkReply::finished, q, [this, reply]() { submitFinished(reply); }); 0668 } 0669 0670 void ProviderPrivate::submitProbe(const QUrl &url) 0671 { 0672 QNetworkRequest request(url); 0673 request.setHeader(QNetworkRequest::UserAgentHeader, QString(QStringLiteral("KUserFeedback/") + QStringLiteral(KUSERFEEDBACK_VERSION_STRING))); 0674 auto reply = networkAccessManager->get(request); 0675 QObject::connect(reply, &QNetworkReply::finished, q, [this, reply]() { submitProbeFinished(reply); }); 0676 } 0677 0678 void ProviderPrivate::submitProbeFinished(QNetworkReply *reply) 0679 { 0680 reply->deleteLater(); 0681 0682 if (reply->error() != QNetworkReply::NoError) { 0683 qCWarning(Log) << "failed to probe user feedback submission interface:" << reply->errorString() << reply->readAll(); 0684 return; 0685 } 0686 0687 const auto redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); 0688 if (redirectTarget.isValid()) { 0689 if (++redirectCount >= 20) { 0690 qCWarning(Log) << "Redirect loop on" << reply->url().resolved(redirectTarget).toString(); 0691 return; 0692 } 0693 submitProbe(reply->url().resolved(redirectTarget)); 0694 return; 0695 } 0696 0697 submit(reply->url()); 0698 } 0699 0700 void ProviderPrivate::writeAuditLog(const QDateTime &dt) 0701 { 0702 const QString path = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QStringLiteral("/kuserfeedback/audit"); 0703 QDir().mkpath(path); 0704 0705 QJsonObject docObj; 0706 foreach (auto source, dataSources) { 0707 if (!isValidSource(source) || !source->isActive() || telemetryMode < source->telemetryMode()) 0708 continue; 0709 QJsonObject obj; 0710 const auto data = source->data(); 0711 if (data.canConvert<QVariantMap>()) 0712 obj.insert(QLatin1String("data"), QJsonObject::fromVariantMap(data.toMap())); 0713 else if (data.canConvert<QVariantList>()) 0714 obj.insert(QLatin1String("data"), QJsonArray::fromVariantList(data.value<QVariantList>())); 0715 if (obj.isEmpty()) 0716 continue; 0717 obj.insert(QLatin1String("telemetryMode"), QString::fromLatin1(telemetryModeEnum().valueToKey(source->telemetryMode()))); 0718 obj.insert(QLatin1String("description"), source->description()); 0719 docObj.insert(source->id(), obj); 0720 } 0721 0722 QFile file(path + QLatin1Char('/') + dt.toString(QStringLiteral("yyyyMMdd-hhmmss")) + QStringLiteral(".log")); 0723 if (!file.open(QFile::WriteOnly)) { 0724 qCWarning(Log) << "Unable to open audit log file:" << file.fileName() << file.errorString(); 0725 return; 0726 } 0727 0728 QJsonDocument doc(docObj); 0729 file.write(doc.toJson()); 0730 0731 qCDebug(Log) << "Audit log written:" << file.fileName(); 0732 } 0733 0734 QString Provider::describeDataSources() const 0735 { 0736 QString ret; 0737 0738 const auto& mo = staticMetaObject; 0739 const int modeEnumIdx = mo.indexOfEnumerator("TelemetryMode"); 0740 Q_ASSERT(modeEnumIdx >= 0); 0741 0742 const auto modeEnum = mo.enumerator(modeEnumIdx); 0743 for (auto source : qAsConst(d->dataSources)) { 0744 ret += QString::fromUtf8(modeEnum.valueToKey(source->telemetryMode())) + QStringLiteral(": ") + source->name() + QLatin1Char('\n'); 0745 } 0746 return ret; 0747 } 0748 0749 #include "moc_provider.cpp"