mirror of
https://bitbucket.org/smil3y/kde-workspace.git
synced 2025-02-23 10:22:49 +00:00
libs: favicons KDED module rewrite
saves a lot of I/O and network trafic if there are multiple bookmarks to the same host and multiple queries are made (which essentially will end up being many jobs for the same icon). also implemented fallback to alternative http://foo.bar/favicon.png and added tests for more cases, bonus points? Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
This commit is contained in:
parent
1cbda72fad
commit
d811882f97
9 changed files with 175 additions and 435 deletions
|
@ -50,10 +50,10 @@ void FavIconsItr::setStatus(const QString & status)
|
|||
model()->emitDataChanged(currentBookmark());
|
||||
}
|
||||
|
||||
void FavIconsItr::slotDone(bool succeeded, const QString& errorString)
|
||||
void FavIconsItr::slotDone(bool succeeded)
|
||||
{
|
||||
// kDebug() << "FavIconsItr::slotDone()";
|
||||
setStatus(succeeded ? i18n("OK") : errorString);
|
||||
setStatus(succeeded ? i18n("OK") : i18n("Download failed"));
|
||||
holder()->addAffectedBookmark(KBookmark::parentAddress(currentBookmark().address()));
|
||||
delayedEmitNextOne();
|
||||
}
|
||||
|
@ -72,8 +72,8 @@ void FavIconsItr::doAction()
|
|||
setStatus(i18n("Updating favicon..."));
|
||||
if (!m_updater) {
|
||||
m_updater = new FavIconUpdater(this);
|
||||
connect(m_updater, SIGNAL(done(bool,QString)),
|
||||
this, SLOT(slotDone(bool,QString)) );
|
||||
connect(m_updater, SIGNAL(done(bool)),
|
||||
this, SLOT(slotDone(bool)) );
|
||||
}
|
||||
m_updater->downloadIcon(currentBookmark());
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ public:
|
|||
virtual void cancel();
|
||||
|
||||
public Q_SLOTS:
|
||||
void slotDone(bool succeeded, const QString& errorString);
|
||||
void slotDone(bool succeeded);
|
||||
|
||||
protected:
|
||||
virtual void doAction();
|
||||
|
|
|
@ -38,10 +38,10 @@ FavIconUpdater::FavIconUpdater(QObject *parent)
|
|||
: QObject(parent),
|
||||
m_favIconModule("org.kde.kded", "/modules/favicons", QDBusConnection::sessionBus())
|
||||
{
|
||||
connect(&m_favIconModule, SIGNAL(iconChanged(bool,QString,QString)),
|
||||
this, SLOT(notifyChange(bool,QString,QString)) );
|
||||
connect(&m_favIconModule, SIGNAL(error(bool,QString,QString)),
|
||||
this, SLOT(slotFavIconError(bool,QString,QString)) );
|
||||
connect(
|
||||
&m_favIconModule, SIGNAL(iconChanged(QString,QString)),
|
||||
this, SLOT(notifyChange(QString,QString))
|
||||
);
|
||||
}
|
||||
|
||||
void FavIconUpdater::downloadIcon(const KBookmark &bk)
|
||||
|
@ -54,46 +54,26 @@ void FavIconUpdater::downloadIcon(const KBookmark &bk)
|
|||
m_bk.setIcon(favicon);
|
||||
KEBApp::self()->notifyCommandExecuted();
|
||||
// kDebug() << "emit done(true)";
|
||||
emit done(true, QString());
|
||||
emit done(true);
|
||||
|
||||
} else {
|
||||
kDebug() << "no favicon found";
|
||||
m_favIconModule.forceDownloadHostIcon(url);
|
||||
m_favIconModule.forceDownloadUrlIcon(url);
|
||||
}
|
||||
}
|
||||
|
||||
FavIconUpdater::~FavIconUpdater()
|
||||
void FavIconUpdater::notifyChange(const QString &url, const QString &iconName)
|
||||
{
|
||||
}
|
||||
|
||||
bool FavIconUpdater::isFavIconSignalRelevant(bool isHost, const QString& hostOrURL) const
|
||||
{
|
||||
// Is this signal interesting to us? (Don't react on an unrelated favicon)
|
||||
return (isHost && hostOrURL == m_bk.url().host()) ||
|
||||
(!isHost && hostOrURL == m_bk.url().url()); // should we use the api that ignores trailing slashes?
|
||||
}
|
||||
|
||||
void FavIconUpdater::notifyChange(bool isHost,
|
||||
const QString& hostOrURL,
|
||||
const QString& iconName)
|
||||
{
|
||||
kDebug() << hostOrURL << iconName;
|
||||
if (isFavIconSignalRelevant(isHost, hostOrURL)) {
|
||||
if (iconName.isEmpty()) { // old version of the kded module could emit with an empty iconName on error
|
||||
slotFavIconError(isHost, hostOrURL, QString());
|
||||
kDebug() << url << iconName;
|
||||
if (m_bk.url().url() == url) {
|
||||
// kded module emits empty iconName on error
|
||||
if (iconName.isEmpty()) {
|
||||
emit done(false);
|
||||
} else {
|
||||
m_bk.setIcon(iconName);
|
||||
emit done(true, QString());
|
||||
emit done(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FavIconUpdater::slotFavIconError(bool isHost, const QString& hostOrURL, const QString& errorString)
|
||||
{
|
||||
kDebug() << hostOrURL << errorString;
|
||||
if (isFavIconSignalRelevant(isHost, hostOrURL)) {
|
||||
emit done(false, errorString);
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_faviconupdater.cpp"
|
||||
|
|
|
@ -29,18 +29,13 @@ class FavIconUpdater : public QObject
|
|||
|
||||
public:
|
||||
FavIconUpdater(QObject *parent);
|
||||
~FavIconUpdater();
|
||||
void downloadIcon(const KBookmark &bk);
|
||||
|
||||
private Q_SLOTS:
|
||||
void notifyChange(bool isHost, const QString& hostOrURL, const QString& iconName);
|
||||
void slotFavIconError(bool isHost, const QString& hostOrURL, const QString& errorString);
|
||||
void notifyChange(const QString &url, const QString& iconName);
|
||||
|
||||
Q_SIGNALS:
|
||||
void done(bool succeeded, const QString& error);
|
||||
|
||||
private:
|
||||
bool isFavIconSignalRelevant(bool isHost, const QString& hostOrURL) const;
|
||||
void done(bool succeeded);
|
||||
|
||||
private:
|
||||
KBookmark m_bk;
|
||||
|
|
|
@ -42,115 +42,54 @@
|
|||
K_PLUGIN_FACTORY(FavIconsFactory, registerPlugin<FavIconsModule>();)
|
||||
K_EXPORT_PLUGIN(FavIconsFactory("favicons"))
|
||||
|
||||
static QString portForUrl(const KUrl& url)
|
||||
static bool isIconOld(const QString &iconFile)
|
||||
{
|
||||
if (url.port() > 0) {
|
||||
return (QString(QLatin1Char('_')) + QString::number(url.port()));
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
static QString simplifyURL(const KUrl &url)
|
||||
{
|
||||
// splat any = in the URL so it can be safely used as a config key
|
||||
QString result = url.host() + portForUrl(url) + url.path();
|
||||
for (int i = 0; i < result.length(); ++i) {
|
||||
if (result[i] == QLatin1Char('=')) {
|
||||
result[i] = QLatin1Char('_');
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static QString iconNameFromURL(const KUrl &iconURL)
|
||||
{
|
||||
if (iconURL.path() == QLatin1String("/favicon.ico")) {
|
||||
return iconURL.host() + portForUrl(iconURL);
|
||||
}
|
||||
|
||||
QString result = simplifyURL(iconURL);
|
||||
// splat / so it can be safely used as a file name
|
||||
for (int i = 0; i < result.length(); ++i) {
|
||||
if (result[i] == QLatin1Char('/')) {
|
||||
result[i] = QLatin1Char('_');
|
||||
}
|
||||
}
|
||||
|
||||
QString ext = result.right(4);
|
||||
if (ext == QLatin1String(".ico") || ext == QLatin1String(".png") || ext == QLatin1String(".xpm")) {
|
||||
result.remove(result.length() - 4, 4);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static QString removeSlash(QString result)
|
||||
{
|
||||
for (unsigned int i = result.length() - 1; i > 0; --i) {
|
||||
if (result[i] != QLatin1Char('/')) {
|
||||
result.truncate(i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static QString faviconsCacheDir()
|
||||
{
|
||||
QString faviconsDir = KGlobal::dirs()->saveLocation("cache", QLatin1String("favicons/"));
|
||||
faviconsDir.truncate(faviconsDir.length() - 9); // Strip off "favicons/"
|
||||
return faviconsDir;
|
||||
}
|
||||
|
||||
static bool isIconOld(const QString &icon)
|
||||
{
|
||||
const QFileInfo iconInfo(icon);
|
||||
const QFileInfo iconInfo(iconFile);
|
||||
const QDateTime iconLastModified = iconInfo.lastModified();
|
||||
if (!iconInfo.isFile() || !iconLastModified.isValid()) {
|
||||
// kDebug() << "isIconOld" << icon << "yes, no such file";
|
||||
// kDebug() << "isIconOld" << iconFile << "yes, no such file";
|
||||
return true; // Trigger a new download on error
|
||||
}
|
||||
|
||||
// kDebug() << "isIconOld" << icon << "?";
|
||||
// kDebug() << "isIconOld" << iconFile << "?";
|
||||
const QDateTime currentTime = QDateTime::currentDateTime();
|
||||
return ((currentTime.toTime_t() - iconLastModified.toTime_t()) > 604800); // arbitrary value (one week)
|
||||
}
|
||||
|
||||
struct FavIconsDownloadInfo
|
||||
static QString iconNameFromURL(const QString &url)
|
||||
{
|
||||
QString hostOrURL;
|
||||
bool isHost;
|
||||
QByteArray iconData;
|
||||
};
|
||||
QT_BEGIN_NAMESPACE
|
||||
Q_DECLARE_TYPEINFO(FavIconsDownloadInfo, Q_PRIMITIVE_TYPE);
|
||||
QT_END_NAMESPACE
|
||||
|
||||
static QString makeIconName(const FavIconsDownloadInfo& download, const KUrl& iconURL)
|
||||
{
|
||||
QString iconName = QString::fromLatin1("favicons/");
|
||||
iconName += (download.isHost ? download.hostOrURL : iconNameFromURL(iconURL));
|
||||
return iconName;
|
||||
return QString::fromLatin1("favicons/%1").arg(KUrl(url).host());
|
||||
}
|
||||
|
||||
struct FavIconsModulePrivate
|
||||
static QString iconFilePath(const QString &iconName)
|
||||
{
|
||||
FavIconsModulePrivate() : config(nullptr) { }
|
||||
~FavIconsModulePrivate() { delete config; }
|
||||
return QString::fromLatin1("%1/%2.png").arg(KGlobal::dirs()->saveLocation("cache"), iconName);
|
||||
}
|
||||
|
||||
QMap<KIO::TransferJob*, FavIconsDownloadInfo> downloads;
|
||||
static QString faviconFromUrl(const QString &url, const QLatin1String &extension)
|
||||
{
|
||||
KUrl faviconUrl(url);
|
||||
faviconUrl.setPath(QString::fromLatin1("/favicon.%1").arg(extension));
|
||||
faviconUrl.setEncodedQuery(QByteArray());
|
||||
return faviconUrl.url();
|
||||
}
|
||||
|
||||
class FavIconsModulePrivate
|
||||
{
|
||||
public:
|
||||
KUrl::List queuedDownloads;
|
||||
KUrl::List failedDownloads;
|
||||
KConfig *config;
|
||||
KIO::MetaData metaData;
|
||||
};
|
||||
|
||||
FavIconsModule::FavIconsModule(QObject* parent, const QList<QVariant>&)
|
||||
: KDEDModule(parent)
|
||||
FavIconsModule::FavIconsModule(QObject* parent, const QList<QVariant> &args)
|
||||
: KDEDModule(parent),
|
||||
d(new FavIconsModulePrivate())
|
||||
{
|
||||
d = new FavIconsModulePrivate;
|
||||
Q_UNUSED(args);
|
||||
|
||||
d->metaData.insert(QLatin1String("cache"), "reload");
|
||||
d->metaData.insert(QLatin1String("no-www-auth"), QLatin1String("true"));
|
||||
d->config = new KConfig(KStandardDirs::locateLocal("data", QLatin1String("konqueror/faviconrc")));
|
||||
|
||||
new FavIconsAdaptor(this);
|
||||
}
|
||||
|
@ -160,162 +99,135 @@ FavIconsModule::~FavIconsModule()
|
|||
delete d;
|
||||
}
|
||||
|
||||
QString FavIconsModule::iconForUrl(const KUrl &url)
|
||||
QString FavIconsModule::iconForUrl(const QString &url)
|
||||
{
|
||||
if (url.host().isEmpty()) {
|
||||
if (url.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
// kDebug() << url;
|
||||
|
||||
const QString simplifiedURL = removeSlash(simplifyURL(url));
|
||||
QString icon = d->config->group(QString()).readEntry(simplifiedURL, QString());
|
||||
|
||||
if (!icon.isEmpty()) {
|
||||
icon = iconNameFromURL(KUrl(icon));
|
||||
} else {
|
||||
icon = url.host();
|
||||
}
|
||||
|
||||
icon = QLatin1String("favicons/") + icon;
|
||||
|
||||
kDebug() << "URL:" << url << "ICON:" << icon;
|
||||
|
||||
if (QFile::exists(faviconsCacheDir() + icon + QLatin1String(".png"))) {
|
||||
return icon;
|
||||
const QString iconName = iconNameFromURL(url);
|
||||
const QString iconFile = iconFilePath(iconName);
|
||||
if (QFile::exists(iconFile)) {
|
||||
kDebug() << "URL" << url << "icon" << iconName;
|
||||
return iconName;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void FavIconsModule::setIconForUrl(const KUrl &url, const KUrl &iconURL)
|
||||
void FavIconsModule::downloadUrlIcon(const QString &url)
|
||||
{
|
||||
// kDebug() << url << iconURL;
|
||||
const QString iconName = QLatin1String("favicons/") + iconNameFromURL(iconURL);
|
||||
const QString iconFile = faviconsCacheDir() + iconName + QLatin1String(".png");
|
||||
|
||||
const QString iconName = iconNameFromURL(url);
|
||||
const QString iconFile = iconFilePath(iconName);
|
||||
if (!isIconOld(iconFile)) {
|
||||
// kDebug() << "emit iconChanged" << false << url << iconName;
|
||||
emit iconChanged(false, url.url(), iconName);
|
||||
kDebug() << "Icon for URL already downloaded" << url;
|
||||
emit iconChanged(url, iconName);
|
||||
return;
|
||||
}
|
||||
|
||||
startDownload(url.url(), false, iconURL);
|
||||
startDownload(url, iconFile);
|
||||
}
|
||||
|
||||
void FavIconsModule::downloadHostIcon(const KUrl &url)
|
||||
void FavIconsModule::forceDownloadUrlIcon(const QString &url)
|
||||
{
|
||||
// kDebug() << url;
|
||||
const QString iconFile = faviconsCacheDir() + QLatin1String("favicons/") + url.host() + QLatin1String(".png");
|
||||
if (!isIconOld(iconFile)) {
|
||||
// kDebug() << "not old -> doing nothing";
|
||||
d->failedDownloads.removeAll(url); // force a download to happen
|
||||
const QString iconName = iconNameFromURL(url);
|
||||
const QString iconFile = iconFilePath(iconName);
|
||||
QFile::remove(iconFile);
|
||||
downloadUrlIcon(url);
|
||||
}
|
||||
|
||||
void FavIconsModule::startDownload(const QString &url, const QString &iconFile)
|
||||
{
|
||||
const QString iconName = iconNameFromURL(url);
|
||||
if (d->queuedDownloads.contains(iconName)) {
|
||||
kDebug() << "Icon download queued for" << url;
|
||||
return;
|
||||
}
|
||||
startDownload(url.host(), true, KUrl(url, QLatin1String("/favicon.ico")));
|
||||
}
|
||||
|
||||
void FavIconsModule::forceDownloadHostIcon(const KUrl &url)
|
||||
{
|
||||
// kDebug() << url;
|
||||
KUrl iconURL = KUrl(url, QLatin1String("/favicon.ico"));
|
||||
d->failedDownloads.removeAll(iconURL); // force a download to happen
|
||||
startDownload(url.host(), true, iconURL);
|
||||
}
|
||||
|
||||
void FavIconsModule::startDownload(const QString &hostOrURL, bool isHost, const KUrl &iconURL)
|
||||
{
|
||||
if (d->failedDownloads.contains(iconURL)) {
|
||||
// kDebug() << iconURL << "already in failedDownloads, emitting error";
|
||||
emit error(isHost, hostOrURL, i18n("No favicon found"));
|
||||
if (d->failedDownloads.contains(url)) {
|
||||
kDebug() << "Icon download already failed for" << url;
|
||||
emit iconChanged(url, QString());
|
||||
return;
|
||||
}
|
||||
|
||||
// kDebug() << iconURL;
|
||||
FavIconsDownloadInfo download;
|
||||
download.hostOrURL = hostOrURL;
|
||||
download.isHost = isHost;
|
||||
KIO::TransferJob *tjob = KIO::get(iconURL, KIO::NoReload, KIO::HideProgressInfo);
|
||||
d->queuedDownloads.append(iconName);
|
||||
const QString faviconUrl = faviconFromUrl(url, QLatin1String("ico"));
|
||||
startJob(url, faviconUrl, iconFile);
|
||||
}
|
||||
|
||||
void FavIconsModule::startJob(const QString &url, const QString &faviconUrl, const QString &iconFile)
|
||||
{
|
||||
kDebug() << "Downloading" << faviconUrl << "as" << iconFile;
|
||||
KIO::StoredTransferJob *tjob = KIO::storedGet(faviconUrl, KIO::NoReload, KIO::HideProgressInfo);
|
||||
tjob->setAutoDelete(false);
|
||||
tjob->addMetaData(d->metaData);
|
||||
d->downloads.insert(tjob, download);
|
||||
connect(tjob, SIGNAL(infoMessage(KJob*,QString,QString)), SLOT(slotInfoMessage(KJob*,QString)));
|
||||
connect(tjob, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotData(KIO::Job*,QByteArray)));
|
||||
connect(tjob, SIGNAL(result(KJob*)), SLOT(slotResult(KJob*)));
|
||||
tjob->setProperty("faviconsUrl", url);
|
||||
tjob->setProperty("faviconsFile", iconFile);
|
||||
connect(tjob, SIGNAL(finished(KJob*)), SLOT(slotFinished(KJob*)));
|
||||
tjob->start();
|
||||
}
|
||||
|
||||
void FavIconsModule::slotData(KIO::Job *job, const QByteArray &data)
|
||||
void FavIconsModule::slotFinished(KJob *kjob)
|
||||
{
|
||||
KIO::TransferJob* tjob = qobject_cast<KIO::TransferJob*>(job);
|
||||
FavIconsDownloadInfo &download = d->downloads[tjob];
|
||||
const int oldSize = download.iconData.size();
|
||||
// Size limit. Stop downloading if the file is huge.
|
||||
// Testcase (as of june 2008, at least): http://planet-soc.com/favicon.ico, 136K and strange format.
|
||||
if (oldSize > 500000) {
|
||||
kWarning() << "Favicon too big, aborting download of" << tjob->url();
|
||||
const KUrl iconURL = tjob->url();
|
||||
d->failedDownloads.append(iconURL);
|
||||
d->downloads.remove(tjob);
|
||||
tjob->kill();
|
||||
KIO::StoredTransferJob* tjob = qobject_cast<KIO::StoredTransferJob*>(kjob);
|
||||
const QString faviconsUrl = tjob->property("faviconsUrl").toString();
|
||||
const QString faviconsFile = tjob->property("faviconsFile").toString();
|
||||
if (tjob->error()) {
|
||||
const QString tjoburl = tjob->url().url();
|
||||
if (tjoburl.endsWith(QLatin1String(".ico"))) {
|
||||
const QString faviconUrl = faviconFromUrl(faviconsUrl, QLatin1String("png"));
|
||||
kDebug() << "Attempting alternative icon" << faviconUrl;
|
||||
tjob->deleteLater();
|
||||
startJob(faviconsUrl, faviconUrl, faviconsFile);
|
||||
return;
|
||||
}
|
||||
kWarning() << "Job error" << tjob->errorString();
|
||||
downloadError(faviconsUrl);
|
||||
tjob->deleteLater();
|
||||
return;
|
||||
}
|
||||
download.iconData.append(data);
|
||||
}
|
||||
|
||||
void FavIconsModule::slotResult(KJob *job)
|
||||
{
|
||||
KIO::TransferJob* tjob = qobject_cast<KIO::TransferJob*>(job);
|
||||
FavIconsDownloadInfo download = d->downloads[tjob];
|
||||
d->downloads.remove(tjob);
|
||||
const KUrl iconURL = tjob->url();
|
||||
QString iconName;
|
||||
QString errorMessage;
|
||||
if (!tjob->error()) {
|
||||
QBuffer buffer(&download.iconData);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
QImageReader ir(&buffer);
|
||||
if (ir.canRead()) {
|
||||
const QImage img = ir.read();
|
||||
if (!img.isNull()) {
|
||||
iconName = makeIconName(download, iconURL);
|
||||
const QString localPath = faviconsCacheDir() + iconName + QLatin1String(".png");
|
||||
if (!img.save(localPath, "PNG")) {
|
||||
iconName.clear();
|
||||
errorMessage = i18n("Error saving image to %1", localPath);
|
||||
kWarning() << "Error saving image to" << localPath;
|
||||
} else {
|
||||
if (!download.isHost) {
|
||||
d->config->group(QString()).writeEntry(removeSlash(download.hostOrURL), iconURL.url());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errorMessage = i18n("Image reader returned null image");
|
||||
kWarning() << "Image reader returned null image" << ir.errorString();
|
||||
}
|
||||
} else {
|
||||
errorMessage = i18n("Image reader cannot read the data");
|
||||
kWarning() << "Image reader cannot read the data" << ir.errorString();
|
||||
}
|
||||
} else {
|
||||
errorMessage = tjob->errorString();
|
||||
kWarning() << "Job error" << tjob->errorString();
|
||||
QBuffer buffer;
|
||||
buffer.setData(tjob->data());
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
QImageReader ir(&buffer);
|
||||
if (!ir.canRead()) {
|
||||
kWarning() << "Image reader cannot read the data" << ir.errorString();
|
||||
downloadError(faviconsUrl);
|
||||
tjob->deleteLater();
|
||||
return;
|
||||
}
|
||||
const QImage img = ir.read();
|
||||
tjob->deleteLater();
|
||||
if (iconName.isEmpty()) {
|
||||
// kDebug() << "adding" << iconURL << "to failed downloads";
|
||||
d->failedDownloads.append(iconURL);
|
||||
emit error(download.isHost, download.hostOrURL, errorMessage);
|
||||
} else {
|
||||
// kDebug() << "emit iconChanged" << download.isHost << download.hostOrURL << iconName;
|
||||
emit iconChanged(download.isHost, download.hostOrURL, iconName);
|
||||
if (img.isNull()) {
|
||||
kWarning() << "Image reader returned null image" << ir.errorString();
|
||||
downloadError(faviconsUrl);
|
||||
return;
|
||||
}
|
||||
if (!img.save(faviconsFile, "PNG")) {
|
||||
kWarning() << "Error saving image to" << faviconsFile;
|
||||
downloadError(faviconsUrl);
|
||||
return;
|
||||
}
|
||||
downloadSuccess(faviconsUrl);
|
||||
}
|
||||
|
||||
void FavIconsModule::slotInfoMessage(KJob *job, const QString &msg)
|
||||
void FavIconsModule::downloadSuccess(const QString &url)
|
||||
{
|
||||
KIO::TransferJob* tjob = qobject_cast<KIO::TransferJob*>(job);
|
||||
emit infoMessage(tjob->url().url(), msg);
|
||||
kDebug() << "Downloaded icon for" << url;
|
||||
const QString iconName = iconNameFromURL(url);
|
||||
d->queuedDownloads.removeAll(iconName);
|
||||
emit iconChanged(url, iconName);
|
||||
}
|
||||
|
||||
void FavIconsModule::downloadError(const QString &url)
|
||||
{
|
||||
if (!d->failedDownloads.contains(url)) {
|
||||
kDebug() << "Adding" << url << "to failed downloads";
|
||||
d->failedDownloads.append(url);
|
||||
}
|
||||
const QString iconName = iconNameFromURL(url);
|
||||
d->queuedDownloads.removeAll(iconName);
|
||||
emit iconChanged(url, QString());
|
||||
}
|
||||
|
||||
#include "moc_favicons.cpp"
|
||||
|
|
|
@ -19,28 +19,15 @@
|
|||
#ifndef _FAVICONS_H_
|
||||
#define _FAVICONS_H_
|
||||
|
||||
#include <QString>
|
||||
#include <kdedmodule.h>
|
||||
#include <kurl.h>
|
||||
|
||||
class KJob;
|
||||
namespace KIO { class Job; }
|
||||
#include <kjob.h>
|
||||
|
||||
/**
|
||||
* KDED Module to handle shortcut icons ("favicons")
|
||||
* FavIconsModule implements a KDED Module that handles the association of
|
||||
* URLs and hosts with shortcut icons and the icons' downloads in a central
|
||||
* place.
|
||||
* URLs with shortcut icons and downloads such on demand.
|
||||
*
|
||||
* After a successful download, the D-Bus signal iconChanged() is emitted.
|
||||
* It has the signature void iconChanged(bool, QString, QString);
|
||||
* The first parameter is true if the icon is a "host" icon, that is it is
|
||||
* the default icon for all URLs on the given host. In this case, the
|
||||
* second parameter is a host name, otherwise the second parameter is the
|
||||
* URL which is associated with the icon. The third parameter is the
|
||||
* @ref KIconLoader friendly name of the downloaded icon, the same as
|
||||
* @ref iconForUrl will from now on return for any matching URL.
|
||||
*
|
||||
* @short KDED Module for favicons
|
||||
* @author Malte Starostik <malte@kde.org>
|
||||
*/
|
||||
class FavIconsModule : public KDEDModule
|
||||
|
@ -49,10 +36,10 @@ class FavIconsModule : public KDEDModule
|
|||
Q_CLASSINFO("D-Bus Interface", "org.kde.FavIcon")
|
||||
|
||||
public:
|
||||
FavIconsModule(QObject* parent, const QList<QVariant>&);
|
||||
virtual ~FavIconsModule();
|
||||
FavIconsModule(QObject* parent, const QList<QVariant> &args);
|
||||
~FavIconsModule();
|
||||
|
||||
public Q_SLOTS: // dbus methods, called by the adaptor
|
||||
public Q_SLOTS:
|
||||
/**
|
||||
* Looks up an icon name for a given URL. This function does not
|
||||
* initiate any download. If no icon for the URL or its host has
|
||||
|
@ -62,18 +49,8 @@ public Q_SLOTS: // dbus methods, called by the adaptor
|
|||
* @return the icon name suitable to pass to @ref KIconLoader or
|
||||
* QString() if no icon for this URL was found.
|
||||
*/
|
||||
QString iconForUrl(const KUrl &url);
|
||||
QString iconForUrl(const QString &url);
|
||||
|
||||
/**
|
||||
* Associates an icon with the given URL. If the icon was not
|
||||
* downloaded before or the downloaded was too long ago, a
|
||||
* download attempt will be started and the iconChanged() D-Bus
|
||||
* signal is emitted after the download finished successfully.
|
||||
*
|
||||
* @param url the URL which will be associated with the icon
|
||||
* @param iconURL the URL of the icon to be downloaded
|
||||
*/
|
||||
void setIconForUrl(const KUrl &url, const KUrl &iconURL);
|
||||
/**
|
||||
* Downloads the icon for a given host if it was not downloaded before
|
||||
* or the download was too long ago. If the download finishes
|
||||
|
@ -81,7 +58,7 @@ public Q_SLOTS: // dbus methods, called by the adaptor
|
|||
*
|
||||
* @param url any URL on the host for which the icon is to be downloaded
|
||||
*/
|
||||
void downloadHostIcon(const KUrl &url);
|
||||
void downloadUrlIcon(const QString &url);
|
||||
|
||||
/**
|
||||
* Downloads the icon for a given host, even if we tried very recently.
|
||||
|
@ -92,37 +69,25 @@ public Q_SLOTS: // dbus methods, called by the adaptor
|
|||
*
|
||||
* @param url any URL on the host for which the icon is to be downloaded
|
||||
*/
|
||||
void forceDownloadHostIcon(const KUrl &url);
|
||||
void forceDownloadUrlIcon(const QString &url);
|
||||
|
||||
signals: // D-Bus signals
|
||||
Q_SIGNALS: // D-Bus signals
|
||||
/**
|
||||
* Emitted once a new icon is available, for a host or url
|
||||
*/
|
||||
void iconChanged(bool isHost, QString hostOrURL, QString iconName);
|
||||
/**
|
||||
* Progress info while downloading an icon
|
||||
*/
|
||||
void infoMessage(QString iconURL, QString msg);
|
||||
/**
|
||||
* Emitted if an error occurred while downloading the icon for the given host or url.
|
||||
* You can usually ignore this (e.g. web browsers don't need to do anything if
|
||||
* no favicon was found), but this signal can be useful in some cases, e.g.
|
||||
* to let keditbookmarks know that it should move on to the next bookmark.
|
||||
*/
|
||||
void error(bool isHost, QString hostOrURL, QString errorString);
|
||||
|
||||
private:
|
||||
void startDownload(const QString &, bool, const KUrl &);
|
||||
void iconChanged(QString url, QString iconName);
|
||||
|
||||
private Q_SLOTS:
|
||||
void slotData(KIO::Job *, const QByteArray &);
|
||||
void slotResult(KJob *);
|
||||
void slotInfoMessage(KJob *, const QString &);
|
||||
void slotFinished(KJob *kjob);
|
||||
|
||||
private:
|
||||
struct FavIconsModulePrivate *d;
|
||||
void startDownload(const QString &url, const QString &iconFile);
|
||||
void startJob(const QString &url, const QString &faviconUrl, const QString &iconFile);
|
||||
void downloadSuccess(const QString &url);
|
||||
void downloadError(const QString &url);
|
||||
|
||||
private:
|
||||
class FavIconsModulePrivate *d;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim: ts=4 sw=4 et
|
||||
#endif // _FAVICONS_H_
|
||||
|
|
|
@ -2,31 +2,17 @@
|
|||
<node>
|
||||
<interface name="org.kde.FavIcon">
|
||||
<signal name="iconChanged">
|
||||
<arg name="isHost" type="b" direction="out"/>
|
||||
<arg name="hostOrUrl" type="s" direction="out"/>
|
||||
<arg name="iconName" type="s" direction="out"/>
|
||||
</signal>
|
||||
<signal name="infoMessage">
|
||||
<arg name="iconUrl" type="s" direction="out"/>
|
||||
<arg name="msg" type="s" direction="out"/>
|
||||
</signal>
|
||||
<signal name="error">
|
||||
<arg name="isHost" type="b" direction="out"/>
|
||||
<arg name="hostOrUrl" type="s" direction="out"/>
|
||||
<arg name="url" type="s" direction="out"/>
|
||||
<arg name="iconName" type="s" direction="out"/>
|
||||
</signal>
|
||||
<method name="iconForUrl">
|
||||
<arg type="s" direction="out"/>
|
||||
<arg name="url" type="s" direction="in"/>
|
||||
</method>
|
||||
<method name="setIconForUrl">
|
||||
<arg name="url" type="s" direction="in"/>
|
||||
<arg name="iconUrl" type="s" direction="in"/>
|
||||
</method>
|
||||
<method name="downloadHostIcon">
|
||||
<method name="downloadUrlIcon">
|
||||
<arg name="url" type="s" direction="in"/>
|
||||
</method>
|
||||
<method name="forceDownloadHostIcon">
|
||||
<method name="forceDownloadUrlIcon">
|
||||
<arg name="url" type="s" direction="in"/>
|
||||
</method>
|
||||
</interface>
|
||||
|
|
|
@ -89,20 +89,11 @@ static void cleanCache()
|
|||
FavIconTest::FavIconTest()
|
||||
: QObject(),
|
||||
m_iconChanged(false),
|
||||
m_isHost(false),
|
||||
m_favIconModule("org.kde.kded", "/modules/favicons", QDBusConnection::sessionBus())
|
||||
{
|
||||
connect(
|
||||
&m_favIconModule, SIGNAL(iconChanged(bool,QString,QString)),
|
||||
this, SLOT(slotIconChanged(bool,QString,QString))
|
||||
);
|
||||
connect(
|
||||
&m_favIconModule, SIGNAL(infoMessage(QString,QString)),
|
||||
this, SLOT(slotInfoMessage(QString,QString))
|
||||
);
|
||||
connect(
|
||||
&m_favIconModule, SIGNAL(error(bool,QString,QString)),
|
||||
this, SLOT(slotError(bool,QString,QString))
|
||||
&m_favIconModule, SIGNAL(iconChanged(QString,QString)),
|
||||
this, SLOT(slotIconChanged(QString,QString))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -110,79 +101,6 @@ void FavIconTest::initTestCase()
|
|||
{
|
||||
}
|
||||
|
||||
void FavIconTest::testSetIconForURL_data()
|
||||
{
|
||||
QTest::addColumn<QString>("url");
|
||||
QTest::addColumn<QString>("icon");
|
||||
QTest::addColumn<QString>("result");
|
||||
|
||||
QTest::newRow("https://www.google.com")
|
||||
<< QString::fromLatin1("https://www.google.com") << QString::fromLatin1("https://www.google.com/favicon.ico")
|
||||
<< QString::fromLatin1("favicons/www.google.com");
|
||||
QTest::newRow("https://www.ibm.com")
|
||||
<< QString::fromLatin1("https://www.ibm.com") << QString::fromLatin1("https://www.ibm.com/favicon.ico")
|
||||
<< QString::fromLatin1("favicons/www.ibm.com");
|
||||
QTest::newRow("https://github.com/")
|
||||
<< QString::fromLatin1("https://github.com/") << QString::fromLatin1("https://github.com/favicon.ico")
|
||||
<< QString::fromLatin1("favicons/github.com");
|
||||
// lb-140-82-121-3-fra.github.com if not address
|
||||
QTest::newRow("https://140.82.121.3/")
|
||||
<< QString::fromLatin1("https://140.82.121.3/") << QString::fromLatin1("https://140.82.121.3/favicon.ico")
|
||||
<< QString::fromLatin1("favicons/lb-140-82-121-3-fra.github.com");
|
||||
}
|
||||
|
||||
void FavIconTest::testSetIconForURL()
|
||||
{
|
||||
QFETCH(QString, url);
|
||||
QFETCH(QString, icon);
|
||||
QFETCH(QString, result);
|
||||
|
||||
if (!checkICOReadable()) {
|
||||
QSKIP("ico not readable", SkipAll);
|
||||
}
|
||||
|
||||
if (!checkNetworkAccess()) {
|
||||
QSKIP("no network access", SkipAll);
|
||||
}
|
||||
|
||||
cleanCache();
|
||||
|
||||
#if USE_EVENT_LOOP
|
||||
QEventLoop eventLoop;
|
||||
connect(&m_favIconModule, SIGNAL(iconChanged(bool,QString,QString)), &eventLoop, SLOT(quit()));
|
||||
QSignalSpy spy(&m_favIconModule, SIGNAL(iconChanged(bool,QString,QString)));
|
||||
QVERIFY(spy.isValid());
|
||||
QCOMPARE(spy.count(), 0);
|
||||
m_favIconModule.setIconForUrl(url, icon);
|
||||
qDebug("called setIconForUrl, waiting");
|
||||
if (spy.count() < 1) {
|
||||
QTimer::singleShot(s_waitTime, &eventLoop, SLOT(quit()));
|
||||
eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
|
||||
QCOMPARE(spy.count(), 1);
|
||||
QCOMPARE(spy[0][0].toBool(), false);
|
||||
QCOMPARE(spy[0][1].toString(), url);
|
||||
QCOMPARE(spy[0][2].toString(), result);
|
||||
#else
|
||||
m_iconChanged = false;
|
||||
m_favIconModule.setIconForUrl(url, icon);
|
||||
qDebug("called setIconForUrl, waiting");
|
||||
QElapsedTimer elapsedTimer;
|
||||
elapsedTimer.start();
|
||||
while (!m_iconChanged && elapsedTimer.elapsed() < s_waitTime) {
|
||||
QTest::qWait(400);
|
||||
}
|
||||
QVERIFY(m_iconChanged);
|
||||
if (m_isHost) {
|
||||
QCOMPARE(m_hostOrURL, KUrl(url).host());
|
||||
} else {
|
||||
QCOMPARE(m_hostOrURL, url);
|
||||
}
|
||||
QCOMPARE(m_iconName, result);
|
||||
#endif
|
||||
}
|
||||
|
||||
void FavIconTest::testIconForURL_data()
|
||||
{
|
||||
QTest::addColumn<QString>("url");
|
||||
|
@ -190,10 +108,10 @@ void FavIconTest::testIconForURL_data()
|
|||
|
||||
QTest::newRow("https://www.google.com")
|
||||
<< QString::fromLatin1("https://www.google.com") << QString::fromLatin1("favicons/www.google.com");
|
||||
QTest::newRow("https://www.ibm.com")
|
||||
QTest::newRow("https://www.ibm.com/foo?bar=baz")
|
||||
<< QString::fromLatin1("https://www.ibm.com") << QString::fromLatin1("favicons/www.ibm.com");
|
||||
QTest::newRow("https://github.com/")
|
||||
<< QString::fromLatin1("https://github.com/") << QString::fromLatin1("favicons/github.com");
|
||||
QTest::newRow("https://www.wpoven.com/") // NOTE: favicon.png
|
||||
<< QString::fromLatin1("https://www.wpoven.com/") << QString::fromLatin1("favicons/www.wpoven.com");
|
||||
QTest::newRow("https://140.82.121.3/")
|
||||
<< QString::fromLatin1("https://140.82.121.3/") << QString::fromLatin1("favicons/140.82.121.3");
|
||||
}
|
||||
|
@ -219,11 +137,11 @@ void FavIconTest::testIconForURL()
|
|||
|
||||
#if USE_EVENT_LOOP
|
||||
QEventLoop eventLoop;
|
||||
connect(&m_favIconModule, SIGNAL(iconChanged(bool,QString,QString)), &eventLoop, SLOT(quit()));
|
||||
QSignalSpy spy(&m_favIconModule, SIGNAL(iconChanged(bool,QString,QString)));
|
||||
connect(&m_favIconModule, SIGNAL(iconChanged(QString,QString)), &eventLoop, SLOT(quit()));
|
||||
QSignalSpy spy(&m_favIconModule, SIGNAL(iconChanged(QString,QString)));
|
||||
QVERIFY(spy.isValid());
|
||||
QCOMPARE(spy.count(), 0);
|
||||
m_favIconModule.downloadHostIcon(url);
|
||||
m_favIconModule.downloadUrlIcon(url);
|
||||
qDebug("called downloadHostIcon, waiting");
|
||||
if (spy.count() < 1) {
|
||||
QTimer::singleShot(s_waitTime, &eventLoop, SLOT(quit()));
|
||||
|
@ -232,7 +150,7 @@ void FavIconTest::testIconForURL()
|
|||
QCOMPARE(spy.count(), 1);
|
||||
#else
|
||||
m_iconChanged = false;
|
||||
m_favIconModule.downloadHostIcon(url);
|
||||
m_favIconModule.downloadUrlIcon(url);
|
||||
qDebug("called downloadHostIcon, waiting");
|
||||
QElapsedTimer elapsedTimer;
|
||||
elapsedTimer.start();
|
||||
|
@ -246,23 +164,12 @@ void FavIconTest::testIconForURL()
|
|||
QCOMPARE(favicon, icon);
|
||||
}
|
||||
|
||||
void FavIconTest::slotIconChanged(const bool isHost, const QString &hostOrURL, const QString &iconName)
|
||||
void FavIconTest::slotIconChanged(const QString &url, const QString &iconName)
|
||||
{
|
||||
qDebug() << isHost << hostOrURL << iconName;
|
||||
qDebug() << url << iconName;
|
||||
m_iconChanged = true;
|
||||
m_isHost = isHost;
|
||||
m_hostOrURL = hostOrURL;
|
||||
m_url = url;
|
||||
m_iconName = iconName;
|
||||
}
|
||||
|
||||
void FavIconTest::slotInfoMessage(const QString &iconURL, const QString &msg)
|
||||
{
|
||||
qDebug() << iconURL << msg;
|
||||
}
|
||||
|
||||
void FavIconTest::slotError(const bool isHost, const QString &hostOrURL, const QString &errorString)
|
||||
{
|
||||
qWarning() << isHost << hostOrURL << errorString;
|
||||
}
|
||||
|
||||
#include "moc_favicontest.cpp"
|
||||
|
|
|
@ -31,19 +31,14 @@ public:
|
|||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void testSetIconForURL_data();
|
||||
void testSetIconForURL();
|
||||
void testIconForURL_data();
|
||||
void testIconForURL();
|
||||
|
||||
void slotIconChanged(const bool isHost, const QString &hostOrURL, const QString &iconName);
|
||||
void slotInfoMessage(const QString &iconURL, const QString &msg);
|
||||
void slotError(const bool isHost, const QString &hostOrURL, const QString &errorString);
|
||||
void slotIconChanged(const QString &url, const QString &iconName);
|
||||
|
||||
private:
|
||||
bool m_iconChanged;
|
||||
bool m_isHost;
|
||||
QString m_hostOrURL;
|
||||
QString m_url;
|
||||
QString m_iconName;
|
||||
|
||||
org::kde::FavIcon m_favIconModule;
|
||||
|
|
Loading…
Add table
Reference in a new issue