File indexing completed on 2024-11-24 04:49:03

0001 /*
0002    SPDX-FileCopyrightText: 2015-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "gravatarcache.h"
0008 #include "gravatar_debug.h"
0009 #include "hash.h"
0010 
0011 #include <QCache>
0012 #include <QDir>
0013 #include <QFile>
0014 #include <QFileInfo>
0015 #include <QSaveFile>
0016 #include <QStandardPaths>
0017 
0018 #include <algorithm>
0019 #include <vector>
0020 
0021 using namespace Gravatar;
0022 
0023 Q_GLOBAL_STATIC(GravatarCache, s_gravatarCache)
0024 
0025 class Gravatar::GravatarCachePrivate
0026 {
0027 public:
0028     template<typename T>
0029     inline void insertMissingHash(std::vector<T> &vec, const T &hash)
0030     {
0031         auto it = std::lower_bound(vec.begin(), vec.end(), hash);
0032         if (it != vec.end() && *it == hash) {
0033             return; // already present (shouldn't happen)
0034         }
0035         vec.insert(it, hash);
0036     }
0037 
0038     template<typename T>
0039     inline void saveVector(const std::vector<T> &vec, const QString &fileName)
0040     {
0041         QSaveFile f(mGravatarPath + fileName);
0042         if (!f.open(QFile::WriteOnly)) {
0043             qCWarning(GRAVATAR_LOG) << "Can't write missing hashes cache file:" << f.fileName() << f.errorString();
0044             return;
0045         }
0046 
0047         f.resize(vec.size() * sizeof(T));
0048         f.write(reinterpret_cast<const char *>(vec.data()), vec.size() * sizeof(T));
0049         f.commit();
0050     }
0051 
0052     template<typename T>
0053     inline void loadVector(std::vector<T> &vec, const QString &fileName)
0054     {
0055         if (!vec.empty()) { // already loaded
0056             return;
0057         }
0058 
0059         QFile f(mGravatarPath + fileName);
0060         if (!f.open(QFile::ReadOnly)) {
0061             return; // does not exist yet
0062         }
0063         if (f.size() % sizeof(T) != 0) {
0064             qCWarning(GRAVATAR_LOG) << "Missing hash cache is corrupt:" << f.fileName();
0065             return;
0066         }
0067         vec.resize(f.size() / sizeof(T));
0068         f.read(reinterpret_cast<char *>(vec.data()), f.size());
0069     }
0070 
0071     QCache<Hash, QPixmap> mCachePixmap;
0072     QString mGravatarPath;
0073     std::vector<Hash128> mMd5Misses;
0074     std::vector<Hash256> mSha256Misses;
0075 };
0076 
0077 GravatarCache::GravatarCache()
0078     : d(new Gravatar::GravatarCachePrivate)
0079 {
0080     d->mCachePixmap.setMaxCost(20);
0081     // Make sure that this folder is created. Otherwise we can't store gravatar
0082     d->mGravatarPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1StringView("/gravatar/");
0083     QDir().mkpath(d->mGravatarPath);
0084 }
0085 
0086 GravatarCache::~GravatarCache() = default;
0087 
0088 GravatarCache *GravatarCache::self()
0089 {
0090     return s_gravatarCache;
0091 }
0092 
0093 void GravatarCache::saveGravatarPixmap(const Hash &hash, const QPixmap &pixmap)
0094 {
0095     if (!hash.isValid() || pixmap.isNull()) {
0096         return;
0097     }
0098 
0099     const QString path = d->mGravatarPath + hash.hexString() + QLatin1StringView(".png");
0100     qCDebug(GRAVATAR_LOG) << " path " << path;
0101     if (pixmap.save(path)) {
0102         qCDebug(GRAVATAR_LOG) << " saved in cache " << path;
0103         d->mCachePixmap.insert(hash, new QPixmap(pixmap));
0104     }
0105 }
0106 
0107 void GravatarCache::saveMissingGravatar(const Hash &hash)
0108 {
0109     switch (hash.type()) {
0110     case Hash::Invalid:
0111         break;
0112     case Hash::Md5:
0113         d->insertMissingHash(d->mMd5Misses, hash.md5());
0114         d->saveVector(d->mMd5Misses, QStringLiteral("missing.md5"));
0115         break;
0116     case Hash::Sha256:
0117         d->insertMissingHash(d->mSha256Misses, hash.sha256());
0118         d->saveVector(d->mSha256Misses, QStringLiteral("missing.sha256"));
0119         break;
0120     }
0121 }
0122 
0123 QPixmap GravatarCache::loadGravatarPixmap(const Hash &hash, bool &gravatarStored)
0124 {
0125     gravatarStored = false;
0126     // qCDebug(GRAVATAR_LOG) << " hashStr" << hash.hexString();
0127     if (!hash.isValid()) {
0128         return {};
0129     }
0130 
0131     // in-memory cache
0132     if (d->mCachePixmap.contains(hash)) {
0133         qCDebug(GRAVATAR_LOG) << " contains in cache " << hash.hexString();
0134         gravatarStored = true;
0135         return *(d->mCachePixmap.object(hash));
0136     }
0137 
0138     // file-system cache
0139     const QString path = d->mGravatarPath + hash.hexString() + QLatin1StringView(".png");
0140     if (QFileInfo::exists(path)) {
0141         QPixmap pix;
0142         if (pix.load(path)) {
0143             qCDebug(GRAVATAR_LOG) << " add to cache " << hash.hexString() << path;
0144             d->mCachePixmap.insert(hash, new QPixmap(pix));
0145             gravatarStored = true;
0146             return pix;
0147         }
0148     }
0149 
0150     // missing gravatar cache (ie. known to not exist one)
0151     switch (hash.type()) {
0152     case Hash::Invalid:
0153         break;
0154     case Hash::Md5:
0155         d->loadVector(d->mMd5Misses, QStringLiteral("missing.md5"));
0156         gravatarStored = std::binary_search(d->mMd5Misses.begin(), d->mMd5Misses.end(), hash.md5());
0157         break;
0158     case Hash::Sha256:
0159         d->loadVector(d->mSha256Misses, QStringLiteral("missing.sha256"));
0160         gravatarStored = std::binary_search(d->mSha256Misses.begin(), d->mSha256Misses.end(), hash.sha256());
0161         break;
0162     }
0163 
0164     return {};
0165 }
0166 
0167 int GravatarCache::maximumSize() const
0168 {
0169     return d->mCachePixmap.maxCost();
0170 }
0171 
0172 void GravatarCache::setMaximumSize(int maximumSize)
0173 {
0174     if (d->mCachePixmap.maxCost() != maximumSize) {
0175         d->mCachePixmap.setMaxCost(maximumSize);
0176     }
0177 }
0178 
0179 void GravatarCache::clear()
0180 {
0181     d->mCachePixmap.clear();
0182 }
0183 
0184 void GravatarCache::clearAllCache()
0185 {
0186     const QString path = d->mGravatarPath;
0187     if (!path.isEmpty()) {
0188         QDir dir(path);
0189         if (dir.exists()) {
0190             const QFileInfoList list = dir.entryInfoList(); // get list of matching files and delete all
0191             for (const QFileInfo &it : list) {
0192                 dir.remove(it.fileName());
0193             }
0194         }
0195     }
0196     clear();
0197     d->mMd5Misses.clear();
0198     d->mSha256Misses.clear();
0199 }