Gravatar

gravatarresolvurljob.cpp
1/*
2 SPDX-FileCopyrightText: 2015-2025 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "gravatarresolvurljob.h"
8using namespace Qt::Literals::StringLiterals;
9
10#include "gravatar_debug.h"
11#include "misc/gravatarcache.h"
12#include "misc/hash.h"
13
14#include <QCryptographicHash>
15#include <QNetworkInformation>
16#include <QNetworkReply>
17#include <QUrlQuery>
18
19using namespace Gravatar;
20
21class Gravatar::GravatarResolvUrlJobPrivate
22{
23public:
24 GravatarResolvUrlJobPrivate() = default;
25 QPixmap mPixmap;
26 QString mEmail;
27 Hash mCalculatedHash;
28 QNetworkAccessManager *mNetworkAccessManager = nullptr;
29 int mSize = 80;
30
31 enum Backend {
32 None = 0x0,
33 Libravatar = 0x1,
34 Gravatar = 0x2
35 };
36 int mBackends = Gravatar;
37
38 bool mHasGravatar = false;
39 bool mUseDefaultPixmap = false;
40};
41
42GravatarResolvUrlJob::GravatarResolvUrlJob(QObject *parent)
43 : QObject(parent)
44 , d(new Gravatar::GravatarResolvUrlJobPrivate)
45{
47}
48
49GravatarResolvUrlJob::~GravatarResolvUrlJob() = default;
50
51bool GravatarResolvUrlJob::canStart() const
52{
53 if (auto info = QNetworkInformation::instance();
54 info && (info->reachability() != QNetworkInformation::Reachability::Online || info->isBehindCaptivePortal())) {
55 return false;
56 }
57
58 // qCDebug(GRAVATAR_LOG) << "email " << d->mEmail;
59 return !d->mEmail.trimmed().isEmpty() && (d->mEmail.contains(QLatin1Char('@')));
60}
61
62QUrl GravatarResolvUrlJob::generateGravatarUrl(bool useLibravatar)
63{
64 return createUrl(useLibravatar);
65}
66
67bool GravatarResolvUrlJob::hasGravatar() const
68{
69 return d->mHasGravatar;
70}
71
72void GravatarResolvUrlJob::startNetworkManager(const QUrl &url)
73{
74 if (!d->mNetworkAccessManager) {
75 d->mNetworkAccessManager = new QNetworkAccessManager(this);
76 d->mNetworkAccessManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
77 d->mNetworkAccessManager->setStrictTransportSecurityEnabled(true);
78 d->mNetworkAccessManager->enableStrictTransportSecurityStore(true);
79 connect(d->mNetworkAccessManager, &QNetworkAccessManager::finished, this, &GravatarResolvUrlJob::slotFinishLoadPixmap);
80 }
81
82 QNetworkRequest req(url);
84 req.setAttribute(QNetworkRequest::Http2AllowedAttribute, true);
85 d->mNetworkAccessManager->get(req);
86}
87
88void GravatarResolvUrlJob::start()
89{
90 if (d->mBackends == GravatarResolvUrlJobPrivate::None) {
91 d->mBackends = GravatarResolvUrlJobPrivate::Gravatar; // default is Gravatar if nothing else is selected
92 }
93
94 d->mHasGravatar = false;
95 if (canStart()) {
96 processNextBackend();
97 } else {
98 qCDebug(GRAVATAR_LOG) << "Gravatar can not start";
100 }
101}
102
103void GravatarResolvUrlJob::processNextBackend()
104{
105 if (d->mHasGravatar || d->mBackends == GravatarResolvUrlJobPrivate::None) {
106 Q_EMIT finished(this);
107 deleteLater();
108 return;
109 }
110
111 QUrl url;
112 if (d->mBackends & GravatarResolvUrlJobPrivate::Libravatar) {
113 d->mBackends &= ~GravatarResolvUrlJobPrivate::Libravatar;
114 url = createUrl(true);
115 } else if (d->mBackends & GravatarResolvUrlJobPrivate::Gravatar) {
116 d->mBackends &= ~GravatarResolvUrlJobPrivate::Gravatar;
117 url = createUrl(false);
118 }
119
120 // qDebug() << " url " << url;
121 Q_EMIT resolvUrl(url);
122 if (!cacheLookup(d->mCalculatedHash)) {
123 startNetworkManager(url);
124 } else {
125 processNextBackend();
126 }
127}
128
129void GravatarResolvUrlJob::slotFinishLoadPixmap(QNetworkReply *reply)
130{
131 if (reply->error() == QNetworkReply::NoError) {
132 const QByteArray data = reply->readAll();
133 d->mPixmap.loadFromData(data);
134 d->mHasGravatar = true;
135 // For the moment don't use cache other we will store a lot of pixmap
136 if (!d->mUseDefaultPixmap) {
137 GravatarCache::self()->saveGravatarPixmap(d->mCalculatedHash, d->mPixmap);
138 }
139 } else {
141 GravatarCache::self()->saveMissingGravatar(d->mCalculatedHash);
142 } else {
143 qCDebug(GRAVATAR_LOG) << "Network error:" << reply->request().url() << reply->errorString();
144 }
145 }
146 reply->deleteLater();
147
148 processNextBackend();
149}
150
151QString GravatarResolvUrlJob::email() const
152{
153 return d->mEmail;
154}
155
156void GravatarResolvUrlJob::setEmail(const QString &email)
157{
158 d->mEmail = email;
159}
160
161Hash GravatarResolvUrlJob::calculateHash()
162{
163 const auto email = d->mEmail.toLower().toUtf8();
164 return Hash(QCryptographicHash::hash(email, QCryptographicHash::Md5), Hash::Md5);
165}
166
167bool GravatarResolvUrlJob::fallbackGravatar() const
168{
169 return d->mBackends & GravatarResolvUrlJobPrivate::Gravatar;
170}
171
172void GravatarResolvUrlJob::setFallbackGravatar(bool fallbackGravatar)
173{
174 if (fallbackGravatar) {
175 d->mBackends |= GravatarResolvUrlJobPrivate::Gravatar;
176 } else {
177 d->mBackends &= ~GravatarResolvUrlJobPrivate::Gravatar;
178 }
179}
180
181bool GravatarResolvUrlJob::useLibravatar() const
182{
183 return d->mBackends & GravatarResolvUrlJobPrivate::Libravatar;
184}
185
186void GravatarResolvUrlJob::setUseLibravatar(bool useLibravatar)
187{
188 if (useLibravatar) {
189 d->mBackends |= GravatarResolvUrlJobPrivate::Libravatar;
190 } else {
191 d->mBackends &= ~GravatarResolvUrlJobPrivate::Libravatar;
192 }
193}
194
195bool GravatarResolvUrlJob::useDefaultPixmap() const
196{
197 return d->mUseDefaultPixmap;
198}
199
200void GravatarResolvUrlJob::setUseDefaultPixmap(bool useDefaultPixmap)
201{
202 d->mUseDefaultPixmap = useDefaultPixmap;
203}
204
205int GravatarResolvUrlJob::size() const
206{
207 return d->mSize;
208}
209
210QPixmap GravatarResolvUrlJob::pixmap() const
211{
212 return d->mPixmap;
213}
214
215void GravatarResolvUrlJob::setSize(int size)
216{
217 if (size <= 0) {
218 size = 80;
219 } else if (size > 2048) {
220 size = 2048;
221 }
222 d->mSize = size;
223}
224
225Hash GravatarResolvUrlJob::calculatedHash() const
226{
227 return d->mCalculatedHash;
228}
229
230QUrl GravatarResolvUrlJob::createUrl(bool useLibravatar)
231{
232 QUrl url;
233 d->mCalculatedHash = Hash();
234 if (!canStart()) {
235 return url;
236 }
237 QUrlQuery query;
238 if (!d->mUseDefaultPixmap) {
239 // Add ?d=404
240 query.addQueryItem(QStringLiteral("d"), QStringLiteral("404"));
241 }
242 if (d->mSize != 80) {
243 query.addQueryItem(QStringLiteral("s"), QString::number(d->mSize));
244 }
245 url.setScheme(QStringLiteral("https"));
246 if (useLibravatar) {
247 url.setHost(QStringLiteral("seccdn.libravatar.org"));
248 } else {
249 url.setHost(QStringLiteral("secure.gravatar.com"));
250 }
251 d->mCalculatedHash = calculateHash();
252 url.setPath("/avatar/"_L1 + d->mCalculatedHash.hexString());
253 url.setQuery(query);
254 return url;
255}
256
257bool GravatarResolvUrlJob::cacheLookup(const Hash &hash)
258{
259 bool haveStoredPixmap = false;
260 const QPixmap pix = GravatarCache::self()->loadGravatarPixmap(hash, haveStoredPixmap);
261 if (haveStoredPixmap && !pix.isNull()) { // we know a Gravatar for this hash
262 d->mPixmap = pix;
263 d->mHasGravatar = true;
264 Q_EMIT finished(this);
265 deleteLater();
266 return true;
267 }
268 return haveStoredPixmap;
269}
270
271#include "moc_gravatarresolvurljob.cpp"
std::optional< QSqlQuery > query(const QString &queryStatement)
QByteArray hash(QByteArrayView data, Algorithm method)
QString errorString() const const
QByteArray readAll()
void finished(QNetworkReply *reply)
QNetworkInformation * instance()
NetworkError error() const const
QNetworkRequest request() const const
QUrl url() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
bool isNull() const const
QString number(double n, char format, int precision)
void setHost(const QString &host, ParsingMode mode)
void setPath(const QString &path, ParsingMode mode)
void setQuery(const QString &query, ParsingMode mode)
void setScheme(const QString &scheme)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 4 2025 12:02:55 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.