favicons.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2001 Malte Starostik <malte@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017    Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include <string.h>
00021 #include <time.h>
00022 
00023 #include <qbuffer.h>
00024 #include <qfile.h>
00025 #include <qcache.h>
00026 #include <qimage.h>
00027 #include <qtimer.h>
00028 
00029 #include <kdatastream.h> // DO NOT REMOVE, otherwise bool marshalling breaks
00030 #include <kicontheme.h>
00031 #include <kimageio.h>
00032 #include <ksimpleconfig.h>
00033 #include <kstandarddirs.h>
00034 #include <kio/job.h>
00035 
00036 #include "favicons.moc"
00037 
00038 struct FaviconsModulePrivate
00039 {
00040     virtual ~FaviconsModulePrivate() { delete config; }
00041 
00042     struct DownloadInfo
00043     {
00044         QString hostOrURL;
00045         bool isHost;
00046         QByteArray iconData;
00047     };
00048     QMap<KIO::Job *, DownloadInfo> downloads;
00049     QStringList failedDownloads;
00050     KSimpleConfig *config;
00051     QPtrList<KIO::Job> killJobs;
00052     KIO::MetaData metaData;
00053     QString faviconsDir;
00054     QCache<QString> faviconsCache;
00055 };
00056 
00057 FaviconsModule::FaviconsModule(const QCString &obj)
00058     : KDEDModule(obj)
00059 {
00060     // create our favicons folder so that KIconLoader knows about it
00061     d = new FaviconsModulePrivate;
00062     d->faviconsDir = KGlobal::dirs()->saveLocation( "cache", "favicons/" );
00063     d->faviconsDir.truncate(d->faviconsDir.length()-9); // Strip off "favicons/"
00064     d->metaData.insert("ssl_no_client_cert", "TRUE");
00065     d->metaData.insert("ssl_militant", "TRUE");
00066     d->metaData.insert("UseCache", "false");
00067     d->metaData.insert("cookies", "none");
00068     d->metaData.insert("no-auth", "true");
00069     d->config = new KSimpleConfig(locateLocal("data", "konqueror/faviconrc"));
00070     d->killJobs.setAutoDelete(true);
00071     d->faviconsCache.setAutoDelete(true);
00072 }
00073 
00074 FaviconsModule::~FaviconsModule()
00075 {
00076     delete d;
00077 }
00078 
00079 QString removeSlash(QString result) 
00080 {
00081     for (unsigned int i = result.length() - 1; i > 0; --i)
00082         if (result[i] != '/')
00083         {
00084             result.truncate(i + 1);
00085             break;
00086         }
00087 
00088     return result;
00089 }
00090 
00091 
00092 QString FaviconsModule::iconForURL(const KURL &url)
00093 {
00094     if (url.host().isEmpty())
00095         return QString::null;
00096 
00097     QString icon;
00098     QString simplifiedURL = simplifyURL(url);
00099 
00100     QString *iconURL = d->faviconsCache.find( removeSlash(simplifiedURL) );
00101     if (iconURL)
00102         icon = *iconURL;
00103     else
00104         icon = d->config->readEntry( removeSlash(simplifiedURL) );
00105 
00106     if (!icon.isEmpty())
00107         icon = iconNameFromURL(KURL( icon ));
00108     else 
00109         icon = url.host();
00110         
00111     icon = "favicons/" + icon;
00112 
00113     if (QFile::exists(d->faviconsDir+icon+".png"))
00114         return icon;
00115 
00116     return QString::null;
00117 }
00118 
00119 QString FaviconsModule::simplifyURL(const KURL &url)
00120 {
00121     // splat any = in the URL so it can be safely used as a config key
00122     QString result = url.host() + url.path();
00123     for (unsigned int i = 0; i < result.length(); ++i)
00124         if (result[i] == '=')
00125             result[i] = '_';
00126     return result;
00127 }
00128 
00129 QString FaviconsModule::iconNameFromURL(const KURL &iconURL)
00130 {
00131     if (iconURL.path() == "/favicon.ico")
00132        return iconURL.host();
00133 
00134     QString result = simplifyURL(iconURL);
00135     // splat / so it can be safely used as a file name
00136     for (unsigned int i = 0; i < result.length(); ++i)
00137         if (result[i] == '/')
00138             result[i] = '_';
00139 
00140     QString ext = result.right(4);
00141     if (ext == ".ico" || ext == ".png" || ext == ".xpm")
00142         result.remove(result.length() - 4, 4);
00143 
00144     return result;
00145 }
00146 
00147 bool FaviconsModule::isIconOld(const QString &icon)
00148 {
00149     struct stat st;
00150     if (stat(QFile::encodeName(icon), &st) != 0)
00151         return true; // Trigger a new download on error
00152 
00153     return (time(0) - st.st_mtime) > 604800; // arbitrary value (one week)
00154 }
00155 
00156 void FaviconsModule::setIconForURL(const KURL &url, const KURL &iconURL)
00157 {
00158     QString simplifiedURL = simplifyURL(url);
00159 
00160     d->faviconsCache.insert(removeSlash(simplifiedURL), new QString(iconURL.url()) );
00161 
00162     QString iconName = "favicons/" + iconNameFromURL(iconURL);
00163     QString iconFile = d->faviconsDir + iconName + ".png";
00164 
00165     if (!isIconOld(iconFile)) {
00166         emit iconChanged(false, simplifiedURL, iconName);
00167         return;
00168     }
00169 
00170     startDownload(simplifiedURL, false, iconURL);
00171 }
00172 
00173 void FaviconsModule::downloadHostIcon(const KURL &url)
00174 {
00175     QString iconFile = d->faviconsDir + "favicons/" + url.host() + ".png";
00176     if (!isIconOld(iconFile))
00177         return;
00178 
00179     startDownload(url.host(), true, KURL(url, "/favicon.ico"));
00180 }
00181 
00182 void FaviconsModule::startDownload(const QString &hostOrURL, bool isHost, const KURL &iconURL)
00183 {
00184     if (d->failedDownloads.contains(iconURL.url()))
00185         return;
00186 
00187     KIO::Job *job = KIO::get(iconURL, false, false);
00188     job->addMetaData(d->metaData);
00189     connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)), SLOT(slotData(KIO::Job *, const QByteArray &)));
00190     connect(job, SIGNAL(result(KIO::Job *)), SLOT(slotResult(KIO::Job *)));
00191     connect(job, SIGNAL(infoMessage(KIO::Job *, const QString &)), SLOT(slotInfoMessage(KIO::Job *, const QString &)));
00192     FaviconsModulePrivate::DownloadInfo download;
00193     download.hostOrURL = hostOrURL;
00194     download.isHost = isHost;
00195     d->downloads.insert(job, download);
00196 }
00197 
00198 void FaviconsModule::slotData(KIO::Job *job, const QByteArray &data)
00199 {
00200     FaviconsModulePrivate::DownloadInfo &download = d->downloads[job];
00201     unsigned int oldSize = download.iconData.size();
00202     if (oldSize > 0x10000)
00203     {
00204         d->killJobs.append(job);
00205         QTimer::singleShot(0, this, SLOT(slotKill()));
00206     }
00207     download.iconData.resize(oldSize + data.size());
00208     memcpy(download.iconData.data() + oldSize, data.data(), data.size());
00209 }
00210 
00211 void FaviconsModule::slotResult(KIO::Job *job)
00212 {
00213     FaviconsModulePrivate::DownloadInfo download = d->downloads[job];
00214     d->downloads.remove(job);
00215     KURL iconURL = static_cast<KIO::TransferJob *>(job)->url();
00216     QString iconName;
00217     if (!job->error())
00218     {
00219         QBuffer buffer(download.iconData);
00220         buffer.open(IO_ReadOnly);
00221         QImageIO io;
00222         io.setIODevice(&buffer);
00223         io.setParameters("size=16");
00224         // Check here too, the job might have had no error, but the downloaded
00225         // file contains just a 404 message sent with a 200 status.
00226         // microsoft.com does that... (malte)
00227         if (io.read())
00228         {
00229             // Some sites have nasty 32x32 icons, according to the MS docs
00230             // IE ignores them, well, we scale them, otherwise the location
00231             // combo / menu will look quite ugly
00232             if (io.image().width() != KIcon::SizeSmall || io.image().height() != KIcon::SizeSmall)
00233                 io.setImage(io.image().smoothScale(KIcon::SizeSmall, KIcon::SizeSmall));
00234 
00235             if (download.isHost)
00236                 iconName = download.hostOrURL;
00237             else
00238                 iconName = iconNameFromURL(iconURL);
00239 
00240             iconName = "favicons/" + iconName;
00241 
00242             io.setIODevice(0);
00243             io.setFileName(d->faviconsDir + iconName + ".png");
00244             io.setFormat("PNG");
00245             if (!io.write())
00246                 iconName = QString::null;
00247             else if (!download.isHost)
00248                 d->config->writeEntry( removeSlash(download.hostOrURL), iconURL.url());
00249         }
00250     }
00251     if (iconName.isEmpty())
00252         d->failedDownloads.append(iconURL.url());
00253 
00254     emit iconChanged(download.isHost, download.hostOrURL, iconName);
00255 }
00256 
00257 void FaviconsModule::slotInfoMessage(KIO::Job *job, const QString &msg)
00258 {
00259     emit infoMessage(static_cast<KIO::TransferJob *>( job )->url(), msg);
00260 }
00261 
00262 void FaviconsModule::slotKill()
00263 {
00264     d->killJobs.clear();
00265 }
00266 
00267 extern "C" {
00268     KDE_EXPORT KDEDModule *create_favicons(const QCString &obj)
00269     {
00270         KImageIO::registerFormats();
00271         return new FaviconsModule(obj);
00272     }
00273 }
00274 
00275 // vim: ts=4 sw=4 et
KDE Home | KDE Accessibility Home | Description of Access Keys