Krita Source Code Documentation
Loading...
Searching...
No Matches
KisSupporterBundlesFetcher Class Reference

#include <KisSupporterBundlesFetcher.h>

+ Inheritance diagram for KisSupporterBundlesFetcher:

Signals

void fetchIndexDone (bool success, const QString &errorMessage)
 

Public Member Functions

const QVector< KisSupporterBundle > & bundles () const
 
QNetworkReply * fetchBundle (const KisSupporterBundle &bundle)
 
void fetchIndex ()
 
bool isFetching () const
 
 KisSupporterBundlesFetcher (QObject *parent=nullptr)
 

Static Public Member Functions

static bool hasNetworkReplyError (QNetworkReply *reply, QString &outErrorMessage)
 

Private Member Functions

bool checkExistingIndexDb (unsigned int expectedVersion, const QString &expectedHash)
 
bool doWithIndexDb (QString &outErrorMessage, const std::function< bool(QSqlDatabase &, QString &)> &block)
 
void emitCheckTxtFetchFailed (const QString &errorMessage)
 
void emitFailure (const QString &errorMessage)
 
void emitIndexDbFetchFailed (const QString &errorMessage)
 
QNetworkReply * fetch (const QString &path)
 
void fetchIndexDb (unsigned int expectedVersion, const QString &expectedHash)
 
void handleCheckTxtFetched (QNetworkReply *reply)
 
bool handleCheckTxtPredefinedFailure (const QString &content)
 
void handleIndexDbFetched (QNetworkReply *reply, unsigned int expectedVersion, const QString &expectedHash)
 
void readIndexDbBundles ()
 

Static Private Member Functions

static bool readSqliteUserVersion (const QByteArray &bytes, unsigned int &outVersion)
 

Private Attributes

QVector< KisSupporterBundlem_bundles
 
bool m_fetching = false
 
QString m_indexDbPath
 
KisNetworkAccessManagerm_nam = nullptr
 

Static Private Attributes

static constexpr char BASE_URL [] = "https://cdn.kde.org/krita/.android-supporters-extra/supporterbundles/"
 

Detailed Description

Definition at line 19 of file KisSupporterBundlesFetcher.h.

Constructor & Destructor Documentation

◆ KisSupporterBundlesFetcher()

KisSupporterBundlesFetcher::KisSupporterBundlesFetcher ( QObject * parent = nullptr)
explicit

Definition at line 22 of file KisSupporterBundlesFetcher.cpp.

23 : QObject(parent)
24{
25 QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
26 m_indexDbPath = cacheDir.filePath(QStringLiteral("kissupporterbundlesindex.db"));
27 dbgResources << "Index db path:" << m_indexDbPath;
28}
#define dbgResources
Definition kis_debug.h:43

References dbgResources, and m_indexDbPath.

Member Function Documentation

◆ bundles()

const QVector< KisSupporterBundle > & KisSupporterBundlesFetcher::bundles ( ) const
inline

Definition at line 30 of file KisSupporterBundlesFetcher.h.

31 {
32 return m_bundles;
33 }
QVector< KisSupporterBundle > m_bundles

References m_bundles.

◆ checkExistingIndexDb()

bool KisSupporterBundlesFetcher::checkExistingIndexDb ( unsigned int expectedVersion,
const QString & expectedHash )
private

Definition at line 132 of file KisSupporterBundlesFetcher.cpp.

133{
134 QFile file(m_indexDbPath);
135 if (!file.open(QIODevice::ReadOnly)) {
136 dbgResources << "Could not open existing index db";
137 return false;
138 }
139
140 QByteArray bytes = file.readAll();
141 unsigned int version;
142 if (!readSqliteUserVersion(bytes, version)) {
143 dbgResources << "Could not read existing version";
144 return false;
145 }
146
147 if (version != expectedVersion) {
148 dbgResources << "Existing version" << version << "!=" << expectedVersion;
149 return false;
150 }
151
152 QString hash = QCryptographicHash::hash(bytes, QCryptographicHash::Md5).toHex();
153 if (hash != expectedHash) {
154 dbgResources << "Existing hash" << hash << "!=" << expectedHash;
155 return false;
156 }
157
158 dbgResources << "Cached index database is valid";
159 return true;
160}
static bool readSqliteUserVersion(const QByteArray &bytes, unsigned int &outVersion)

References dbgResources, m_indexDbPath, and readSqliteUserVersion().

◆ doWithIndexDb()

bool KisSupporterBundlesFetcher::doWithIndexDb ( QString & outErrorMessage,
const std::function< bool(QSqlDatabase &, QString &)> & block )
private

Definition at line 331 of file KisSupporterBundlesFetcher.cpp.

333{
334 QString connectionName = QStringLiteral("KisSupporterBundlesFetcher");
335 bool ok;
336 {
337 QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), connectionName);
338 db.setDatabaseName(m_indexDbPath);
339 if (db.open()) {
340 ok = block(db, outErrorMessage);
341 db.close();
342 } else {
343 ok = false;
344 outErrorMessage = db.lastError().text();
345 }
346 }
347 QSqlDatabase::removeDatabase(connectionName);
348 return ok;
349}

References m_indexDbPath.

◆ emitCheckTxtFetchFailed()

void KisSupporterBundlesFetcher::emitCheckTxtFetchFailed ( const QString & errorMessage)
private

Definition at line 315 of file KisSupporterBundlesFetcher.cpp.

316{
317 emitFailure(i18n("Failed to retrieve bundle checksums: %1", errorMessage));
318}
void emitFailure(const QString &errorMessage)

References emitFailure().

◆ emitFailure()

void KisSupporterBundlesFetcher::emitFailure ( const QString & errorMessage)
private

Definition at line 325 of file KisSupporterBundlesFetcher.cpp.

326{
327 m_fetching = false;
328 Q_EMIT fetchIndexDone(false, errorMessage);
329}
void fetchIndexDone(bool success, const QString &errorMessage)

References fetchIndexDone(), and m_fetching.

◆ emitIndexDbFetchFailed()

void KisSupporterBundlesFetcher::emitIndexDbFetchFailed ( const QString & errorMessage)
private

Definition at line 320 of file KisSupporterBundlesFetcher.cpp.

321{
322 emitFailure(i18n("Failed to retrieve bundles: %1", errorMessage));
323}

References emitFailure().

◆ fetch()

QNetworkReply * KisSupporterBundlesFetcher::fetch ( const QString & path)
private

Definition at line 299 of file KisSupporterBundlesFetcher.cpp.

300{
301 QUrl url(QString::fromUtf8(BASE_URL));
302 url.setPath(url.path(QUrl::FullyDecoded) + path);
303
304 QNetworkRequest req;
305 req.setUrl(url);
306
307 if (!m_nam) {
308 m_nam = new KisNetworkAccessManager(this);
309 }
310
311 dbgResources << "Fetch" << url;
312 return m_nam->get(req);
313}
Network Access Manager for use with Krita.

References BASE_URL, dbgResources, and m_nam.

◆ fetchBundle()

QNetworkReply * KisSupporterBundlesFetcher::fetchBundle ( const KisSupporterBundle & bundle)

Definition at line 41 of file KisSupporterBundlesFetcher.cpp.

42{
43 return fetch(QStringLiteral("bundles/%1").arg(bundle.fileName));
44}
QNetworkReply * fetch(const QString &path)

References fetch(), and KisSupporterBundle::fileName.

◆ fetchIndex()

void KisSupporterBundlesFetcher::fetchIndex ( )

Definition at line 30 of file KisSupporterBundlesFetcher.cpp.

31{
33 m_fetching = true;
34 QNetworkReply *reply = fetch(QStringLiteral("check0.txt"));
35 connect(reply, &QNetworkReply::finished, this, [=] {
37 reply->deleteLater();
38 });
39}
void handleCheckTxtFetched(QNetworkReply *reply)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128

References fetch(), handleCheckTxtFetched(), KIS_SAFE_ASSERT_RECOVER_RETURN, and m_fetching.

◆ fetchIndexDb()

void KisSupporterBundlesFetcher::fetchIndexDb ( unsigned int expectedVersion,
const QString & expectedHash )
private

Definition at line 162 of file KisSupporterBundlesFetcher.cpp.

163{
164 QNetworkReply *reply = fetch(QStringLiteral("index0.db"));
165 connect(reply, &QNetworkReply::finished, this, [=] {
166 handleIndexDbFetched(reply, expectedVersion, expectedHash);
167 reply->deleteLater();
168 });
169}
void handleIndexDbFetched(QNetworkReply *reply, unsigned int expectedVersion, const QString &expectedHash)

References fetch(), and handleIndexDbFetched().

◆ fetchIndexDone

void KisSupporterBundlesFetcher::fetchIndexDone ( bool success,
const QString & errorMessage )
signal

◆ handleCheckTxtFetched()

void KisSupporterBundlesFetcher::handleCheckTxtFetched ( QNetworkReply * reply)
private

Definition at line 62 of file KisSupporterBundlesFetcher.cpp.

63{
64 QString errorMessage;
65 if (hasNetworkReplyError(reply, errorMessage)) {
66 emitCheckTxtFetchFailed(errorMessage);
67 return;
68 }
69
70 QString content = QString::fromUtf8(reply->readAll());
71
72 // "Predefined failures" are for forward-compatibility for stuff like the
73 // format changing and old versions of Krita becoming unsupported or the
74 // service getting discontinued altogether.
76 return;
77 }
78
79 // The check0.txt is supposed to contain a version followed by an MD5 hash
80 // digest. For forward-compatibility, we'll ignore anything else afterwards.
81 QRegularExpression re(QStringLiteral("\\A\\s*([0-9]+)\\s+([0-9a-fA-F]+)\\b"));
82 QRegularExpressionMatch match = re.match(content);
83 if (!match.hasMatch()) {
84 emitCheckTxtFetchFailed(i18n("Invalid checksums file."));
85 return;
86 }
87
88 bool ok;
89 unsigned int expectedVersion = match.captured(1).toUInt(&ok);
90 dbgResources << "Expected version from fetched check0.txt:" << expectedVersion;
91 if (!ok) {
92 emitCheckTxtFetchFailed(i18n("Invalid checksums version."));
93 return;
94 }
95
96 QString expectedHash = match.captured(2);
97 dbgResources << "Expected hash from fetched check0.txt:" << expectedHash;
98
99 if (checkExistingIndexDb(expectedVersion, expectedHash)) {
101 } else {
102 fetchIndexDb(expectedVersion, expectedHash);
103 }
104}
bool checkExistingIndexDb(unsigned int expectedVersion, const QString &expectedHash)
void emitCheckTxtFetchFailed(const QString &errorMessage)
static bool hasNetworkReplyError(QNetworkReply *reply, QString &outErrorMessage)
void fetchIndexDb(unsigned int expectedVersion, const QString &expectedHash)
bool handleCheckTxtPredefinedFailure(const QString &content)

References checkExistingIndexDb(), dbgResources, emitCheckTxtFetchFailed(), fetchIndexDb(), handleCheckTxtPredefinedFailure(), hasNetworkReplyError(), and readIndexDbBundles().

◆ handleCheckTxtPredefinedFailure()

bool KisSupporterBundlesFetcher::handleCheckTxtPredefinedFailure ( const QString & content)
private

Definition at line 106 of file KisSupporterBundlesFetcher.cpp.

107{
108 QRegularExpression re(QStringLiteral("\\A!failure=([a-zA-Z0-9]*)"), QRegularExpression::CaseInsensitiveOption);
109 QRegularExpressionMatch match = re.match(content);
110 if (!match.hasMatch()) {
111 return false;
112 }
113
114 QString failureType = match.captured(1);
115 QString errorMessage;
116 if (failureType.compare(QStringLiteral("outdated"), Qt::CaseInsensitive) == 0) {
117 errorMessage = i18n("You have to update Krita to use this service.");
118 } else if (failureType.compare(QStringLiteral("region"), Qt::CaseInsensitive) == 0) {
119 errorMessage = i18n("This service is not available in your region.");
120 } else if (failureType.compare(QStringLiteral("unavailable"), Qt::CaseInsensitive) == 0) {
121 errorMessage = i18n("This service is currently unavailable.");
122 } else if (failureType.compare(QStringLiteral("discontinued"), Qt::CaseInsensitive) == 0) {
123 errorMessage = i18n("This service has been discontinued.");
124 } else {
125 errorMessage = i18n("Unknown service error %1.").arg(failureType);
126 }
127
128 emitFailure(errorMessage);
129 return true;
130}

References emitFailure().

◆ handleIndexDbFetched()

void KisSupporterBundlesFetcher::handleIndexDbFetched ( QNetworkReply * reply,
unsigned int expectedVersion,
const QString & expectedHash )
private

Definition at line 171 of file KisSupporterBundlesFetcher.cpp.

174{
175 QString errorMessage;
176 if (hasNetworkReplyError(reply, errorMessage)) {
177 emitIndexDbFetchFailed(errorMessage);
178 return;
179 }
180
181 QByteArray bytes = reply->readAll();
182 unsigned int version;
183 if (!readSqliteUserVersion(bytes, version)) {
184 emitIndexDbFetchFailed(i18n("Not enough data."));
185 return;
186 }
187
188 dbgResources << "got version:" << version;
189 dbgResources << "expected:" << expectedVersion;
190 if (version != expectedVersion) {
191 emitIndexDbFetchFailed(i18n("Invalid version."));
192 return;
193 }
194
195 QString hash = QCryptographicHash::hash(bytes, QCryptographicHash::Md5).toHex();
196 dbgResources << "got hash:" << hash;
197 dbgResources << "expected:" << expectedHash;
198 if (hash != expectedHash) {
199 emitIndexDbFetchFailed(i18n("Invalid checksum."));
200 return;
201 }
202
203 QString dirName = QFileInfo(m_indexDbPath).path();
204 if (!QDir().mkpath(dirName)) {
205 emitIndexDbFetchFailed(i18n("Failed to create directory %1.").arg(dirName));
206 return;
207 }
208
209 QSaveFile saveFile(m_indexDbPath);
210 saveFile.setDirectWriteFallback(true);
211 if (!saveFile.open(QIODevice::WriteOnly)) {
212 emitIndexDbFetchFailed(i18n("Failed to open %1 for writing.").arg(m_indexDbPath));
213 return;
214 }
215
216 qint64 written = saveFile.write(bytes);
217 if (written != qint64(bytes.size()) || !saveFile.commit()) {
218 emitIndexDbFetchFailed(i18n("Failed to write %1.").arg(m_indexDbPath));
219 return;
220 }
221
223}
void emitIndexDbFetchFailed(const QString &errorMessage)

References dbgResources, emitIndexDbFetchFailed(), hasNetworkReplyError(), m_indexDbPath, readIndexDbBundles(), and readSqliteUserVersion().

◆ hasNetworkReplyError()

bool KisSupporterBundlesFetcher::hasNetworkReplyError ( QNetworkReply * reply,
QString & outErrorMessage )
static

Definition at line 46 of file KisSupporterBundlesFetcher.cpp.

47{
48 if (reply->error() != QNetworkReply::NoError) {
49 outErrorMessage = reply->errorString();
50 return true;
51 }
52
53 int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
54 if (statusCode < 200 || statusCode > 300) {
55 outErrorMessage = i18n("Server responded with error %1.").arg(statusCode);
56 return true;
57 }
58
59 return false;
60}

◆ isFetching()

bool KisSupporterBundlesFetcher::isFetching ( ) const
inline

Definition at line 25 of file KisSupporterBundlesFetcher.h.

26 {
27 return m_fetching;
28 }

References m_fetching.

◆ readIndexDbBundles()

void KisSupporterBundlesFetcher::readIndexDbBundles ( )
private

Definition at line 225 of file KisSupporterBundlesFetcher.cpp.

226{
227 QString errorMessage;
228 bool ok = doWithIndexDb(errorMessage, [this](QSqlDatabase &db, QString &outErrorMessage) {
229 QSqlQuery query(db);
230
231 QString selectBundlesSql = QStringLiteral(
232 "select bundle_id, file_name, size_in_bytes, source, title,\n"
233 " description, author, license, checksum, thumbnail\n"
234 "from bundle");
235 if (!query.exec(selectBundlesSql)) {
236 outErrorMessage = query.lastError().text();
237 return false;
238 }
239
240 QHash<long long, KisSupporterBundle> bundlesById;
241 while (query.next()) {
242 long long id = query.value(0).toLongLong();
243 KisSupporterBundle bundle = {
244 query.value(1).toString(),
245 query.value(2).toLongLong(),
246 query.value(3).toString(),
247 query.value(4).toString(),
248 query.value(5).toString(),
249 query.value(6).toString(),
250 query.value(7).toString(),
251 query.value(8).toString(),
252 QPixmap(),
253 QSet<QString>(),
254 QHash<QString, int>(),
255 };
256 bundle.thumbnail.loadFromData(query.value(9).toByteArray());
257 bundlesById.insert(id, bundle);
258 }
259
260 QString selectTagsSql = QStringLiteral("select bundle_id, tag from bundle_tag");
261 if (!query.exec(selectTagsSql)) {
262 outErrorMessage = query.lastError().text();
263 return false;
264 }
265
266 while (query.next()) {
267 long long id = query.value(0).toLongLong();
268 QHash<long long, KisSupporterBundle>::iterator it = bundlesById.find(id);
269 if (it != bundlesById.end()) {
270 it->tags.insert(query.value(1).toString());
271 }
272 }
273
274 QString selectResourceCountsSql = QStringLiteral(
275 "select bundle_id, media_type, amount\n"
276 "from bundle_resource_count");
277 if (query.exec(selectResourceCountsSql)) {
278 while (query.next()) {
279 long long id = query.value(0).toLongLong();
280 QHash<long long, KisSupporterBundle>::iterator it = bundlesById.find(id);
281 if (it != bundlesById.end()) {
282 it->resourceCountByMediaType.insert(query.value(1).toString(), query.value(2).toInt());
283 }
284 }
285 } else {
286 warnResources << "Error querying bundle resource counts:" << query.lastError().text();
287 // Not critical to have these counts, just keep going.
288 }
289
290 m_bundles = QVector<KisSupporterBundle>(bundlesById.begin(), bundlesById.end());
291 dbgResources << "Loaded" << m_bundles.size() << "supporter bundle(s)";
292 return true;
293 });
294
295 m_fetching = false;
296 emit fetchIndexDone(ok, errorMessage);
297}
bool doWithIndexDb(QString &outErrorMessage, const std::function< bool(QSqlDatabase &, QString &)> &block)
#define warnResources
Definition kis_debug.h:85

References dbgResources, doWithIndexDb(), fetchIndexDone(), m_bundles, m_fetching, KisSupporterBundle::thumbnail, and warnResources.

◆ readSqliteUserVersion()

bool KisSupporterBundlesFetcher::readSqliteUserVersion ( const QByteArray & bytes,
unsigned int & outVersion )
staticprivate

Definition at line 351 of file KisSupporterBundlesFetcher.cpp.

352{
353 // The SQLite format stores its "user version" value at index 60 as a 32 bit
354 // bigendian unsigned integer. See https://www.sqlite.org/fileformat.html
355 if (bytes.size() < 64) {
356 return false;
357 } else {
358 outVersion = qFromBigEndian<quint32>(bytes.constData() + 60);
359 return true;
360 }
361}

Member Data Documentation

◆ BASE_URL

constexpr char KisSupporterBundlesFetcher::BASE_URL[] = "https://cdn.kde.org/krita/.android-supporters-extra/supporterbundles/"
staticconstexprprivate

Definition at line 45 of file KisSupporterBundlesFetcher.h.

◆ m_bundles

QVector<KisSupporterBundle> KisSupporterBundlesFetcher::m_bundles
private

Definition at line 70 of file KisSupporterBundlesFetcher.h.

◆ m_fetching

bool KisSupporterBundlesFetcher::m_fetching = false
private

Definition at line 71 of file KisSupporterBundlesFetcher.h.

◆ m_indexDbPath

QString KisSupporterBundlesFetcher::m_indexDbPath
private

Definition at line 69 of file KisSupporterBundlesFetcher.h.

◆ m_nam

KisNetworkAccessManager* KisSupporterBundlesFetcher::m_nam = nullptr
private

Definition at line 68 of file KisSupporterBundlesFetcher.h.


The documentation for this class was generated from the following files: