Files
transmission/qt/FaviconCache.cc
Charles Kerr 1bb9e2eef2 refactor: speed up FaviconCache::add() again. (#1402)
* 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.
2020-08-26 14:00:39 -05:00

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();
}