mirror of
https://github.com/transmission/transmission.git
synced 2025-12-24 12:28:52 +00:00
* refactor: speed up FaviconCache::add() again. Another iteration on FaviconCache::add() since it's still showing up so high in my perf tests. add()'s url argument is now a QString instead of a QUrl, and the class has a private unordered_map that maps QString urls into Keys. Basically, QUrl generation is so expensive that it's worth caching the result to avoid constructing the intermediate QUrl object. Also, ensure that the network reply's `deleteLater()` method is called so that they don't leak, and add 'svg' to the icon list since it's now supported on all major browsers.
206 lines
4.8 KiB
C++
206 lines
4.8 KiB
C++
/*
|
|
* This file Copyright (C) 2012-2015 Mnemosyne LLC
|
|
*
|
|
* It may be used under the GNU GPL versions 2 or 3
|
|
* or any future license endorsed by Mnemosyne LLC.
|
|
*
|
|
*/
|
|
|
|
#include <QDir>
|
|
#include <QNetworkAccessManager>
|
|
#include <QNetworkReply>
|
|
#include <QNetworkRequest>
|
|
#include <QStandardPaths>
|
|
|
|
#include "FaviconCache.h"
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
FaviconCache::FaviconCache() :
|
|
nam_(new QNetworkAccessManager(this))
|
|
{
|
|
connect(nam_, SIGNAL(finished(QNetworkReply*)), this, SLOT(onRequestFinished(QNetworkReply*)));
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
namespace
|
|
{
|
|
|
|
QPixmap scale(QPixmap pixmap)
|
|
{
|
|
return pixmap.scaled(FaviconCache::getIconSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
}
|
|
|
|
QString getCacheDir()
|
|
{
|
|
auto const base = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
|
return QDir(base).absoluteFilePath(QStringLiteral("favicons"));
|
|
}
|
|
|
|
QString getScrapedFile()
|
|
{
|
|
auto const base = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
|
return QDir(base).absoluteFilePath(QStringLiteral("favicons-scraped.txt"));
|
|
}
|
|
|
|
void markUrlAsScraped(QString const& url_str)
|
|
{
|
|
auto skip_file = QFile(getScrapedFile());
|
|
if (skip_file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append))
|
|
{
|
|
skip_file.write(url_str.toUtf8());
|
|
skip_file.write("\n");
|
|
}
|
|
}
|
|
|
|
} // unnamed namespace
|
|
|
|
void FaviconCache::ensureCacheDirHasBeenScanned()
|
|
{
|
|
static bool has_been_scanned = false;
|
|
if (has_been_scanned)
|
|
{
|
|
return;
|
|
}
|
|
|
|
has_been_scanned = true;
|
|
|
|
// remember which hosts we've asked for a favicon so that we
|
|
// don't re-ask them every time we start a new session
|
|
auto skip_file = QFile(getScrapedFile());
|
|
if (skip_file.open(QIODevice::ReadOnly | QIODevice::Text | QIODevice::ExistingOnly))
|
|
{
|
|
while (!skip_file.atEnd())
|
|
{
|
|
auto const url = QString::fromUtf8(skip_file.readLine()).trimmed();
|
|
auto const key = getKey(QUrl{ url });
|
|
keys_.insert({ url, key });
|
|
pixmaps_.try_emplace(key);
|
|
}
|
|
}
|
|
|
|
// load the cached favicons
|
|
auto cache_dir = QDir(getCacheDir());
|
|
cache_dir.mkpath(cache_dir.absolutePath());
|
|
QStringList const files = cache_dir.entryList(QDir::Files | QDir::Readable);
|
|
for (auto const& file : files)
|
|
{
|
|
QPixmap pixmap(cache_dir.absoluteFilePath(file));
|
|
if (!pixmap.isNull())
|
|
{
|
|
pixmaps_[file] = scale(pixmap);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
QString FaviconCache::getDisplayName(Key const& key)
|
|
{
|
|
auto name = key;
|
|
name[0] = name.at(0).toTitleCase();
|
|
return name;
|
|
}
|
|
|
|
FaviconCache::Key FaviconCache::getKey(QUrl const& url)
|
|
{
|
|
auto host = url.host();
|
|
|
|
// remove tld
|
|
auto const suffix = url.topLevelDomain();
|
|
host.truncate(host.size() - suffix.size());
|
|
|
|
// remove subdomain
|
|
auto const pos = host.indexOf(QLatin1Char('.'));
|
|
return pos < 0 ? host : host.remove(0, pos + 1);
|
|
}
|
|
|
|
FaviconCache::Key FaviconCache::getKey(QString const& displayName)
|
|
{
|
|
return displayName.toLower();
|
|
}
|
|
|
|
QSize FaviconCache::getIconSize()
|
|
{
|
|
return QSize(16, 16);
|
|
}
|
|
|
|
QPixmap FaviconCache::find(Key const& key)
|
|
{
|
|
ensureCacheDirHasBeenScanned();
|
|
|
|
return pixmaps_[key];
|
|
}
|
|
|
|
FaviconCache::Key FaviconCache::add(QString const& url_str)
|
|
{
|
|
ensureCacheDirHasBeenScanned();
|
|
|
|
// find or add this url's key
|
|
auto k_it = keys_.find(url_str);
|
|
if (k_it != keys_.end())
|
|
{
|
|
return k_it->second;
|
|
}
|
|
|
|
auto const url = QUrl { url_str };
|
|
auto const key = getKey(url);
|
|
keys_.insert({ url_str, key });
|
|
|
|
// Try to download a favicon if we don't have one.
|
|
// Add a placeholder to prevent repeat downloads.
|
|
if (pixmaps_.try_emplace(key).second)
|
|
{
|
|
markUrlAsScraped(url_str);
|
|
|
|
auto const path = QStringLiteral("http://%1/favicon.").arg(url.host());
|
|
nam_->get(QNetworkRequest(path + QStringLiteral("gif")));
|
|
nam_->get(QNetworkRequest(path + QStringLiteral("ico")));
|
|
nam_->get(QNetworkRequest(path + QStringLiteral("jpg")));
|
|
nam_->get(QNetworkRequest(path + QStringLiteral("png")));
|
|
nam_->get(QNetworkRequest(path + QStringLiteral("svg")));
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
void FaviconCache::onRequestFinished(QNetworkReply* reply)
|
|
{
|
|
auto const key = getKey(reply->url());
|
|
|
|
QPixmap pixmap;
|
|
|
|
QByteArray const content = reply->readAll();
|
|
|
|
if (reply->error() == QNetworkReply::NoError)
|
|
{
|
|
pixmap.loadFromData(content);
|
|
}
|
|
|
|
if (!pixmap.isNull())
|
|
{
|
|
// save it in memory...
|
|
pixmaps_[key] = scale(pixmap);
|
|
|
|
// save it on disk...
|
|
QDir cache_dir(getCacheDir());
|
|
cache_dir.mkpath(cache_dir.absolutePath());
|
|
QFile file(cache_dir.absoluteFilePath(key));
|
|
file.open(QIODevice::WriteOnly);
|
|
file.write(content);
|
|
file.close();
|
|
|
|
// notify listeners
|
|
emit pixmapReady(key);
|
|
}
|
|
|
|
reply->deleteLater();
|
|
}
|