1 | /* |
2 | This file is part of KIO. |
3 | SPDX-FileCopyrightText: 2001 Malte Starostik <malte@kde.org> |
4 | SPDX-FileCopyrightText: 2016 David Faure <faure@kde.org> |
5 | |
6 | SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL |
7 | */ |
8 | |
9 | #include "faviconscache_p.h" |
10 | |
11 | #include <KConfig> |
12 | #include <KConfigGroup> |
13 | #include <QMutex> |
14 | |
15 | #include <QCache> |
16 | #include <QDir> |
17 | #include <QFile> |
18 | #include <QSet> |
19 | #include <QStandardPaths> |
20 | #include <QUrl> |
21 | |
22 | using namespace KIO; |
23 | |
24 | static QString portForUrl(const QUrl &url) |
25 | { |
26 | if (url.port() > 0) { |
27 | return QLatin1Char('_') + QString::number(url.port()); |
28 | } |
29 | return QString(); |
30 | } |
31 | |
32 | static QString simplifyUrl(const QUrl &url) |
33 | { |
34 | // splat any = in the URL so it can be safely used as a config key |
35 | QString result = url.host() + portForUrl(url) + url.path(); |
36 | result.replace(before: QLatin1Char('='), after: QLatin1Char('_')); |
37 | while (result.endsWith(c: QLatin1Char('/'))) { |
38 | result.chop(n: 1); |
39 | } |
40 | return result; |
41 | } |
42 | |
43 | static QString iconNameFromUrl(const QUrl &iconUrl) |
44 | { |
45 | if (iconUrl.path() == QLatin1String("/favicon.ico" )) { |
46 | return iconUrl.host() + portForUrl(url: iconUrl); |
47 | } |
48 | |
49 | QString result = simplifyUrl(url: iconUrl); |
50 | // splat / so it can be safely used as a file name |
51 | result.replace(before: QLatin1Char('/'), after: QLatin1Char('_')); |
52 | |
53 | const int dotExtLen = 4; |
54 | const QStringView ext = QStringView(result).right(n: dotExtLen); |
55 | if (ext == QLatin1String(".ico" ) || ext == QLatin1String(".png" ) || ext == QLatin1String(".xpm" )) { |
56 | result.chop(n: dotExtLen); |
57 | } |
58 | |
59 | return result; |
60 | } |
61 | |
62 | //// |
63 | |
64 | class KIO::FavIconsCachePrivate |
65 | { |
66 | public: |
67 | FavIconsCachePrivate() |
68 | : cacheDir(QStandardPaths::writableLocation(type: QStandardPaths::GenericCacheLocation) + QStringLiteral("/favicons/" )) |
69 | , config(cacheDir + QStringLiteral("index" )) |
70 | { |
71 | } |
72 | |
73 | QString cachedIconUrlForUrl(const QUrl &url); |
74 | |
75 | const QString cacheDir; |
76 | QMutex mutex; // protects all the member variables below |
77 | KConfig config; |
78 | QCache<QString, QString> faviconsCache; |
79 | QSet<QUrl> failedDownloads; |
80 | }; |
81 | |
82 | QString FavIconsCachePrivate::cachedIconUrlForUrl(const QUrl &url) |
83 | { |
84 | Q_ASSERT(!mutex.tryLock()); |
85 | const QString simplifiedUrl = simplifyUrl(url); |
86 | QString *cachedIconUrl = faviconsCache[simplifiedUrl]; |
87 | return (cachedIconUrl ? *cachedIconUrl : config.group(group: QString()).readEntry(key: simplifiedUrl, aDefault: QString())); |
88 | } |
89 | |
90 | FavIconsCache *FavIconsCache::instance() |
91 | { |
92 | static FavIconsCache s_cache; // remind me why we need Q_GLOBAL_STATIC, again, now that C++11 guarantees thread safety? |
93 | return &s_cache; |
94 | } |
95 | |
96 | FavIconsCache::FavIconsCache() |
97 | : d(new FavIconsCachePrivate) |
98 | { |
99 | } |
100 | |
101 | FavIconsCache::~FavIconsCache() = default; |
102 | |
103 | QString FavIconsCache::iconForUrl(const QUrl &url) |
104 | { |
105 | if (url.host().isEmpty()) { |
106 | return QString(); |
107 | } |
108 | QMutexLocker locker(&d->mutex); |
109 | const QString cachedIconUrl = d->cachedIconUrlForUrl(url); |
110 | QString icon = d->cacheDir; |
111 | if (!cachedIconUrl.isEmpty()) { |
112 | icon += iconNameFromUrl(iconUrl: QUrl(cachedIconUrl)); |
113 | } else { |
114 | icon += url.host(); |
115 | } |
116 | icon += QStringLiteral(".png" ); |
117 | if (QFile::exists(fileName: icon)) { |
118 | return icon; |
119 | } |
120 | return QString(); |
121 | } |
122 | |
123 | QUrl FavIconsCache::iconUrlForUrl(const QUrl &url) |
124 | { |
125 | QMutexLocker locker(&d->mutex); |
126 | const QString cachedIconUrl = d->cachedIconUrlForUrl(url); |
127 | if (!cachedIconUrl.isEmpty()) { |
128 | return QUrl(cachedIconUrl); |
129 | } else { |
130 | QUrl iconUrl; |
131 | iconUrl.setScheme(url.scheme()); |
132 | iconUrl.setHost(host: url.host()); |
133 | iconUrl.setPort(url.port()); |
134 | iconUrl.setPath(QStringLiteral("/favicon.ico" )); |
135 | iconUrl.setUserInfo(userInfo: url.userInfo()); |
136 | return iconUrl; |
137 | } |
138 | } |
139 | |
140 | void FavIconsCache::setIconForUrl(const QUrl &url, const QUrl &iconUrl) |
141 | { |
142 | QMutexLocker locker(&d->mutex); |
143 | const QString simplifiedUrl = simplifyUrl(url); |
144 | const QString iconUrlStr = iconUrl.url(); |
145 | d->faviconsCache.insert(key: simplifiedUrl, object: new QString(iconUrlStr)); |
146 | d->config.group(group: QString()).writeEntry(key: simplifiedUrl, value: iconUrlStr); |
147 | d->config.sync(); |
148 | } |
149 | |
150 | QString FavIconsCache::cachePathForIconUrl(const QUrl &iconUrl) const |
151 | { |
152 | QMutexLocker locker(&d->mutex); |
153 | const QString iconName = iconNameFromUrl(iconUrl); |
154 | return d->cacheDir + iconName + QLatin1String(".png" ); |
155 | } |
156 | |
157 | void FavIconsCache::ensureCacheExists() |
158 | { |
159 | QMutexLocker locker(&d->mutex); |
160 | QDir().mkpath(dirPath: d->cacheDir); |
161 | } |
162 | |
163 | void FavIconsCache::addFailedDownload(const QUrl &url) |
164 | { |
165 | QMutexLocker locker(&d->mutex); |
166 | d->failedDownloads.insert(value: url); |
167 | } |
168 | |
169 | void FavIconsCache::removeFailedDownload(const QUrl &url) |
170 | { |
171 | QMutexLocker locker(&d->mutex); |
172 | d->failedDownloads.remove(value: url); |
173 | } |
174 | |
175 | bool FavIconsCache::isFailedDownload(const QUrl &url) const |
176 | { |
177 | QMutexLocker locker(&d->mutex); |
178 | return d->failedDownloads.contains(value: url); |
179 | } |
180 | |
181 | #include "moc_faviconscache_p.cpp" |
182 | |