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