provider.cpp 7.85 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
    Copyright (C) 2016 Volker Krause <vkrause@kde.org>

    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 <http://www.gnu.org/licenses/>.
*/

18
#include <config-userfeedback-version.h>
19
#include "provider.h"
20
#include "surveyinfo.h"
21
22
23
24
25
26
27
28
29
30

#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QSettings>
31
#include <QStringList>
32
#include <QTime>
Volker Krause's avatar
Volker Krause committed
33
#include <QTimer>
34
35
#include <QUrl>

36
#include <algorithm>
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <numeric>

using namespace UserFeedback;

namespace UserFeedback {
class ProviderPrivate
{
public:
    ProviderPrivate(Provider *qq);

    void reset();
    int currentApplicationTime() const;

    void load();
    void store();

    void aboutToQuit();

    QByteArray jsonData() const;
    void submitFinished();
57
    void selectSurvey(const SurveyInfo &survey) const;
58
59
60
61
62
63
64
65

    Provider *q;

    QString productId;

    QNetworkAccessManager *networkAccessManager;
    QUrl serverUrl;
    QDateTime lastSubmitTime;
Volker Krause's avatar
Volker Krause committed
66
    int submissionInterval;
67
    Provider::StatisticsCollectionMode statisticsMode;
68

69
70
    int surveyInterval;
    QDateTime lastSurveyTime;
71
72
    QStringList completedSurveys;

73
74
75
76
77
78
79
80
81
    QTime startTime;
    int startCount;
    int usageTime;
};
}

ProviderPrivate::ProviderPrivate(Provider *qq)
    : q(qq)
    , networkAccessManager(Q_NULLPTR)
Volker Krause's avatar
Volker Krause committed
82
    , submissionInterval(-1)
83
    , statisticsMode(Provider::NoStatistics)
84
    , surveyInterval(-1)
85
86
87
    , startCount(0)
    , usageTime(0)
{
88
89
90
91
    auto domain = QCoreApplication::organizationDomain().split(QLatin1Char('.'));
    std::reverse(domain.begin(), domain.end());
    productId = domain.join(QLatin1Char('.')) + QLatin1Char('.') + QCoreApplication::applicationName();

92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
    startTime.start();
}

void ProviderPrivate::reset()
{
    startCount = 0;
    usageTime = 0;
    startTime.start();
}

int ProviderPrivate::currentApplicationTime() const
{
    return usageTime + (startTime.elapsed() / 1000);
}

void ProviderPrivate::load()
{
    QSettings settings;
    settings.beginGroup(QStringLiteral("UserFeedback"));
    lastSubmitTime = settings.value(QStringLiteral("LastSubmission")).toDateTime();
112
113
114
    // TODO store as string via QMetaEnum
    statisticsMode = static_cast<Provider::StatisticsCollectionMode>(settings.value(QStringLiteral("StatisticsCollectionMode"), Provider::NoStatistics).toInt());

115
116
    surveyInterval = settings.value(QStringLiteral("SurveyInterval"), -1).toInt();
    lastSurveyTime = settings.value(QStringLiteral("LastSurvey")).toDateTime();
117
    completedSurveys = settings.value(QStringLiteral("CompletedSurveys"), QStringList()).toStringList();
118
119
120

    startCount = std::max(settings.value(QStringLiteral("ApplicationStartCount"), 0).toInt() + 1, 1);
    usageTime = std::max(settings.value(QStringLiteral("ApplicationTime"), 0).toInt(), 0);
121
122
123
124
125
126
127
}

void ProviderPrivate::store()
{
    QSettings settings;
    settings.beginGroup(QStringLiteral("UserFeedback"));
    settings.setValue(QStringLiteral("LastSubmission"), lastSubmitTime);
128
129
    settings.setValue(QStringLiteral("StatisticsCollectionMode"), statisticsMode);

130
131
    settings.setValue(QStringLiteral("SurveyInterval"), surveyInterval);
    settings.setValue(QStringLiteral("LastSurvey"), lastSurveyTime);
132
    settings.setValue(QStringLiteral("CompletedSurveys"), completedSurveys);
133
134
135

    settings.setValue(QStringLiteral("ApplicationStartCount"), startCount);
    settings.setValue(QStringLiteral("ApplicationTime"), currentApplicationTime());
136
137
138
139
140
141
142
143
144
145
146
147
}

void ProviderPrivate::aboutToQuit()
{
    qDebug() << Q_FUNC_INFO;
    store();
}

QByteArray ProviderPrivate::jsonData() const
{
    QJsonObject obj;
    obj.insert(QStringLiteral("productId"), productId);
148
149
150
151
152
153

    if (statisticsMode != Provider::NoStatistics) {
        obj.insert(QStringLiteral("startCount"), startCount);
        obj.insert(QStringLiteral("usageTime"), currentApplicationTime());
        obj.insert(QStringLiteral("version"), QCoreApplication::applicationVersion());
    }
154
155
156
157
158
159
160
161
162
163

    QJsonDocument doc(obj);
    return doc.toJson();
}

void ProviderPrivate::submitFinished()
{
    auto reply = qobject_cast<QNetworkReply*>(q->sender());
    Q_ASSERT(reply);

164
165
    if (reply->error() != QNetworkReply::NoError) {
        qWarning() << "failed to submit user feedback:" << reply->errorString();
166
        return;
167
    }
168
169

    lastSubmitTime = QDateTime::currentDateTime();
170
171
172
173

    const auto obj = QJsonDocument::fromJson(reply->readAll()).object();
    if (obj.contains(QStringLiteral("survey"))) {
        const auto surveyObj = obj.value(QStringLiteral("survey")).toObject();
174
        const auto survey = SurveyInfo::fromJson(surveyObj);
175
        selectSurvey(survey);
176
    }
Volker Krause's avatar
Volker Krause committed
177
178
179

    if (submissionInterval > 0)
        QTimer::singleShot(submissionInterval * 24 * 60 * 60 * 1000, q, SLOT(submit()));
180
181
}

182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
void ProviderPrivate::selectSurvey(const SurveyInfo &survey) const
{
    qDebug() << Q_FUNC_INFO << "got survey:" << survey.url();
    if (surveyInterval < 0) // surveys disabled
        return;

    if (!survey.isValid() || completedSurveys.contains(QString::number(survey.id())))
        return;

    if (lastSurveyTime.addDays(surveyInterval) > QDateTime::currentDateTime())
        return;

    emit q->surveyAvailable(survey);
}

197
198
199
200
201
202
203
204
205
206
207
208

Provider::Provider(QObject *parent) :
    QObject(parent),
    d(new ProviderPrivate(this))
{
    qDebug() << Q_FUNC_INFO;

    connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit()));

    d->load();
}

209
210
211
212
213
Provider::~Provider()
{
    delete d;
}

214
215
216
217
218
219
220
221
222
223
void Provider::setProductIdentifier(const QString &productId)
{
    d->productId = productId;
}

void Provider::setFeedbackServer(const QUrl &url)
{
    d->serverUrl = url;
}

Volker Krause's avatar
Volker Krause committed
224
225
226
227
228
229
230
231
232
233
234
235
236
237
void Provider::setSubmissionInterval(int days)
{
    d->submissionInterval = days;
    if (d->submissionInterval <= 0)
        return;

    const auto nextSubmission = d->lastSubmitTime.addDays(days);
    const auto now = QDateTime::currentDateTime();
    if (nextSubmission <= now)
        submit();
    else
        QTimer::singleShot(now.msecsTo(nextSubmission), this, SLOT(submit()));
}

238
239
240
241
242
243
244
245
246
247
Provider::StatisticsCollectionMode Provider::statisticsCollectionMode() const
{
    return d->statisticsMode;
}

void Provider::setStatisticsCollectionMode(StatisticsCollectionMode mode)
{
    d->statisticsMode = mode;
}

248
249
250
251
252
253
254
255
256
257
int Provider::surveyInterval() const
{
    return d->surveyInterval;
}

void Provider::setSurveyInterval(int days)
{
    d->surveyInterval = days;
}

258
259
260
void Provider::setSurveyCompleted(const SurveyInfo &info)
{
    d->completedSurveys.push_back(QString::number(info.id()));
261
    d->lastSurveyTime = QDateTime::currentDateTime();
262
263
264
    d->store();
}

265
266
void Provider::submit()
{
267
268
269
270
271
    if (!d->serverUrl.isValid()) {
        qWarning() << "No feedback server URL specified!";
        return;
    }

272
273
274
275
276
277
278
    if (!d->networkAccessManager)
        d->networkAccessManager = new QNetworkAccessManager(this);

    auto url = d->serverUrl;
    url.setPath(url.path() + QStringLiteral("/receiver/submit"));
    QNetworkRequest request(url);
    request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
279
    request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("UserFeedback/") + QStringLiteral(USERFEEDBACK_VERSION));
280
281
282
283
284
    auto reply = d->networkAccessManager->post(request, d->jsonData());
    connect(reply, SIGNAL(finished()), this, SLOT(submitFinished()));
}

#include "moc_provider.cpp"