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 }