diff --git a/autotests/gravatarcachetest.h b/autotests/gravatarcachetest.h --- a/autotests/gravatarcachetest.h +++ b/autotests/gravatarcachetest.h @@ -33,6 +33,8 @@ void shouldHaveDefaultValue(); void shouldChangeCacheValue(); void testLookup(); + void testMissing(); + void testMissing_data(); }; #endif // GRAVATARCACHETEST_H diff --git a/autotests/gravatarcachetest.cpp b/autotests/gravatarcachetest.cpp --- a/autotests/gravatarcachetest.cpp +++ b/autotests/gravatarcachetest.cpp @@ -19,13 +19,17 @@ #include "gravatarcachetest.h" #include "../src/misc/gravatarcache.h" +#include "../src/misc/hash.h" +#include #include #include #include using namespace Gravatar; +Q_DECLARE_METATYPE(Gravatar::Hash) + GravatarCacheTest::GravatarCacheTest(QObject *parent) : QObject(parent) { @@ -61,11 +65,12 @@ void GravatarCacheTest::testLookup() { + Hash hash(QCryptographicHash::hash(QByteArray("test@example.com"), QCryptographicHash::Md5), Hash::Md5); { GravatarCache cache; cache.clearAllCache(); bool found = false; - const auto result = cache.loadGravatarPixmap(QStringLiteral("fa1afe1"), found); + const auto result = cache.loadGravatarPixmap(hash, found); QVERIFY(!found); QVERIFY(result.isNull()); } @@ -75,11 +80,11 @@ { GravatarCache cache; - cache.saveGravatarPixmap(QStringLiteral("fa1afe1"), px); + cache.saveGravatarPixmap(hash, px); // in-memory cache lookup bool found = false; - const auto result = cache.loadGravatarPixmap(QStringLiteral("fa1afe1"), found); + const auto result = cache.loadGravatarPixmap(hash, found); QVERIFY(found); QVERIFY(!result.isNull()); QCOMPARE(result.size(), QSize(42, 42)); @@ -89,11 +94,50 @@ // disk lookup GravatarCache cache; bool found = false; - const auto result = cache.loadGravatarPixmap(QStringLiteral("fa1afe1"), found); + const auto result = cache.loadGravatarPixmap(hash, found); QVERIFY(found); QVERIFY(!result.isNull()); QCOMPARE(result.size(), QSize(42, 42)); } } +void GravatarCacheTest::testMissing_data() +{ + QTest::addColumn("hash"); + QTest::newRow("md5") << Hash(QCryptographicHash::hash(QByteArray("testMD5@example.com"), QCryptographicHash::Md5), Hash::Md5); + QTest::newRow("Sha256") << Hash(QCryptographicHash::hash(QByteArray("testSHA256@example.com"), QCryptographicHash::Sha256), Hash::Sha256); +} + +void GravatarCacheTest::testMissing() +{ + QFETCH(Hash, hash); + { + GravatarCache cache; + cache.clearAllCache(); + bool found = false; + const auto result = cache.loadGravatarPixmap(hash, found); + QVERIFY(!found); + QVERIFY(result.isNull()); + } + + { + // store miss and verify in memory + GravatarCache cache; + cache.saveMissingGravatar(hash); + bool found = false; + const auto result = cache.loadGravatarPixmap(hash, found); + QVERIFY(found); + QVERIFY(result.isNull()); + } + + { + // verify miss in disk storage + GravatarCache cache; + bool found = false; + const auto result = cache.loadGravatarPixmap(hash, found); + QVERIFY(found); + QVERIFY(result.isNull()); + } +} + QTEST_MAIN(GravatarCacheTest) diff --git a/autotests/gravatarresolvurljobtest.cpp b/autotests/gravatarresolvurljobtest.cpp --- a/autotests/gravatarresolvurljobtest.cpp +++ b/autotests/gravatarresolvurljobtest.cpp @@ -19,6 +19,7 @@ #include "gravatarresolvurljobtest.h" #include "../src/job/gravatarresolvurljob.h" +#include "../src/misc/hash.h" #include GravatarResolvUrlJobTest::GravatarResolvUrlJobTest(QObject *parent) @@ -167,7 +168,7 @@ job.setEmail(input); job.setUseLibravatar(uselibravatar); QUrl url = job.generateGravatarUrl(job.useLibravatar()); - QCOMPARE(calculedhash, job.calculatedHash()); + QCOMPARE(calculedhash, job.calculatedHash().hexString()); QCOMPARE(url, output); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ set(gravatarlib_SRCS misc/gravatarcache.cpp + misc/hash.cpp widgets/gravatardownloadpixmapwidget.cpp widgets/gravatardownloadpixmapdialog.cpp widgets/gravatarconfigwidget.cpp diff --git a/src/job/gravatarresolvurljob.h b/src/job/gravatarresolvurljob.h --- a/src/job/gravatarresolvurljob.h +++ b/src/job/gravatarresolvurljob.h @@ -25,8 +25,12 @@ #include #include #include + +class GravatarResolvUrlJobTest; namespace Gravatar { class GravatarResolvUrlJobPrivate; +class Hash; + class GRAVATAR_EXPORT GravatarResolvUrlJob : public QObject { Q_OBJECT @@ -40,14 +44,9 @@ QString email() const; void setEmail(const QString &email); - QUrl generateGravatarUrl(bool useLibravatar); - bool hasGravatar() const; - QString calculatedHash() const; - void setSize(int size); - int size() const; QPixmap pixmap() const; @@ -67,13 +66,17 @@ private Q_SLOTS: void slotFinishLoadPixmap(QNetworkReply *reply); - void slotError(QNetworkReply::NetworkError error); private: + friend class ::GravatarResolvUrlJobTest; + + QUrl generateGravatarUrl(bool useLibravatar); + Hash calculatedHash() const; + void processNextBackend(); void startNetworkManager(const QUrl &url); QUrl createUrl(bool useLibravatar); - QString calculateHash(bool useLibravator); - bool cacheLookup(const QString &hash); + Hash calculateHash(bool useLibravator); + bool cacheLookup(const Hash &hash); GravatarResolvUrlJobPrivate *const d; }; } diff --git a/src/job/gravatarresolvurljob.cpp b/src/job/gravatarresolvurljob.cpp --- a/src/job/gravatarresolvurljob.cpp +++ b/src/job/gravatarresolvurljob.cpp @@ -19,6 +19,7 @@ #include "gravatarresolvurljob.h" #include "misc/gravatarcache.h" +#include "misc/hash.h" #include "gravatar_debug.h" #include @@ -36,24 +37,27 @@ GravatarResolvUrlJobPrivate() : mNetworkAccessManager(nullptr) , mSize(80) + , mBackends(Gravatar) , mHasGravatar(false) , mUseDefaultPixmap(false) - , mUseLibravatar(false) - , mFallbackGravatar(true) - , mFallbackDone(false) { } QPixmap mPixmap; QString mEmail; - QString mCalculatedHash; + Hash mCalculatedHash; QNetworkAccessManager *mNetworkAccessManager; int mSize; + + enum Backend { + None = 0x0, + Libravatar = 0x1, + Gravatar = 0x2 + }; + int mBackends; + bool mHasGravatar; bool mUseDefaultPixmap; - bool mUseLibravatar; - bool mFallbackGravatar; - bool mFallbackDone; }; GravatarResolvUrlJob::GravatarResolvUrlJob(QObject *parent) @@ -88,36 +92,51 @@ void GravatarResolvUrlJob::startNetworkManager(const QUrl &url) { - if (PimCommon::NetworkManager::self()->networkConfigureManager()->isOnline()) { - if (!d->mNetworkAccessManager) { - d->mNetworkAccessManager = new QNetworkAccessManager(this); - connect(d->mNetworkAccessManager, &QNetworkAccessManager::finished, this, &GravatarResolvUrlJob::slotFinishLoadPixmap); - } - QNetworkReply *reply = d->mNetworkAccessManager->get(QNetworkRequest(url)); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(slotError(QNetworkReply::NetworkError))); - } else { - qCDebug(GRAVATAR_LOG) << " network is not connected"; - deleteLater(); - return; + if (!d->mNetworkAccessManager) { + d->mNetworkAccessManager = new QNetworkAccessManager(this); + connect(d->mNetworkAccessManager, &QNetworkAccessManager::finished, this, &GravatarResolvUrlJob::slotFinishLoadPixmap); } + d->mNetworkAccessManager->get(QNetworkRequest(url)); } void GravatarResolvUrlJob::start() { + if (d->mBackends == GravatarResolvUrlJobPrivate::None) + d->mBackends = GravatarResolvUrlJobPrivate::Gravatar; // default is Gravatar if nothing else is selected + d->mHasGravatar = false; - d->mFallbackDone = false; if (canStart()) { - d->mCalculatedHash.clear(); - const QUrl url = createUrl(d->mUseLibravatar); - Q_EMIT resolvUrl(url); - if (!cacheLookup(d->mCalculatedHash)) - startNetworkManager(url); + processNextBackend(); } else { qCDebug(GRAVATAR_LOG) << "Gravatar can not start"; deleteLater(); } } +void GravatarResolvUrlJob::processNextBackend() +{ + if (d->mHasGravatar || d->mBackends == GravatarResolvUrlJobPrivate::None) { + Q_EMIT finished(this); + deleteLater(); + return; + } + + QUrl url; + if (d->mBackends & GravatarResolvUrlJobPrivate::Libravatar) { + d->mBackends &= ~GravatarResolvUrlJobPrivate::Libravatar; + url = createUrl(true); + } else if (d->mBackends & GravatarResolvUrlJobPrivate::Gravatar) { + d->mBackends &= ~GravatarResolvUrlJobPrivate::Gravatar; + url = createUrl(false); + } + + Q_EMIT resolvUrl(url); + if (!cacheLookup(d->mCalculatedHash)) + startNetworkManager(url); + else + processNextBackend(); +} + void GravatarResolvUrlJob::slotFinishLoadPixmap(QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { @@ -127,25 +146,14 @@ if (!d->mUseDefaultPixmap) { GravatarCache::self()->saveGravatarPixmap(d->mCalculatedHash, d->mPixmap); } - } else if (d->mUseLibravatar && d->mFallbackGravatar && !d->mFallbackDone) { - d->mFallbackDone = true; - d->mCalculatedHash.clear(); - const QUrl url = createUrl(false); - Q_EMIT resolvUrl(url); - if (!cacheLookup(d->mCalculatedHash)) - startNetworkManager(url); - return; + } else { + if (reply->error() == QNetworkReply::ContentNotFoundError) + GravatarCache::self()->saveMissingGravatar(d->mCalculatedHash); + else + qCDebug(GRAVATAR_LOG) << "Network error:" << reply->request().url() << reply->errorString(); } - reply->deleteLater(); - Q_EMIT finished(this); - deleteLater(); -} -void GravatarResolvUrlJob::slotError(QNetworkReply::NetworkError error) -{ - if (error == QNetworkReply::ContentNotFoundError) { - d->mHasGravatar = false; - } + processNextBackend(); } QString GravatarResolvUrlJob::email() const @@ -158,31 +166,38 @@ d->mEmail = email; } -QString GravatarResolvUrlJob::calculateHash(bool useLibravator) +Hash GravatarResolvUrlJob::calculateHash(bool useLibravator) { - QCryptographicHash hash(useLibravator ? QCryptographicHash::Sha256 : QCryptographicHash::Md5); - hash.addData(d->mEmail.toLower().toUtf8()); - return QString::fromUtf8(hash.result().toHex()); + const auto email = d->mEmail.toLower().toUtf8(); + if (useLibravator) + return Hash(QCryptographicHash::hash(email, QCryptographicHash::Sha256), Hash::Sha256); + return Hash(QCryptographicHash::hash(email, QCryptographicHash::Md5), Hash::Md5); } bool GravatarResolvUrlJob::fallbackGravatar() const { - return d->mFallbackGravatar; + return d->mBackends & GravatarResolvUrlJobPrivate::Gravatar; } void GravatarResolvUrlJob::setFallbackGravatar(bool fallbackGravatar) { - d->mFallbackGravatar = fallbackGravatar; + if (fallbackGravatar) + d->mBackends |= GravatarResolvUrlJobPrivate::Gravatar; + else + d->mBackends &= ~GravatarResolvUrlJobPrivate::Gravatar; } bool GravatarResolvUrlJob::useLibravatar() const { - return d->mUseLibravatar; + return d->mBackends & GravatarResolvUrlJobPrivate::Libravatar; } void GravatarResolvUrlJob::setUseLibravatar(bool useLibravatar) { - d->mUseLibravatar = useLibravatar; + if (useLibravatar) + d->mBackends |= GravatarResolvUrlJobPrivate::Libravatar; + else + d->mBackends &= ~GravatarResolvUrlJobPrivate::Libravatar; } bool GravatarResolvUrlJob::useDefaultPixmap() const @@ -215,15 +230,15 @@ d->mSize = size; } -QString GravatarResolvUrlJob::calculatedHash() const +Hash GravatarResolvUrlJob::calculatedHash() const { return d->mCalculatedHash; } QUrl GravatarResolvUrlJob::createUrl(bool useLibravatar) { QUrl url; - d->mCalculatedHash.clear(); + d->mCalculatedHash = Hash(); if (!canStart()) { return url; } @@ -243,21 +258,21 @@ } url.setPort(443); d->mCalculatedHash = calculateHash(useLibravatar); - url.setPath(QLatin1String("/avatar/") + d->mCalculatedHash); + url.setPath(QLatin1String("/avatar/") + d->mCalculatedHash.hexString()); url.setQuery(query); return url; } -bool GravatarResolvUrlJob::cacheLookup(const QString &hash) +bool GravatarResolvUrlJob::cacheLookup(const Hash &hash) { bool haveStoredPixmap = false; const QPixmap pix = GravatarCache::self()->loadGravatarPixmap(hash, haveStoredPixmap); - if (haveStoredPixmap && !pix.isNull()) { + if (haveStoredPixmap && !pix.isNull()) { // we know a Gravatar for this hash d->mPixmap = pix; d->mHasGravatar = true; Q_EMIT finished(this); deleteLater(); return true; } - return false; + return haveStoredPixmap; } diff --git a/src/misc/gravatarcache.h b/src/misc/gravatarcache.h --- a/src/misc/gravatarcache.h +++ b/src/misc/gravatarcache.h @@ -26,17 +26,20 @@ namespace Gravatar { class GravatarCachePrivate; +class Hash; + class GRAVATAR_EXPORT GravatarCache { public: static GravatarCache *self(); GravatarCache(); ~GravatarCache(); - void saveGravatarPixmap(const QString &hashStr, const QPixmap &pixmap); + void saveGravatarPixmap(const Hash &hash, const QPixmap &pixmap); + void saveMissingGravatar(const Hash &hash); - QPixmap loadGravatarPixmap(const QString &hashStr, bool &gravatarStored); + QPixmap loadGravatarPixmap(const Hash &hash, bool &gravatarStored); int maximumSize() const; void setMaximumSize(int maximumSize); diff --git a/src/misc/gravatarcache.cpp b/src/misc/gravatarcache.cpp --- a/src/misc/gravatarcache.cpp +++ b/src/misc/gravatarcache.cpp @@ -18,26 +18,71 @@ */ #include "gravatarcache.h" -#include #include "gravatar_debug.h" +#include "hash.h" #include +#include #include #include +#include #include + +#include +#include + using namespace Gravatar; Q_GLOBAL_STATIC(GravatarCache, s_gravatarCache) class Gravatar::GravatarCachePrivate { public: - GravatarCachePrivate() + template + inline void insertMissingHash(std::vector &vec, const T &hash) + { + auto it = std::lower_bound(vec.begin(), vec.end(), hash); + if (it != vec.end() && *it == hash) + return; // already present (shouldn't happen) + vec.insert(it, hash); + } + + template + inline void saveVector(const std::vector &vec, const QString &fileName) { + QSaveFile f(mGravatarPath + fileName); + if (!f.open(QFile::WriteOnly)) { + qWarning(GRAVATAR_LOG) << "Can't write missing hashes cache file:" << f.fileName() << f.errorString(); + return; + } + + f.resize(vec.size() * sizeof(T)); + f.write(reinterpret_cast(vec.data()), vec.size() * sizeof(T)); + f.commit(); } - QCache mCachePixmap; + template + inline void loadVector(std::vector &vec, const QString &fileName) + { + if (!vec.empty()) // already loaded + return; + + QFile f(mGravatarPath + fileName); + if (!f.open(QFile::ReadOnly)) + return; // does not exist yet + + if (f.size() % sizeof(T) != 0) { + qWarning(GRAVATAR_LOG) << "Missing hash cache is corrupt:" << f.fileName(); + return; + } + vec.resize(f.size() / sizeof(T)); + f.read(reinterpret_cast(vec.data()), f.size()); + } + + QCache mCachePixmap; QString mGravatarPath; + std::vector mMd5Misses; + std::vector mSha256Misses; }; GravatarCache::GravatarCache() @@ -59,44 +104,75 @@ return s_gravatarCache; } -void GravatarCache::saveGravatarPixmap(const QString &hashStr, const QPixmap &pixmap) +void GravatarCache::saveGravatarPixmap(const Hash &hash, const QPixmap &pixmap) { - if (!hashStr.isEmpty() && !pixmap.isNull()) { - if (!d->mCachePixmap.contains(hashStr)) { - const QString path = d->mGravatarPath + hashStr + QLatin1String(".png"); - qCDebug(GRAVATAR_LOG) << " path " << path; - if (pixmap.save(path)) { - qCDebug(GRAVATAR_LOG) << " saved in cache " << hashStr << path; - d->mCachePixmap.insert(hashStr, new QPixmap(pixmap)); - } - } + if (!hash.isValid() || pixmap.isNull()) + return; + + const QString path = d->mGravatarPath + hash.hexString() + QLatin1String(".png"); + qCDebug(GRAVATAR_LOG) << " path " << path; + if (pixmap.save(path)) { + qCDebug(GRAVATAR_LOG) << " saved in cache " << path; + d->mCachePixmap.insert(hash, new QPixmap(pixmap)); + } +} + +void GravatarCache::saveMissingGravatar(const Hash &hash) +{ + switch (hash.type()) { + case Hash::Invalid: + break; + case Hash::Md5: + d->insertMissingHash(d->mMd5Misses, hash.md5()); + d->saveVector(d->mMd5Misses, QStringLiteral("missing.md5")); + break; + case Hash::Sha256: + d->insertMissingHash(d->mSha256Misses, hash.sha256()); + d->saveVector(d->mSha256Misses, QStringLiteral("missing.sha256")); + break; } } -QPixmap GravatarCache::loadGravatarPixmap(const QString &hashStr, bool &gravatarStored) +QPixmap GravatarCache::loadGravatarPixmap(const Hash &hash, bool &gravatarStored) { gravatarStored = false; - qCDebug(GRAVATAR_LOG) << " hashStr" << hashStr; - if (!hashStr.isEmpty()) { - if (d->mCachePixmap.contains(hashStr)) { - qCDebug(GRAVATAR_LOG) << " contains in cache " << hashStr; + qCDebug(GRAVATAR_LOG) << " hashStr" << hash.hexString(); + if (!hash.isValid()) + return QPixmap(); + + // in-memory cache + if (d->mCachePixmap.contains(hash)) { + qCDebug(GRAVATAR_LOG) << " contains in cache " << hash.hexString(); + gravatarStored = true; + return *(d->mCachePixmap.object(hash)); + } + + // file-system cache + const QString path = d->mGravatarPath + hash.hexString() + QLatin1String(".png"); + if (QFileInfo::exists(path)) { + QPixmap pix; + if (pix.load(path)) { + qCDebug(GRAVATAR_LOG) << " add to cache " << hash.hexString() << path; + d->mCachePixmap.insert(hash, new QPixmap(pix)); gravatarStored = true; - return *(d->mCachePixmap.object(hashStr)); - } else { - const QString path = d->mGravatarPath + hashStr + QLatin1String(".png"); - if (QFileInfo::exists(path)) { - QPixmap pix; - if (pix.load(path)) { - qCDebug(GRAVATAR_LOG) << " add to cache " << hashStr << path; - d->mCachePixmap.insert(hashStr, new QPixmap(pix)); - gravatarStored = true; - return pix; - } - } else { - return QPixmap(); - } + return pix; } } + + // missing gravatar cache (ie. known to not exist one) + switch (hash.type()) { + case Hash::Invalid: + break; + case Hash::Md5: + d->loadVector(d->mMd5Misses, QStringLiteral("missing.md5")); + gravatarStored = std::binary_search(d->mMd5Misses.begin(), d->mMd5Misses.end(), hash.md5()); + break; + case Hash::Sha256: + d->loadVector(d->mSha256Misses, QStringLiteral("missing.sha256")); + gravatarStored = std::binary_search(d->mSha256Misses.begin(), d->mSha256Misses.end(), hash.sha256()); + break; + } + return QPixmap(); } @@ -130,4 +206,6 @@ } } clear(); + d->mMd5Misses.clear(); + d->mSha256Misses.clear(); } diff --git a/src/misc/hash.h b/src/misc/hash.h new file mode 100644 --- /dev/null +++ b/src/misc/hash.h @@ -0,0 +1,82 @@ +/* + Copyright (c) 2017 Volker Krause + + This library 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 library 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 Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef GRAVATAR_HASH_H +#define GRAVATAR_HASH_H + +#include "gravatar_export.h" + +#include +#include + +class QByteArray; +class QString; + +namespace Gravatar { + +template struct UnsignedInt +{ + bool operator<(const UnsignedInt &other) const + { + return memcmp(data, other.data, Size) < 0; + } + + bool operator==(const UnsignedInt &other) const + { + return memcmp(data, other.data, Size) == 0; + } + + uint8_t data[Size]; +}; +struct Hash128 : public UnsignedInt<16> {}; +struct Hash256 : public UnsignedInt<32> {}; + +class Hash; +unsigned int qHash(const Hash &h, unsigned int seed = 0); + +// exported for unit tests only +class GRAVATAR_EXPORT Hash +{ +public: + enum Type { Invalid, Md5, Sha256 }; + Hash(); + explicit Hash(const QByteArray &data, Type type); + + bool operator==(const Hash& other) const; + + bool isValid() const; + + Type type() const; + Hash128 md5() const; + Hash256 sha256() const; + + QString hexString() const; + +private: + friend unsigned int qHash(const Hash &h, unsigned int seed); + union { + Hash128 md5; + Hash256 sha256; + } m_hash; + Type m_type; +}; + +} + +#endif // GRAVATAR_HASH_H diff --git a/src/misc/hash.cpp b/src/misc/hash.cpp new file mode 100644 --- /dev/null +++ b/src/misc/hash.cpp @@ -0,0 +1,108 @@ +/* + Copyright (c) 2017 Volker Krause + + This library 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 library 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 Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "hash.h" + +#include +#include + +using namespace Gravatar; + +Hash::Hash() + : m_type(Invalid) +{ +} + +Hash::Hash(const QByteArray& data, Type type) + : m_type(type) +{ + switch (type) { + case Invalid: + break; + case Md5: + Q_ASSERT(sizeof(Hash128) == data.size()); + m_hash.md5 = *reinterpret_cast(data.constData()); + break; + case Sha256: + Q_ASSERT(sizeof(Hash256) == data.size()); + m_hash.sha256 = *reinterpret_cast(data.constData()); + break; + } +} + +bool Hash::operator==(const Hash& other) const +{ + if (m_type != other.m_type) + return false; + switch (m_type) { + case Invalid: + return true; + case Md5: + return m_hash.md5 == other.m_hash.md5; + case Sha256: + return m_hash.sha256 == other.m_hash.sha256; + } + Q_UNREACHABLE(); +} + +bool Hash::isValid() const +{ + return m_type != Invalid; +} + +Hash::Type Hash::type() const +{ + return m_type; +} + +Hash128 Hash::md5() const +{ + return m_hash.md5; +} + +Hash256 Hash::sha256() const +{ + return m_hash.sha256; +} + +QString Hash::hexString() const +{ + switch (m_type) { + case Invalid: + return QString(); + case Md5: + return QString::fromLatin1(QByteArray::fromRawData(reinterpret_cast(&m_hash.md5), sizeof(Hash128)).toHex()); + case Sha256: + return QString::fromLatin1(QByteArray::fromRawData(reinterpret_cast(&m_hash.sha256), sizeof(Hash256)).toHex()); + } + Q_UNREACHABLE(); +} + +uint Gravatar::qHash(const Gravatar::Hash& h, uint seed) +{ + switch (h.type()) { + case Hash::Invalid: + return seed; + case Hash::Md5: + return qHashBits(&h.m_hash.md5, sizeof(Hash128), seed); + case Hash::Sha256: + return qHashBits(&h.m_hash.sha256, sizeof(Hash256), seed); + } + Q_UNREACHABLE(); +}