Gravatar

gravatarcache.cpp
1/*
2 SPDX-FileCopyrightText: 2015-2024 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "gravatarcache.h"
8using namespace Qt::Literals::StringLiterals;
9
10#include "gravatar_debug.h"
11#include "hash.h"
12
13#include <QCache>
14#include <QDir>
15#include <QFile>
16#include <QFileInfo>
17#include <QSaveFile>
18#include <QStandardPaths>
19
20#include <algorithm>
21#include <vector>
22
23using namespace Gravatar;
24
25Q_GLOBAL_STATIC(GravatarCache, s_gravatarCache)
26
27class Gravatar::GravatarCachePrivate
28{
29public:
30 template<typename T>
31 inline void insertMissingHash(std::vector<T> &vec, const T &hash)
32 {
33 auto it = std::lower_bound(vec.begin(), vec.end(), hash);
34 if (it != vec.end() && *it == hash) {
35 return; // already present (shouldn't happen)
36 }
37 vec.insert(it, hash);
38 }
39
40 template<typename T>
41 inline void saveVector(const std::vector<T> &vec, const QString &fileName)
42 {
43 QSaveFile f(mGravatarPath + fileName);
44 if (!f.open(QFile::WriteOnly)) {
45 qCWarning(GRAVATAR_LOG) << "Can't write missing hashes cache file:" << f.fileName() << f.errorString();
46 return;
47 }
48
49 f.resize(vec.size() * sizeof(T));
50 f.write(reinterpret_cast<const char *>(vec.data()), vec.size() * sizeof(T));
51 f.commit();
52 }
53
54 template<typename T>
55 inline void loadVector(std::vector<T> &vec, const QString &fileName)
56 {
57 if (!vec.empty()) { // already loaded
58 return;
59 }
60
61 QFile f(mGravatarPath + fileName);
62 if (!f.open(QFile::ReadOnly)) {
63 return; // does not exist yet
64 }
65 if (f.size() % sizeof(T) != 0) {
66 qCWarning(GRAVATAR_LOG) << "Missing hash cache is corrupt:" << f.fileName();
67 return;
68 }
69 vec.resize(f.size() / sizeof(T));
70 f.read(reinterpret_cast<char *>(vec.data()), f.size());
71 }
72
73 QCache<Hash, QPixmap> mCachePixmap;
74 QString mGravatarPath;
75 std::vector<Hash128> mMd5Misses;
76 std::vector<Hash256> mSha256Misses;
77};
78
79GravatarCache::GravatarCache()
80 : d(new Gravatar::GravatarCachePrivate)
81{
82 d->mCachePixmap.setMaxCost(20);
83 // Make sure that this folder is created. Otherwise we can't store gravatar
85 QDir().mkpath(d->mGravatarPath);
86}
87
88GravatarCache::~GravatarCache() = default;
89
90GravatarCache *GravatarCache::self()
91{
92 return s_gravatarCache;
93}
94
95void GravatarCache::saveGravatarPixmap(const Hash &hash, const QPixmap &pixmap)
96{
97 if (!hash.isValid() || pixmap.isNull()) {
98 return;
99 }
100
101 const QString path = d->mGravatarPath + hash.hexString() + ".png"_L1;
102 qCDebug(GRAVATAR_LOG) << " path " << path;
103 if (pixmap.save(path)) {
104 qCDebug(GRAVATAR_LOG) << " saved in cache " << path;
105 d->mCachePixmap.insert(hash, new QPixmap(pixmap));
106 }
107}
108
109void GravatarCache::saveMissingGravatar(const Hash &hash)
110{
111 switch (hash.type()) {
112 case Hash::Invalid:
113 break;
114 case Hash::Md5:
115 d->insertMissingHash(d->mMd5Misses, hash.md5());
116 d->saveVector(d->mMd5Misses, QStringLiteral("missing.md5"));
117 break;
118 case Hash::Sha256:
119 d->insertMissingHash(d->mSha256Misses, hash.sha256());
120 d->saveVector(d->mSha256Misses, QStringLiteral("missing.sha256"));
121 break;
122 }
123}
124
125QPixmap GravatarCache::loadGravatarPixmap(const Hash &hash, bool &gravatarStored)
126{
127 gravatarStored = false;
128 // qCDebug(GRAVATAR_LOG) << " hashStr" << hash.hexString();
129 if (!hash.isValid()) {
130 return {};
131 }
132
133 // in-memory cache
134 if (d->mCachePixmap.contains(hash)) {
135 qCDebug(GRAVATAR_LOG) << " contains in cache " << hash.hexString();
136 gravatarStored = true;
137 return *(d->mCachePixmap.object(hash));
138 }
139
140 // file-system cache
141 const QString path = d->mGravatarPath + hash.hexString() + ".png"_L1;
142 if (QFileInfo::exists(path)) {
143 QPixmap pix;
144 if (pix.load(path)) {
145 qCDebug(GRAVATAR_LOG) << " add to cache " << hash.hexString() << path;
146 d->mCachePixmap.insert(hash, new QPixmap(pix));
147 gravatarStored = true;
148 return pix;
149 }
150 }
151
152 // missing gravatar cache (ie. known to not exist one)
153 switch (hash.type()) {
154 case Hash::Invalid:
155 break;
156 case Hash::Md5:
157 d->loadVector(d->mMd5Misses, QStringLiteral("missing.md5"));
158 gravatarStored = std::binary_search(d->mMd5Misses.begin(), d->mMd5Misses.end(), hash.md5());
159 break;
160 case Hash::Sha256:
161 d->loadVector(d->mSha256Misses, QStringLiteral("missing.sha256"));
162 gravatarStored = std::binary_search(d->mSha256Misses.begin(), d->mSha256Misses.end(), hash.sha256());
163 break;
164 }
165
166 return {};
167}
168
169int GravatarCache::maximumSize() const
170{
171 return d->mCachePixmap.maxCost();
172}
173
174void GravatarCache::setMaximumSize(int maximumSize)
175{
176 if (d->mCachePixmap.maxCost() != maximumSize) {
177 d->mCachePixmap.setMaxCost(maximumSize);
178 }
179}
180
181void GravatarCache::clear()
182{
183 d->mCachePixmap.clear();
184}
185
186void GravatarCache::clearAllCache()
187{
188 const QString path = d->mGravatarPath;
189 if (!path.isEmpty()) {
190 QDir dir(path);
191 if (dir.exists()) {
192 const QFileInfoList list = dir.entryInfoList(); // get list of matching files and delete all
193 for (const QFileInfo &it : list) {
194 dir.remove(it.fileName());
195 }
196 }
197 }
198 clear();
199 d->mMd5Misses.clear();
200 d->mSha256Misses.clear();
201}
Cache for both positive and negative avatar lookups.
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
bool mkpath(const QString &dirPath) const const
bool exists() const const
bool isNull() const const
bool load(const QString &fileName, const char *format, Qt::ImageConversionFlags flags)
bool save(QIODevice *device, const char *format, int quality) const const
QString writableLocation(StandardLocation type)
bool isEmpty() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Apr 27 2024 22:06:15 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.