KUserFeedback

provider.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: MIT
5*/
6
7#include <kuserfeedback_version.h>
8
9#include "logging_p.h"
10#include "provider.h"
11#include "provider_p.h"
12#include "abstractdatasource.h"
13#include "startcountsource.h"
14#include "surveyinfo.h"
15#include "usagetimesource.h"
16
17#include <common/surveytargetexpressionparser.h>
18#include <common/surveytargetexpressionevaluator.h>
19
20#include <QCoreApplication>
21#include <QDebug>
22#include <QDir>
23#include <QJsonArray>
24#include <QJsonDocument>
25#include <QJsonObject>
26#include <QStandardPaths>
27#include <QMetaEnum>
28#include <QNetworkAccessManager>
29#include <QNetworkReply>
30#include <QNetworkRequest>
31#include <QSettings>
32#include <QUuid>
33
34#include <algorithm>
35#include <numeric>
36
37using namespace KUserFeedback;
38
39ProviderPrivate::ProviderPrivate(Provider *qq)
40 : q(qq)
41 , networkAccessManager(nullptr)
42 , redirectCount(0)
43 , submissionInterval(-1)
44 , telemetryMode(Provider::NoTelemetry)
45 , surveyInterval(-1)
46 , startCount(0)
47 , usageTime(0)
48 , encouragementStarts(-1)
49 , encouragementTime(-1)
50 , encouragementDelay(300)
51 , encouragementInterval(-1)
52 , backoffIntervalMinutes(-1)
53{
54 submissionTimer.setSingleShot(true);
55 QObject::connect(&submissionTimer, &QTimer::timeout, q, &Provider::submit);
56
57 startTime.start();
58
59 encouragementTimer.setSingleShot(true);
60 QObject::connect(&encouragementTimer, &QTimer::timeout, q, [this]() { emitShowEncouragementMessage(); });
61}
62
63ProviderPrivate::~ProviderPrivate()
64{
65 qDeleteAll(dataSources);
66}
67
68int ProviderPrivate::currentApplicationTime() const
69{
70 return usageTime + (startTime.elapsed() / 1000);
71}
72
73static QMetaEnum telemetryModeEnum()
74{
75 const auto idx = Provider::staticMetaObject.indexOfEnumerator("TelemetryMode");
76 Q_ASSERT(idx >= 0);
77 return Provider::staticMetaObject.enumerator(idx);
78}
79
80std::unique_ptr<QSettings> ProviderPrivate::makeSettings() const
81{
82 // attempt to put our settings next to the application ones,
83 // so replicate how QSettings handles this
84 auto org =
85#ifdef Q_OS_MAC
87#else
89#endif
90 if (org.isEmpty())
91 org = QLatin1String("Unknown Organization");
92
93 std::unique_ptr<QSettings> s(new QSettings(org, QStringLiteral("UserFeedback.") + productId));
94 return s;
95}
96
97std::unique_ptr<QSettings> ProviderPrivate::makeGlobalSettings() const
98{
99 const auto org =
100#ifdef Q_OS_MAC
101 QStringLiteral("kde.org");
102#else
103 QStringLiteral("KDE");
104#endif
105 std::unique_ptr<QSettings> s(new QSettings(org, QStringLiteral("UserFeedback")));
106 return s;
107}
108
109void ProviderPrivate::load()
110{
111 auto s = makeSettings();
112 s->beginGroup(QStringLiteral("UserFeedback"));
113 lastSubmitTime = s->value(QStringLiteral("LastSubmission")).toDateTime();
114
115 const auto modeStr = s->value(QStringLiteral("StatisticsCollectionMode")).toByteArray();
116 telemetryMode = static_cast<Provider::TelemetryMode>(std::max(telemetryModeEnum().keyToValue(modeStr.constData()), 0));
117
118 surveyInterval = s->value(QStringLiteral("SurveyInterval"), -1).toInt();
119 lastSurveyTime = s->value(QStringLiteral("LastSurvey")).toDateTime();
120 completedSurveys = s->value(QStringLiteral("CompletedSurveys"), QStringList()).toStringList();
121
122 startCount = std::max(s->value(QStringLiteral("ApplicationStartCount"), 0).toInt(), 0);
123 usageTime = std::max(s->value(QStringLiteral("ApplicationTime"), 0).toInt(), 0);
124
125 lastEncouragementTime = s->value(QStringLiteral("LastEncouragement")).toDateTime();
126
127 s->endGroup();
128
129 // ensure consistent times if settings is corrupt, to avoid overflows later in the code
130 const auto now = QDateTime::currentDateTime();
131 if (now < lastSubmitTime) {
132 lastSubmitTime = now;
133 }
134 if (now < lastSurveyTime) {
135 lastSurveyTime = now;
136 }
137 if (now < lastEncouragementTime) {
138 lastEncouragementTime = now;
139 }
140
141 foreach (auto source, dataSources) {
142 s->beginGroup(QStringLiteral("Source-") + source->id());
143 source->load(s.get());
144 s->endGroup();
145 }
146
147 auto g = makeGlobalSettings();
148 g->beginGroup(QStringLiteral("UserFeedback"));
149 lastSurveyTime = std::max(g->value(QStringLiteral("LastSurvey")).toDateTime(), lastSurveyTime);
150 lastEncouragementTime = std::max(g->value(QStringLiteral("LastEncouragement")).toDateTime(), lastEncouragementTime);
151}
152
153void ProviderPrivate::store()
154{
155 auto s = makeSettings();
156 s->beginGroup(QStringLiteral("UserFeedback"));
157
158 // another process might have changed this, so read the base value first before writing
159 usageTime = std::max(s->value(QStringLiteral("ApplicationTime"), 0).toInt(), usageTime);
160 s->setValue(QStringLiteral("ApplicationTime"), currentApplicationTime());
161 usageTime = currentApplicationTime();
162 startTime.restart();
163
164 s->endGroup();
165
166 foreach (auto source, dataSources) {
167 s->beginGroup(QStringLiteral("Source-") + source->id());
168 source->store(s.get());
169 s->endGroup();
170 }
171}
172
173void ProviderPrivate::storeOne(const QString &key, const QVariant &value)
174{
175 auto s = makeSettings();
176 s->beginGroup(QStringLiteral("UserFeedback"));
177 s->setValue(key, value);
178}
179
180void ProviderPrivate::storeOneGlobal(const QString &key, const QVariant &value)
181{
182 auto s = makeGlobalSettings();
183 s->beginGroup(QStringLiteral("UserFeedback"));
184 s->setValue(key, value);
185}
186
187void ProviderPrivate::aboutToQuit()
188{
189 store();
190}
191
192bool ProviderPrivate::isValidSource(AbstractDataSource *source) const
193{
194 if (source->id().isEmpty()) {
195 qCWarning(Log) << "Skipping data source with empty name!";
196 return false;
197 }
198 if (source->telemetryMode() == Provider::NoTelemetry) {
199 qCWarning(Log) << "Source" << source->id() << "attempts to report data unconditionally, ignoring!";
200 return false;
201 }
202 if (source->description().isEmpty()) {
203 qCWarning(Log) << "Source" << source->id() << "has no description, ignoring!";
204 return false;
205 }
206
207 Q_ASSERT(!source->id().isEmpty());
208 Q_ASSERT(source->telemetryMode() != Provider::NoTelemetry);
209 Q_ASSERT(!source->description().isEmpty());
210 return true;
211}
212
213QByteArray ProviderPrivate::jsonData(Provider::TelemetryMode mode) const
214{
215 QJsonObject obj;
216 if (mode != Provider::NoTelemetry) {
217 foreach (auto source, dataSources) {
218 if (!isValidSource(source) || !source->isActive())
219 continue;
220 if (mode < source->telemetryMode())
221 continue;
222 const auto data = source->data();
223 if (data.canConvert<QVariantMap>())
224 obj.insert(source->id(), QJsonObject::fromVariantMap(data.toMap()));
225 else if (data.canConvert<QVariantList>())
226 obj.insert(source->id(), QJsonArray::fromVariantList(data.value<QVariantList>()));
227 else
228 qCWarning(Log) << "wrong type for" << source->id() << data;
229 }
230 }
231
232 QJsonDocument doc(obj);
233 return doc.toJson();
234}
235
236void ProviderPrivate::scheduleNextSubmission(qint64 minTime)
237{
238 submissionTimer.stop();
239 if (!q->isEnabled())
240 return;
241 if (submissionInterval <= 0 || (telemetryMode == Provider::NoTelemetry && surveyInterval < 0))
242 return;
243
244 if (minTime == 0) {
245 // If this is a regularly scheduled submission reset the backoff
246 backoffIntervalMinutes = -1;
247 }
248
249 Q_ASSERT(submissionInterval > 0);
250
251 const auto nextSubmission = lastSubmitTime.addDays(submissionInterval);
252 const auto now = QDateTime::currentDateTime();
253 submissionTimer.start(std::max(minTime, now.msecsTo(nextSubmission)));
254}
255
256void ProviderPrivate::submitFinished(QNetworkReply *reply)
257{
258 reply->deleteLater();
259
260 if (reply->error() != QNetworkReply::NoError) {
261 if (backoffIntervalMinutes == -1) {
262 backoffIntervalMinutes = 2;
263 } else {
264 backoffIntervalMinutes = backoffIntervalMinutes * 2;
265 }
266 qCWarning(Log) << "failed to submit user feedback:" << reply->errorString() << reply->readAll() << ". Calling scheduleNextSubmission with minTime" << backoffIntervalMinutes << "minutes";
267 scheduleNextSubmission(backoffIntervalMinutes * 60000ll);
268 return;
269 }
270
271 const auto redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
272 if (redirectTarget.isValid()) {
273 if (++redirectCount >= 20) {
274 qCWarning(Log) << "Redirect loop on" << reply->url().resolved(redirectTarget).toString();
275 return;
276 }
277 submit(reply->url().resolved(redirectTarget));
278 return;
279 }
280
281 lastSubmitTime = QDateTime::currentDateTime();
282
283 auto s = makeSettings();
284 s->beginGroup(QStringLiteral("UserFeedback"));
285 s->setValue(QStringLiteral("LastSubmission"), lastSubmitTime);
286 s->endGroup();
287
288 writeAuditLog(lastSubmitTime);
289
290 // reset source counters
291 foreach (auto source, dataSources) {
292 s->beginGroup(QStringLiteral("Source-") + source->id());
293 source->reset(s.get());
294 s->endGroup();
295 }
296
297 const auto obj = QJsonDocument::fromJson(reply->readAll()).object();
298 const auto it = obj.find(QLatin1String("surveys"));
299 if (it != obj.end() && surveyInterval >= 0) {
300 const auto a = it.value().toArray();
301 qCDebug(Log) << "received" << a.size() << "surveys";
302 foreach(const auto &s, a) {
303 const auto survey = SurveyInfo::fromJson(s.toObject());
304 if (selectSurvey(survey))
305 break;
306 }
307 }
308
309 scheduleNextSubmission();
310}
311
312QVariant ProviderPrivate::sourceData(const QString& sourceId) const
313{
314 foreach (auto src, dataSources) {
315 if (src->id() == sourceId)
316 return src->data();
317 }
318 return QVariant();
319}
320
321bool ProviderPrivate::selectSurvey(const SurveyInfo &survey) const
322{
323 qCDebug(Log) << "got survey:" << survey.url() << survey.target();
324 if (!q->isEnabled() || !survey.isValid() || completedSurveys.contains(survey.uuid().toString()))
325 return false;
326
327 if (surveyInterval != 0 && lastSurveyTime.addDays(surveyInterval) > QDateTime::currentDateTime())
328 return false;
329
330 if (!survey.target().isEmpty()) {
331 SurveyTargetExpressionParser parser;
332 if (!parser.parse(survey.target())) {
333 qCDebug(Log) << "failed to parse target expression";
334 return false;
335 }
336
337 SurveyTargetExpressionEvaluator eval;
338 eval.setDataProvider(this);
339 if (!eval.evaluate(parser.expression()))
340 return false;
341 }
342
343 qCDebug(Log) << "picked survey:" << survey.url();
344 Q_EMIT q->surveyAvailable(survey);
345 return true;
346}
347
348Provider::TelemetryMode ProviderPrivate::highestTelemetryMode() const
349{
350 auto mode = Provider::NoTelemetry;
351 foreach (auto src, dataSources)
352 mode = std::max(mode, src->telemetryMode());
353 return mode;
354}
355
356void ProviderPrivate::scheduleEncouragement()
357{
358 encouragementTimer.stop();
359 if (!q->isEnabled())
360 return;
361
362 // already done, not repetition
363 if (lastEncouragementTime.isValid() && encouragementInterval <= 0)
364 return;
365
366 if (encouragementStarts < 0 && encouragementTime < 0) // encouragement disabled
367 return;
368
369 if (encouragementStarts > startCount) // we need more starts
370 return;
371
372 if (telemetryMode >= highestTelemetryMode() && surveyInterval == 0) // already everything enabled
373 return;
374 // no repetition if some feedback is enabled
375 if (lastEncouragementTime.isValid() && (telemetryMode > Provider::NoTelemetry || surveyInterval >= 0))
376 return;
377
378 Q_ASSERT(encouragementDelay >= 0);
379 int timeToEncouragement = encouragementDelay;
380 if (encouragementTime > 0)
381 timeToEncouragement = std::max(timeToEncouragement, encouragementTime - currentApplicationTime());
382 if (lastEncouragementTime.isValid()) {
383 Q_ASSERT(encouragementInterval > 0);
384 const auto targetTime = lastEncouragementTime.addDays(encouragementInterval);
385 timeToEncouragement = std::max(timeToEncouragement, (int)QDateTime::currentDateTime().secsTo(targetTime));
386 }
387 encouragementTimer.start(timeToEncouragement * 1000);
388}
389
390void ProviderPrivate::emitShowEncouragementMessage()
391{
392 lastEncouragementTime = QDateTime::currentDateTime(); // TODO make this explicit, in case the host application decides to delay?
393 storeOne(QStringLiteral("LastEncouragement"), lastEncouragementTime);
394 storeOneGlobal(QStringLiteral("LastEncouragement"), lastEncouragementTime);
395 Q_EMIT q->showEncouragementMessage();
396}
397
398
400 QObject(parent),
401 d(new ProviderPrivate(this))
402{
403 qCDebug(Log);
404
405 connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() { d->aboutToQuit(); });
406
407 auto domain = QCoreApplication::organizationDomain().split(QLatin1Char('.'));
408 std::reverse(domain.begin(), domain.end());
409 auto id = domain.join(QLatin1String("."));
410 if (!id.isEmpty())
411 id += QLatin1Char('.');
414}
415
416Provider::~Provider()
417{
418 delete d;
419}
420
422{
423 auto s = d->makeGlobalSettings();
424 s->beginGroup(QStringLiteral("UserFeedback"));
425 return s->value(QStringLiteral("Enabled"), true).toBool();
426}
427
428void Provider::setEnabled(bool enabled)
429{
430 if (enabled == isEnabled())
431 return;
432 d->storeOneGlobal(QStringLiteral("Enabled"), enabled);
434}
435
441
443{
444 return d->productId;
445}
446
448{
449 Q_ASSERT(!productId.isEmpty());
450 if (productId == d->productId)
451 return;
452 d->productId = productId;
453
454 d->load();
455 d->startCount++;
456 d->storeOne(QStringLiteral("ApplicationStartCount"), d->startCount);
457
459
460 d->scheduleEncouragement();
461 d->scheduleNextSubmission();
462}
463
465{
466 return d->serverUrl;
467}
468
470{
471 if (d->serverUrl == url)
472 return;
473 d->serverUrl = url;
475}
476
478{
479 return d->submissionInterval;
480}
481
483{
484 if (d->submissionInterval == days)
485 return;
486 d->submissionInterval = days;
488 d->scheduleNextSubmission();
489}
490
492{
493 return d->telemetryMode;
494}
495
497{
498 if (d->telemetryMode == mode)
499 return;
500
501 d->telemetryMode = mode;
502 d->storeOne(QStringLiteral("StatisticsCollectionMode"), QString::fromLatin1(telemetryModeEnum().valueToKey(d->telemetryMode)));
503 d->scheduleNextSubmission();
504 d->scheduleEncouragement();
506}
507
509{
510 // special cases for sources where we track the data here, as it's needed even if we don't report it
511 if (auto countSrc = dynamic_cast<StartCountSource*>(source))
512 countSrc->setProvider(d);
513 if (auto timeSrc = dynamic_cast<UsageTimeSource*>(source))
514 timeSrc->setProvider(d);
515
516 d->dataSources.push_back(source);
517 d->dataSourcesById[source->id()] = source;
518
519 auto s = d->makeSettings();
520 s->beginGroup(QStringLiteral("Source-") + source->id());
521 source->load(s.get());
522
524}
525
527{
528 return d->dataSources;
529}
530
532{
533 auto it = d->dataSourcesById.find(id);
534 return it != std::end(d->dataSourcesById) ? *it : nullptr;
535}
536
538{
539 return d->surveyInterval;
540}
541
543{
544 if (d->surveyInterval == days)
545 return;
546
547 d->surveyInterval = days;
548 d->storeOne(QStringLiteral("SurveyInterval"), d->surveyInterval);
549
550 d->scheduleNextSubmission();
551 d->scheduleEncouragement();
553}
554
556{
557 return d->encouragementStarts;
558}
559
561{
562 if (d->encouragementStarts == starts)
563 return;
564 d->encouragementStarts = starts;
566 d->scheduleEncouragement();
567}
568
570{
571 return d->encouragementTime;
572}
573
575{
576 if (d->encouragementTime == secs)
577 return;
578 d->encouragementTime = secs;
580 d->scheduleEncouragement();
581}
582
584{
585 return d->encouragementDelay;
586}
587
589{
590 if (d->encouragementDelay == secs)
591 return;
592 d->encouragementDelay = std::max(0, secs);
594 d->scheduleEncouragement();
595}
596
598{
599 return d->encouragementInterval;
600}
601
603{
604 if (d->encouragementInterval == days)
605 return;
606 d->encouragementInterval = days;
608 d->scheduleEncouragement();
609}
610
612{
613 d->completedSurveys.push_back(info.uuid().toString());
614 d->lastSurveyTime = QDateTime::currentDateTime();
615
616 auto s = d->makeSettings();
617 s->beginGroup(QStringLiteral("UserFeedback"));
618 s->setValue(QStringLiteral("LastSurvey"), d->lastSurveyTime);
619 s->setValue(QStringLiteral("CompletedSurveys"), d->completedSurveys);
620
621 d->storeOneGlobal(QStringLiteral("LastSurvey"), d->lastSurveyTime);
622}
623
625{
626 d->load();
627}
628
630{
631 d->store();
632}
633
635{
636 if (!isEnabled()) {
637 qCWarning(Log) << "Global kill switch is enabled";
638 return;
639 }
640 if (d->productId.isEmpty()) {
641 qCWarning(Log) << "No productId specified!";
642 return;
643 }
644 if (!d->serverUrl.isValid()) {
645 qCWarning(Log) << "No feedback server URL specified!";
646 return;
647 }
648
649 if (!d->networkAccessManager)
650 d->networkAccessManager = new QNetworkAccessManager(this);
651
652 auto url = d->serverUrl;
653 auto path = d->serverUrl.path();
654 if (!path.endsWith(QLatin1Char('/')))
655 path += QLatin1Char('/');
656 path += QStringLiteral("receiver/submit/") + d->productId;
657 url.setPath(path);
658 d->submitProbe(url);
659}
660
661void ProviderPrivate::submit(const QUrl &url)
662{
663 QNetworkRequest request(url);
664 request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
665 request.setHeader(QNetworkRequest::UserAgentHeader, QString(QStringLiteral("KUserFeedback/") + QStringLiteral(KUSERFEEDBACK_VERSION_STRING)));
666 auto reply = networkAccessManager->post(request, jsonData(telemetryMode));
667 QObject::connect(reply, &QNetworkReply::finished, q, [this, reply]() { submitFinished(reply); });
668}
669
670void ProviderPrivate::submitProbe(const QUrl &url)
671{
672 QNetworkRequest request(url);
673 request.setHeader(QNetworkRequest::UserAgentHeader, QString(QStringLiteral("KUserFeedback/") + QStringLiteral(KUSERFEEDBACK_VERSION_STRING)));
674 auto reply = networkAccessManager->get(request);
675 QObject::connect(reply, &QNetworkReply::finished, q, [this, reply]() { submitProbeFinished(reply); });
676}
677
678void ProviderPrivate::submitProbeFinished(QNetworkReply *reply)
679{
680 reply->deleteLater();
681
682 if (reply->error() != QNetworkReply::NoError) {
683 qCWarning(Log) << "failed to probe user feedback submission interface:" << reply->errorString() << reply->readAll();
684 return;
685 }
686
687 const auto redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
688 if (redirectTarget.isValid()) {
689 if (++redirectCount >= 20) {
690 qCWarning(Log) << "Redirect loop on" << reply->url().resolved(redirectTarget).toString();
691 return;
692 }
693 submitProbe(reply->url().resolved(redirectTarget));
694 return;
695 }
696
697 submit(reply->url());
698}
699
700void ProviderPrivate::writeAuditLog(const QDateTime &dt)
701{
702 const QString path = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QStringLiteral("/kuserfeedback/audit");
703 QDir().mkpath(path);
704
705 QJsonObject docObj;
706 foreach (auto source, dataSources) {
707 if (!isValidSource(source) || !source->isActive() || telemetryMode < source->telemetryMode())
708 continue;
709 QJsonObject obj;
710 const auto data = source->data();
711 if (data.canConvert<QVariantMap>())
712 obj.insert(QLatin1String("data"), QJsonObject::fromVariantMap(data.toMap()));
713 else if (data.canConvert<QVariantList>())
714 obj.insert(QLatin1String("data"), QJsonArray::fromVariantList(data.value<QVariantList>()));
715 if (obj.isEmpty())
716 continue;
717 obj.insert(QLatin1String("telemetryMode"), QString::fromLatin1(telemetryModeEnum().valueToKey(source->telemetryMode())));
718 obj.insert(QLatin1String("description"), source->description());
719 docObj.insert(source->id(), obj);
720 }
721
722 QFile file(path + QLatin1Char('/') + dt.toString(QStringLiteral("yyyyMMdd-hhmmss")) + QStringLiteral(".log"));
723 if (!file.open(QFile::WriteOnly)) {
724 qCWarning(Log) << "Unable to open audit log file:" << file.fileName() << file.errorString();
725 return;
726 }
727
728 QJsonDocument doc(docObj);
729 file.write(doc.toJson());
730
731 qCDebug(Log) << "Audit log written:" << file.fileName();
732}
733
734QString Provider::describeDataSources() const
735{
736 QString ret;
737
738 const auto& mo = staticMetaObject;
739 const int modeEnumIdx = mo.indexOfEnumerator("TelemetryMode");
740 Q_ASSERT(modeEnumIdx >= 0);
741
742 const auto modeEnum = mo.enumerator(modeEnumIdx);
743 for (auto source : qAsConst(d->dataSources)) {
744 ret += QString::fromUtf8(modeEnum.valueToKey(source->telemetryMode())) + QStringLiteral(": ") + source->name() + QLatin1Char('\n');
745 }
746 return ret;
747}
748
749#include "moc_provider.cpp"
Base class for data sources for telemetry data.
void load(QSettings *settings)
Load persistent state for this data source.
QString id() const
Returns the ID of this data source.
void reset(QSettings *settings)
Reset the persistent state of this data source.
virtual QVariant data()=0
Returns the data gathered by this source.
virtual QString name() const
Returns a short name of this data source.
Provider::TelemetryMode telemetryMode() const
Returns which telemetry colleciton mode this data source belongs to.
bool isActive() const
Checks whether this data source is active or not If the data source is not active,...
virtual QString description() const =0
Returns a human-readable, translated description of what this source provides.
The central object managing data sources and transmitting feedback to the server.
Definition provider.h:32
void providerSettingsChanged()
Emitted when any provider setting changed.
void store()
Manually store settings of the provider and all added data sources.
Definition provider.cpp:629
void submit()
Manually submit currently recorded data.
Definition provider.cpp:634
void setProductIdentifier(const QString &productId)
Set the product identifier.
Definition provider.cpp:447
int submissionInterval
Submission interval in days.
Definition provider.h:65
void surveyCompleted(const KUserFeedback::SurveyInfo &info)
Marks the given survey as completed.
Definition provider.cpp:611
void restoreDefaults()
Set the telemetry mode and the survey interval back to their default values.
Definition provider.cpp:436
void setTelemetryMode(TelemetryMode mode)
Set which telemetry data should be submitted.
Definition provider.cpp:496
Provider(QObject *parent=nullptr)
Create a new feedback provider.
Definition provider.cpp:399
int encouragementDelay
Encouragement delay after application start in seconds.
Definition provider.h:86
int applicationUsageTimeUntilEncouragement
Application usage time in seconds before an encouragement message is shown.
Definition provider.h:81
void setEncouragementDelay(int secs)
Set the delay after application start for the earliest display of the encouragement message.
Definition provider.cpp:588
int surveyInterval
The interval in which the user accepts surveys.
Definition provider.h:44
void setApplicationStartsUntilEncouragement(int starts)
Set the amount of application starts until the encouragement message should be shown.
Definition provider.cpp:560
void telemetryModeChanged()
Emitted when the telemetry collection mode has changed.
void load()
Manually load settings of the provider and all added data sources.
Definition provider.cpp:624
void enabledChanged()
Emitted when the global enabled state changed.
AbstractDataSource * dataSource(const QString &id) const
Returns a data source with matched id.
Definition provider.cpp:531
void setSurveyInterval(int days)
Sets the minimum time in days between two surveys.
Definition provider.cpp:542
TelemetryMode telemetryMode
The telemetry mode the user has configured.
Definition provider.h:50
QString productIdentifier
Unique product id as set on the feedback server.
Definition provider.h:55
void surveyIntervalChanged()
Emitted when the survey interval changed.
void addDataSource(AbstractDataSource *source)
Adds a data source for telemetry data collection.
Definition provider.cpp:508
void setEnabled(bool enabled)
Set the global (user-wide) activation state for feedback functionality.
Definition provider.cpp:428
QUrl feedbackServer
URL of the feedback server.
Definition provider.h:60
TelemetryMode
Telemetry collection modes.
Definition provider.h:102
@ NoTelemetry
Transmit no data at all.
Definition provider.h:103
bool isEnabled() const
Returns whether feedback functionality is enabled on this system.
Definition provider.cpp:421
void setApplicationUsageTimeUntilEncouragement(int secs)
Set the amount of usage time until the encouragement message should be shown.
Definition provider.cpp:574
void dataSourcesChanged()
Emitted when a data source is added or removed.
bool enabled
The global enabled state of the feedback functionality.
Definition provider.h:37
int applicationStartsUntilEncouragement
Times the application has to be started before an encouragement message is shown.
Definition provider.h:73
void setSubmissionInterval(int days)
Set the automatic submission interval in days.
Definition provider.cpp:482
int encouragementInterval
Encouragement interval.
Definition provider.h:91
QVector< AbstractDataSource * > dataSources() const
Returns all data sources that have been added to this provider.
Definition provider.cpp:526
void setFeedbackServer(const QUrl &url)
Set the feedback server URL.
Definition provider.cpp:469
void setEncouragementInterval(int days)
Sets the interval after the encouragement should be repeated.
Definition provider.cpp:602
Data source reporting the total amount of applications starts.
Information about a survey request.
Definition surveyinfo.h:31
Data source reporting the total usage time of the application.
QString path(const QString &relativePath)
Classes for integrating telemetry collection, survey targeting, and contribution encouragenemt and co...
QCoreApplication * instance()
QDateTime currentDateTime()
QString toString(QStringView format, QCalendar cal) const const
bool mkpath(const QString &dirPath) const const
QString errorString() const const
QByteArray readAll()
QJsonArray fromVariantList(const QVariantList &list)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
iterator end()
iterator find(QLatin1StringView key)
QJsonObject fromVariantMap(const QVariantMap &map)
iterator insert(QLatin1StringView key, const QJsonValue &value)
bool isEmpty() const const
QVariant attribute(QNetworkRequest::Attribute code) const const
NetworkError error() const const
QUrl url() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
QString writableLocation(StandardLocation type)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
void timeout()
QUrl resolved(const QUrl &relative) const const
QString toString(FormattingOptions options) const const
QString toString(StringFormat mode) const const
void * data()
QUrl toUrl() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:56 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.