diff --git a/autotests/surveyprovidertest.cpp b/autotests/surveyprovidertest.cpp index 116e1fd..f995a40 100644 --- a/autotests/surveyprovidertest.cpp +++ b/autotests/surveyprovidertest.cpp @@ -1,104 +1,120 @@ /* Copyright (C) 2017 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library 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 Library 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 #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #include #endif using namespace KUserFeedback; class SurveyProviderTest : public QObject { Q_OBJECT private slots: void initTestCase() { qRegisterMetaType(); QCoreApplication::setOrganizationName(QStringLiteral("KDE")); QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org")); QCoreApplication::setApplicationName(QStringLiteral("surveyprovidertest")); #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) QStandardPaths::setTestModeEnabled(true); #endif } void testSurveySelect() { { - QSettings s(QCoreApplication::organizationName(), QStringLiteral("UserFeedback.org.kde.providertest"));; + QSettings s(QCoreApplication::organizationName(), QStringLiteral("UserFeedback.org.kde.surveyprovidertest"));; s.beginGroup(QLatin1String("UserFeedback")); s.remove(QLatin1String("LastSurvey")); s.remove(QLatin1String("CompletedSurveys")); } + { + QSettings s(QCoreApplication::organizationName(), QStringLiteral("UserFeedback"));; + s.beginGroup(QLatin1String("UserFeedback")); + s.remove(QLatin1String("LastSurvey")); + } SurveyInfo s1; s1.setUuid(QUuid::createUuid()); s1.setUrl(QUrl(QStringLiteral("https://www.kde.org"))); QVERIFY(s1.isValid()); Provider p; - p.setSurveyInterval(0); + p.setSurveyInterval(90); QSignalSpy spy(&p, SIGNAL(surveyAvailable(KUserFeedback::SurveyInfo))); QVERIFY(spy.isValid()); bool rv = false; QMetaObject::invokeMethod(&p, "selectSurvey", Q_RETURN_ARG(bool, rv), Q_ARG(KUserFeedback::SurveyInfo, s1)); QVERIFY(rv); QCOMPARE(spy.size(), 1); // again, not completed yet QMetaObject::invokeMethod(&p, "selectSurvey", Q_RETURN_ARG(bool, rv), Q_ARG(KUserFeedback::SurveyInfo, s1)); QVERIFY(rv); QCOMPARE(spy.size(), 2); spy.clear(); // survey completed, should no longer be accepted p.surveyCompleted(s1); QMetaObject::invokeMethod(&p, "selectSurvey", Q_RETURN_ARG(bool, rv), Q_ARG(KUserFeedback::SurveyInfo, s1)); QVERIFY(!rv); QVERIFY(spy.isEmpty()); SurveyInfo s2; s2.setUuid(QUuid::createUuid()); s2.setUrl(QUrl(QStringLiteral("https://www.kde.org/survey2"))); QVERIFY(s2.isValid()); // next survey, but interval hasn't passed yet p.setSurveyInterval(90); QMetaObject::invokeMethod(&p, "selectSurvey", Q_RETURN_ARG(bool, rv), Q_ARG(KUserFeedback::SurveyInfo, s2)); QVERIFY(!rv); QVERIFY(spy.isEmpty()); // survey interval passed p.setSurveyInterval(0); QMetaObject::invokeMethod(&p, "selectSurvey", Q_RETURN_ARG(bool, rv), Q_ARG(KUserFeedback::SurveyInfo, s2)); QVERIFY(rv); + + // global survey coordination prevents next survey + p.setSurveyInterval(90); + { + QSettings s(QCoreApplication::organizationName(), QStringLiteral("UserFeedback.org.kde.surveyprovidertest"));; + s.beginGroup(QLatin1String("UserFeedback")); + s.remove(QLatin1String("LastSurvey")); + s.remove(QLatin1String("CompletedSurveys")); + } + QMetaObject::invokeMethod(&p, "selectSurvey", Q_RETURN_ARG(bool, rv), Q_ARG(KUserFeedback::SurveyInfo, s1)); + QVERIFY(!rv); } }; QTEST_MAIN(SurveyProviderTest) #include "surveyprovidertest.moc" diff --git a/src/provider/core/provider.cpp b/src/provider/core/provider.cpp index 07f1f3d..183dec0 100644 --- a/src/provider/core/provider.cpp +++ b/src/provider/core/provider.cpp @@ -1,653 +1,678 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library 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 Library 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 #include "logging_p.h" #include "provider.h" #include "provider_p.h" #include "abstractdatasource.h" #include "startcountsource.h" #include "surveyinfo.h" #include "usagetimesource.h" #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #include #include #include #endif #include #include #include #include #include #include #include #include #include namespace KUserFeedback { #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) Q_LOGGING_CATEGORY(Log, "org.kde.UserFeedback", QtInfoMsg) #elif QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) Q_LOGGING_CATEGORY(Log, "org.kde.UserFeedback") #endif } using namespace KUserFeedback; ProviderPrivate::ProviderPrivate(Provider *qq) : q(qq) , networkAccessManager(nullptr) , redirectCount(0) , submissionInterval(-1) , telemetryMode(Provider::NoTelemetry) , surveyInterval(-1) , startCount(0) , usageTime(0) , encouragementStarts(-1) , encouragementTime(-1) , encouragementDelay(300) , encouragementInterval(-1) { submissionTimer.setSingleShot(true); QObject::connect(&submissionTimer, SIGNAL(timeout()), q, SLOT(submit())); startTime.start(); encouragementTimer.setSingleShot(true); QObject::connect(&encouragementTimer, SIGNAL(timeout()), q, SLOT(emitShowEncouragementMessage())); } ProviderPrivate::~ProviderPrivate() { qDeleteAll(dataSources); } int ProviderPrivate::currentApplicationTime() const { return usageTime + (startTime.elapsed() / 1000); } static QMetaEnum telemetryModeEnum() { const auto idx = Provider::staticMetaObject.indexOfEnumerator("TelemetryMode"); Q_ASSERT(idx >= 0); return Provider::staticMetaObject.enumerator(idx); } std::unique_ptr ProviderPrivate::makeSettings() const { // attempt to put our settings next to the application ones, // so replicate how QSettings handles this auto org = #ifdef Q_OS_MAC QCoreApplication::organizationDomain().isEmpty() ? QCoreApplication::organizationName() : QCoreApplication::organizationDomain(); #else QCoreApplication::organizationName().isEmpty() ? QCoreApplication::organizationDomain() : QCoreApplication::organizationName(); #endif if (org.isEmpty()) org = QLatin1String("Unknown Organization"); std::unique_ptr s(new QSettings(org, QStringLiteral("UserFeedback.") + productId)); return s; } +std::unique_ptr ProviderPrivate::makeGlobalSettings() const +{ + const auto org = +#ifdef Q_OS_MAC + QStringLiteral("kde.org"); +#else + QStringLiteral("KDE"); +#endif + std::unique_ptr s(new QSettings(org, QStringLiteral("UserFeedback"))); + return s; +} + void ProviderPrivate::load() { auto s = makeSettings(); s->beginGroup(QStringLiteral("UserFeedback")); lastSubmitTime = s->value(QStringLiteral("LastSubmission")).toDateTime(); const auto modeStr = s->value(QStringLiteral("StatisticsCollectionMode")).toByteArray(); telemetryMode = static_cast(std::max(telemetryModeEnum().keyToValue(modeStr.constData()), 0)); surveyInterval = s->value(QStringLiteral("SurveyInterval"), -1).toInt(); lastSurveyTime = s->value(QStringLiteral("LastSurvey")).toDateTime(); completedSurveys = s->value(QStringLiteral("CompletedSurveys"), QStringList()).toStringList(); startCount = std::max(s->value(QStringLiteral("ApplicationStartCount"), 0).toInt(), 0); usageTime = std::max(s->value(QStringLiteral("ApplicationTime"), 0).toInt(), 0); lastEncouragementTime = s->value(QStringLiteral("LastEncouragement")).toDateTime(); s->endGroup(); foreach (auto source, dataSources) { s->beginGroup(QStringLiteral("Source-") + source->name()); source->load(s.get()); s->endGroup(); } + + auto g = makeGlobalSettings(); + g->beginGroup(QStringLiteral("UserFeedback")); + lastSurveyTime = std::max(g->value(QStringLiteral("LastSurvey")).toDateTime(), lastSurveyTime); } void ProviderPrivate::store() { auto s = makeSettings(); s->beginGroup(QStringLiteral("UserFeedback")); // another process might have changed this, so read the base value first before writing usageTime = std::max(s->value(QStringLiteral("ApplicationTime"), 0).toInt(), usageTime); s->setValue(QStringLiteral("ApplicationTime"), currentApplicationTime()); usageTime = currentApplicationTime(); startTime.restart(); s->endGroup(); foreach (auto source, dataSources) { s->beginGroup(QStringLiteral("Source-") + source->name()); source->store(s.get()); s->endGroup(); } } void ProviderPrivate::storeOne(const QString &key, const QVariant &value) { auto s = makeSettings(); s->beginGroup(QStringLiteral("UserFeedback")); s->setValue(key, value); } +void ProviderPrivate::storeOneGlobal(const QString &key, const QVariant &value) +{ + auto s = makeGlobalSettings(); + s->beginGroup(QStringLiteral("UserFeedback")); + s->setValue(key, value); +} + void ProviderPrivate::aboutToQuit() { store(); } bool ProviderPrivate::isValidSource(AbstractDataSource *source) const { if (source->name().isEmpty()) { qCWarning(Log) << "Skipping data source with empty name!"; return false; } if (source->telemetryMode() == Provider::NoTelemetry) { qCWarning(Log) << "Source" << source->name() << "attempts to report data unconditionally, ignoring!"; return false; } if (source->description().isEmpty()) { qCWarning(Log) << "Source" << source->name() << "has no description, ignoring!"; return false; } Q_ASSERT(!source->name().isEmpty()); Q_ASSERT(source->telemetryMode() != Provider::NoTelemetry); Q_ASSERT(!source->description().isEmpty()); return true; } #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) QByteArray variantMapToJson(const QVariantMap &m) { QByteArray b = "{"; for (auto it = m.begin(); it != m.end(); ++it) { b += " \"" + it.key().toUtf8() + "\": "; switch (it.value().type()) { case QVariant::String: b += '"' + it.value().toString().toUtf8() + '"'; break; case QVariant::Int: b += QByteArray::number(it.value().toInt()); break; case QVariant::Double: b += QByteArray::number(it.value().toDouble()); break; case QVariant::Map: b += variantMapToJson(it.value().toMap()); break; default: break; } if (std::distance(it, m.end()) != 1) b += ",\n"; } b += " }"; return b; } #endif QByteArray ProviderPrivate::jsonData(Provider::TelemetryMode mode) const { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) QJsonObject obj; if (mode != Provider::NoTelemetry) { foreach (auto source, dataSources) { if (!isValidSource(source)) continue; if (mode < source->telemetryMode()) continue; const auto data = source->data(); if (data.canConvert()) obj.insert(source->name(), QJsonObject::fromVariantMap(data.toMap())); else if (data.canConvert()) obj.insert(source->name(), QJsonArray::fromVariantList(data.value())); else qCWarning(Log) << "wrong type for" << source->name() << data; } } QJsonDocument doc(obj); return doc.toJson(); #else QByteArray b = "{"; if (mode != Provider::NoTelemetry) { for (auto it = dataSources.begin(); it != dataSources.end(); ++it) { if (!isValidSource(*it)) continue; if (mode < (*it)->telemetryMode()) continue; const auto data = (*it)->data(); if (data.canConvert()) { const auto l = data.value(); b += " \"" + (*it)->name().toUtf8() + "\": [ "; for (auto it2 = l.begin(); it2 != l.end(); ++it2) { b += variantMapToJson((*it2).toMap()); if (std::distance(it2, l.end()) != 1) b += ", "; } b += " ]"; } else { b += " \"" + (*it)->name().toUtf8() + "\": " + variantMapToJson(data.toMap()); } if (std::distance(it, dataSources.end()) != 1) b += ",\n"; } } b += '}'; return b; #endif } void ProviderPrivate::scheduleNextSubmission() { submissionTimer.stop(); if (submissionInterval <= 0 || (telemetryMode == Provider::NoTelemetry && surveyInterval < 0)) return; Q_ASSERT(submissionInterval > 0); const auto nextSubmission = lastSubmitTime.addDays(submissionInterval); const auto now = QDateTime::currentDateTime(); submissionTimer.start(std::max(0ll, now.msecsTo(nextSubmission))); } void ProviderPrivate::submitFinished() { auto reply = qobject_cast(q->sender()); Q_ASSERT(reply); if (reply->error() != QNetworkReply::NoError) { qCWarning(Log) << "failed to submit user feedback:" << reply->errorString() << reply->readAll(); return; } const auto redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (redirectTarget.isValid()) { if (++redirectCount >= 20) { qCWarning(Log) << "Redirect loop on" << reply->url().resolved(redirectTarget).toString(); return; } submit(reply->url().resolved(redirectTarget)); return; } lastSubmitTime = QDateTime::currentDateTime(); auto s = makeSettings(); s->beginGroup(QStringLiteral("UserFeedback")); s->setValue(QStringLiteral("LastSubmission"), lastSubmitTime); s->endGroup(); // reset source counters foreach (auto source, dataSources) { s->beginGroup(QStringLiteral("Source-") + source->name()); source->reset(s.get()); s->endGroup(); } #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) const auto obj = QJsonDocument::fromJson(reply->readAll()).object(); const auto it = obj.find(QLatin1String("surveys")); if (it != obj.end() && surveyInterval >= 0) { const auto a = it.value().toArray(); qCDebug(Log) << "received" << a.size() << "surveys"; foreach(const auto &s, a) { const auto survey = SurveyInfo::fromJson(s.toObject()); if (selectSurvey(survey)) break; } } #endif scheduleNextSubmission(); } QVariant ProviderPrivate::sourceData(const QString& sourceName) const { foreach (auto src, dataSources) { if (src->name() == sourceName) return src->data(); } return QVariant(); } bool ProviderPrivate::selectSurvey(const SurveyInfo &survey) const { qCDebug(Log) << "got survey:" << survey.url() << survey.target(); if (!survey.isValid() || completedSurveys.contains(survey.uuid().toString())) return false; if (lastSurveyTime.addDays(surveyInterval) > QDateTime::currentDateTime()) return false; if (!survey.target().isEmpty()) { SurveyTargetExpressionParser parser; if (!parser.parse(survey.target())) { qCDebug(Log) << "failed to parse target expression"; return false; } SurveyTargetExpressionEvaluator eval; eval.setDataProvider(this); if (!eval.evaluate(parser.expression())) return false; } qCDebug(Log) << "picked survey:" << survey.url(); emit q->surveyAvailable(survey); return true; } Provider::TelemetryMode ProviderPrivate::highestTelemetryMode() const { auto mode = Provider::NoTelemetry; foreach (auto src, dataSources) mode = std::max(mode, src->telemetryMode()); return mode; } void ProviderPrivate::scheduleEncouragement() { encouragementTimer.stop(); // already done, not repetition if (lastEncouragementTime.isValid() && encouragementInterval <= 0) return; if (encouragementStarts < 0 && encouragementTime < 0) // encouragement disabled return; if (encouragementStarts > startCount) // we need more starts return; if (telemetryMode >= highestTelemetryMode() && surveyInterval == 0) // already everything enabled return; // no repetition if some feedback is enabled if (lastEncouragementTime.isValid() && (telemetryMode > Provider::NoTelemetry || surveyInterval >= 0)) return; Q_ASSERT(encouragementDelay >= 0); int timeToEncouragement = encouragementDelay; if (encouragementTime > 0) timeToEncouragement = std::max(timeToEncouragement, encouragementTime - currentApplicationTime()); if (lastEncouragementTime.isValid()) { Q_ASSERT(encouragementInterval > 0); const auto targetTime = lastEncouragementTime.addDays(encouragementDelay); timeToEncouragement = std::max(timeToEncouragement, (int)QDateTime::currentDateTime().secsTo(targetTime)); } encouragementTimer.start(timeToEncouragement * 1000); } void ProviderPrivate::emitShowEncouragementMessage() { lastEncouragementTime = QDateTime::currentDateTime(); // TODO make this explicit, in case the host application decides to delay? storeOne(QStringLiteral("LastEncouragement"), lastEncouragementTime); emit q->showEncouragementMessage(); } Provider::Provider(QObject *parent) : QObject(parent), d(new ProviderPrivate(this)) { qCDebug(Log); connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit())); auto domain = QCoreApplication::organizationDomain().split(QLatin1Char('.')); std::reverse(domain.begin(), domain.end()); auto id = domain.join(QLatin1String(".")); if (!id.isEmpty()) id += QLatin1Char('.'); id += QCoreApplication::applicationName(); setProductIdentifier(id); } Provider::~Provider() { delete d; } QString Provider::productIdentifier() const { return d->productId; } void Provider::setProductIdentifier(const QString &productId) { Q_ASSERT(!productId.isEmpty()); if (productId == d->productId) return; d->productId = productId; d->load(); d->startCount++; d->storeOne(QStringLiteral("ApplicationStartCount"), d->startCount); emit providerSettingsChanged(); d->scheduleEncouragement(); d->scheduleNextSubmission(); } QUrl Provider::feedbackServer() const { return d->serverUrl; } void Provider::setFeedbackServer(const QUrl &url) { if (d->serverUrl == url) return; d->serverUrl = url; emit providerSettingsChanged(); } int Provider::submissionInterval() const { return d->submissionInterval; } void Provider::setSubmissionInterval(int days) { if (d->submissionInterval == days) return; d->submissionInterval = days; emit providerSettingsChanged(); d->scheduleNextSubmission(); } Provider::TelemetryMode Provider::telemetryMode() const { return d->telemetryMode; } void Provider::setTelemetryMode(TelemetryMode mode) { if (d->telemetryMode == mode) return; d->telemetryMode = mode; d->storeOne(QStringLiteral("StatisticsCollectionMode"), QString::fromLatin1(telemetryModeEnum().valueToKey(d->telemetryMode))); d->scheduleNextSubmission(); d->scheduleEncouragement(); emit telemetryModeChanged(); } void Provider::addDataSource(AbstractDataSource *source) { // special cases for sources where we track the data here, as it's needed even if we don't report it if (auto countSrc = dynamic_cast(source)) countSrc->setProvider(d); if (auto timeSrc = dynamic_cast(source)) timeSrc->setProvider(d); d->dataSources.push_back(source); auto s = d->makeSettings(); s->beginGroup(QStringLiteral("Source-") + source->name()); source->load(s.get()); } QVector Provider::dataSources() const { return d->dataSources; } int Provider::surveyInterval() const { return d->surveyInterval; } void Provider::setSurveyInterval(int days) { if (d->surveyInterval == days) return; d->surveyInterval = days; d->storeOne(QStringLiteral("SurveyInterval"), d->surveyInterval); d->scheduleNextSubmission(); d->scheduleEncouragement(); emit surveyIntervalChanged(); } int Provider::applicationStartsUntilEncouragement() const { return d->encouragementStarts; } void Provider::setApplicationStartsUntilEncouragement(int starts) { if (d->encouragementStarts == starts) return; d->encouragementStarts = starts; emit providerSettingsChanged(); d->scheduleEncouragement(); } int Provider::applicationUsageTimeUntilEncouragement() const { return d->encouragementTime; } void Provider::setApplicationUsageTimeUntilEncouragement(int secs) { if (d->encouragementTime == secs) return; d->encouragementTime = secs; emit providerSettingsChanged(); d->scheduleEncouragement(); } int Provider::encouragementDelay() const { return d->encouragementDelay; } void Provider::setEncouragementDelay(int secs) { if (d->encouragementDelay == secs) return; d->encouragementDelay = std::max(0, secs); emit providerSettingsChanged(); d->scheduleEncouragement(); } int Provider::encouragementInterval() const { return d->encouragementInterval; } void Provider::setEncouragementInterval(int days) { if (d->encouragementInterval == days) return; d->encouragementInterval = days; emit providerSettingsChanged(); d->scheduleEncouragement(); } void Provider::surveyCompleted(const SurveyInfo &info) { d->completedSurveys.push_back(info.uuid().toString()); d->lastSurveyTime = QDateTime::currentDateTime(); auto s = d->makeSettings(); s->beginGroup(QStringLiteral("UserFeedback")); s->setValue(QStringLiteral("LastSurvey"), d->lastSurveyTime); s->setValue(QStringLiteral("CompletedSurveys"), d->completedSurveys); + + d->storeOneGlobal(QStringLiteral("LastSurvey"), d->lastSurveyTime); } void Provider::submit() { if (d->productId.isEmpty()) { qCWarning(Log) << "No productId specified!"; return; } if (!d->serverUrl.isValid()) { qCWarning(Log) << "No feedback server URL specified!"; return; } if (!d->networkAccessManager) d->networkAccessManager = new QNetworkAccessManager(this); auto url = d->serverUrl; auto path = d->serverUrl.path(); if (!path.endsWith(QLatin1Char('/'))) path += QLatin1Char('/'); path += QStringLiteral("receiver/submit/") + d->productId; url.setPath(path); d->submit(url); } void ProviderPrivate::submit(const QUrl &url) { QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) request.setHeader(QNetworkRequest::UserAgentHeader, QString(QStringLiteral("KUserFeedback/") + QStringLiteral(KUSERFEEDBACK_VERSION_STRING))); #endif auto reply = networkAccessManager->post(request, jsonData(telemetryMode)); QObject::connect(reply, SIGNAL(finished()), q, SLOT(submitFinished())); } #include "moc_provider.cpp" diff --git a/src/provider/core/provider_p.h b/src/provider/core/provider_p.h index b5846c1..6c9f5fa 100644 --- a/src/provider/core/provider_p.h +++ b/src/provider/core/provider_p.h @@ -1,99 +1,101 @@ /* Copyright (C) 2017 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library 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 Library 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 . */ #ifndef KUSERFEEDBACK_PROVIDER_P_H #define KUSERFEEDBACK_PROVIDER_P_H #include "provider.h" #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE class QNetworkAccessManager; class QSettings; QT_END_NAMESPACE namespace KUserFeedback { class ProviderPrivate : public SurveyTargetExpressionDataProvider { public: explicit ProviderPrivate(Provider *qq); ~ProviderPrivate(); int currentApplicationTime() const; std::unique_ptr makeSettings() const; + std::unique_ptr makeGlobalSettings() const; void load(); void store(); void storeOne(const QString &key, const QVariant &value); + void storeOneGlobal(const QString &key, const QVariant &value); void aboutToQuit(); bool isValidSource(AbstractDataSource *source) const; QByteArray jsonData(Provider::TelemetryMode mode) const; void scheduleNextSubmission(); void submit(const QUrl &url); void submitFinished(); bool selectSurvey(const SurveyInfo &survey) const; Provider::TelemetryMode highestTelemetryMode() const; void scheduleEncouragement(); void emitShowEncouragementMessage(); QVariant sourceData(const QString &sourceName) const override; Provider *q; QString productId; QTimer submissionTimer; QNetworkAccessManager *networkAccessManager; QUrl serverUrl; QDateTime lastSubmitTime; int redirectCount; int submissionInterval; Provider::TelemetryMode telemetryMode; int surveyInterval; QDateTime lastSurveyTime; QStringList completedSurveys; QTime startTime; int startCount; int usageTime; QTimer encouragementTimer; QDateTime lastEncouragementTime; int encouragementStarts; int encouragementTime; int encouragementDelay; int encouragementInterval; QVector dataSources; }; } #endif