# kio Library API Documentation

Overview | Class Hierarchy | Classes | Classes (annotated) | Members | Source Files

# previewjob.cpp

    00001 // -*- c++ -*-
    00002 // vim: ts=4 sw=4 et
    00003 /*  This file is part of the KDE libraries
    00004     Copyright (C) 2000 David Faure <faure@kde.org>
    00005                   2000 Carsten Pfeiffer <pfeiffer@kde.org>
    00006                   2001 Malte Starostik <malte.starostik@t-online.de>
    00007
    00008     This library is free software; you can redistribute it and/or
    00009     modify it under the terms of the GNU Library General Public
    00010     License as published by the Free Software Foundation; either
    00011     version 2 of the License, or (at your option) any later version.
    00012
    00013     This library is distributed in the hope that it will be useful,
    00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
    00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    00016     Library General Public License for more details.
    00017
    00018     You should have received a copy of the GNU Library General Public License
    00019     along with this library; see the file COPYING.LIB.  If not, write to
    00020     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    00021     Boston, MA 02111-1307, USA.
    00022 */
    00023
    00024 #include <sys/stat.h>
    00025 #ifdef __FreeBSD__
    00026     #include <machine/param.h>
    00027 #endif
    00028 #include <sys/types.h>
    00029 #include <sys/ipc.h>
    00030 #include <sys/shm.h>
    00031
    00032 #include <qdir.h>
    00033 #include <qfile.h>
    00034 #include <qimage.h>
    00035 #include <qtimer.h>
    00036 #include <qregexp.h>
    00037
    00038 #include <kdatastream.h> // Do not remove, needed for correct bool serialization
    00039 #include <kfileitem.h>
    00040 #include <kapplication.h>
    00041 #include <ktempfile.h>
    00042 #include <ktrader.h>
    00043 #include <kmdcodec.h>
    00044 #include <kglobal.h>
    00045 #include <kstandarddirs.h>
    00046
    00047 #include <kio/kservice.h>
    00048
    00049 #include "previewjob.moc"
    00050
    00051 namespace KIO { struct PreviewItem; }
    00052 using namespace KIO;
    00053
    00054 struct KIO::PreviewItem
    00055 {
    00056     KFileItem *item;
    00057     KService::Ptr plugin;
    00058 };
    00059
    00060 struct KIO::PreviewJobPrivate
    00061 {
    00062     enum { STATE_STATORIG, // if the thumbnail exists
    00063            STATE_GETORIG, // if we create it
    00064            STATE_CREATETHUMB // thumbnail:/ slave
    00065     } state;
    00066     KFileItemList initialItems;
    00067     const QStringList *enabledPlugins;
    00068     // Our todo list :)
    00069     QValueList<PreviewItem> items;
    00070     // The current item
    00071     PreviewItem currentItem;
    00072     // The modification time of that URL
    00073     time_t tOrig;
    00074     // Path to thumbnail cache for the current size
    00075     QString thumbPath;
    00076     // Original URL of current item in TMS format
    00077     // (file:///path/to/file instead of file:/path/to/file)
    00078     QString origName;
    00079     // Thumbnail file name for current item
    00080     QString thumbName;
    00081     // Size of thumbnail
    00082     int width;
    00083     int height;
    00084     // Unscaled size of thumbnail (128 or 256 if cache is enabled)
    00085     int cacheWidth;
    00086     int cacheHeight;
    00087     // Whether the thumbnail should be scaled
    00088     bool bScale;
    00089     // Whether we should save the thumbnail
    00090     bool bSave;
    00091     // If the file to create a thumb for was a temp file, this is its name
    00092     QString tempName;
    00093     // Over that, it's too much
    00094     unsigned long maximumSize;
    00095     // the size for the icon overlay
    00096     int iconSize;
    00097     // the transparency of the blended mimetype icon
    00098     int iconAlpha;
    00099         // Shared memory segment Id. The segment is allocated to a size
    00100         // of extent x extent x 4 (32 bit image) on first need.
    00101         int shmid;
    00102         // And the data area
    00103         uchar *shmaddr;
    00104     // Delete the KFileItems when done?
    00105     bool deleteItems;
    00106     bool succeeded;
    00107     // Root of thumbnail cache
    00108     QString thumbRoot;
    00109 };
    00110
    00111 PreviewJob::PreviewJob( const KFileItemList &items, int width, int height,
    00112     int iconSize, int iconAlpha, bool scale, bool save,
    00113     const QStringList *enabledPlugins, bool deleteItems )
    00114     : KIO::Job( false /* no GUI */ )
    00115 {
    00116     d = new PreviewJobPrivate;
    00117     d->tOrig = 0;
    00118     d->shmid = -1;
    00119     d->shmaddr = 0;
    00120     d->initialItems = items;
    00121     d->enabledPlugins = enabledPlugins;
    00122     d->width = width;
    00123     d->height = height ? height : width;
    00124     d->cacheWidth = d->width;
    00125     d->cacheHeight = d->height;
    00126     d->iconSize = iconSize;
    00127     d->iconAlpha = iconAlpha;
    00128     d->deleteItems = deleteItems;
    00129     d->bScale = scale;
    00130     d->bSave = save && scale;
    00131     d->succeeded = false;
    00132     d->currentItem.item = 0;
    00133     d->thumbRoot = QDir::homeDirPath() + "/.thumbnails/";
    00134
    00135     // Return to event loop first, determineNextFile() might delete this;
    00136     QTimer::singleShot(0, this, SLOT(startPreview()));
    00137 }
    00138
    00139 PreviewJob::~PreviewJob()
    00140 {
    00141     if (d->shmaddr) {
    00142         shmdt((char*)d->shmaddr);
    00143         shmctl(d->shmid, IPC_RMID, 0);
    00144     }
    00145     delete d;
    00146 }
    00147
    00148 void PreviewJob::startPreview()
    00149 {
    00150     // Load the list of plugins to determine which mimetypes are supported
    00151     KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
    00152     QMap<QString, KService::Ptr> mimeMap;
    00153
    00154     for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
    00155         if (!d->enabledPlugins || d->enabledPlugins->contains((*it)->desktopEntryName()))
    00156     {
    00157         QStringList mimeTypes = (*it)->property("MimeTypes").toStringList();
    00158         for (QStringList::ConstIterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt)
    00159             mimeMap.insert(*mt, *it);
    00160     }
    00161
    00162     // Look for images and store the items in our todo list :)
    00163     bool bNeedCache = false;
    00164     for (KFileItemListIterator it(d->initialItems); it.current(); ++it )
    00165     {
    00166         PreviewItem item;
    00167         item.item = it.current();
    00168         QMap<QString, KService::Ptr>::ConstIterator plugin = mimeMap.find(it.current()->mimetype());
    00169         if (plugin == mimeMap.end())
    00170         {
    00171             QString mimeType = it.current()->mimetype();
    00172             plugin = mimeMap.find(mimeType.replace(QRegExp("/.*"), "/*"));
    00173         }
    00174         if (plugin != mimeMap.end())
    00175         {
    00176             item.plugin = *plugin;
    00177             d->items.append(item);
    00178             if (!bNeedCache && d->bSave &&
    00179                 (it.current()->url().protocol() != "file" ||
    00180                  !it.current()->url().directory( false ).startsWith(d->thumbRoot)) &&
    00181                 (*plugin)->property("CacheThumbnail").toBool())
    00182                 bNeedCache = true;
    00183         }
    00184         else
    00185         {
    00186             emitFailed(it.current());
    00187             if (d->deleteItems)
    00188                 delete it.current();
    00189         }
    00190     }
    00191
    00192   // Read configuration value for the maximum allowed size
    00193     KConfig * config = KGlobal::config();
    00194     KConfigGroupSaver cgs( config, "PreviewSettings" );
    00195     d->maximumSize = config->readNumEntry( "MaximumSize", 1024*1024 /* 1MB */ );
    00196
    00197     if (bNeedCache)
    00198     {
    00199         if (d->width <= 128 && d->height <= 128) d->cacheWidth = d->cacheHeight = 128;
    00200         else d->cacheWidth = d->cacheHeight = 256;
    00201         d->thumbPath = d->thumbRoot + (d->cacheWidth == 128 ? "normal/" : "large/");
    00202         KStandardDirs::makeDir(d->thumbPath, 0700);
    00203     }
    00204     else
    00205         d->bSave = false;
    00206     determineNextFile();
    00207 }
    00208
    00209 void PreviewJob::removeItem( const KFileItem *item )
    00210 {
    00211     for (QValueList<PreviewItem>::Iterator it = d->items.begin(); it != d->items.end(); ++it)
    00212         if ((*it).item == item)
    00213         {
    00214             d->items.remove(it);
    00215             break;
    00216         }
    00217
    00218     if (d->currentItem.item == item)
    00219     {
    00220         subjobs.first()->kill();
    00221         subjobs.removeFirst();
    00222         determineNextFile();
    00223     }
    00224 }
    00225
    00226 void PreviewJob::determineNextFile()
    00227 {
    00228     if (d->currentItem.item)
    00229     {
    00230         if (!d->succeeded)
    00231             emitFailed();
    00232         if (d->deleteItems)
    00233             delete d->currentItem.item;
    00234     }
    00235     // No more items ?
    00236     if ( d->items.isEmpty() )
    00237     {
    00238         emitResult();
    00239         return;
    00240     }
    00241     else
    00242     {
    00243         // First, stat the orig file
    00244         d->state = PreviewJobPrivate::STATE_STATORIG;
    00245         d->currentItem = d->items.first();
    00246         d->succeeded = false;
    00247         d->items.remove(d->items.begin());
    00248         KIO::Job *job = KIO::stat( d->currentItem.item->url(), false );
    00249         addSubjob(job);
    00250     }
    00251 }
    00252
    00253 void PreviewJob::slotResult( KIO::Job *job )
    00254 {
    00255     subjobs.remove( job );
    00256     Q_ASSERT ( subjobs.isEmpty() ); // We should have only one job at a time ...
    00257     switch ( d->state )
    00258     {
    00259         case PreviewJobPrivate::STATE_STATORIG:
    00260         {
    00261             if (job->error()) // that's no good news...
    00262             {
    00263                 // Drop this one and move on to the next one
    00264                 determineNextFile();
    00265                 return;
    00266             }
    00267             KIO::UDSEntry entry = ((KIO::StatJob*)job)->statResult();
    00268             KIO::UDSEntry::ConstIterator it = entry.begin();
    00269             d->tOrig = 0;
    00270             int found = 0;
    00271             for( ; it != entry.end() && found < 2; it++ )
    00272             {
    00273                 if ( (*it).m_uds == KIO::UDS_MODIFICATION_TIME )
    00274                 {
    00275                     d->tOrig = (time_t)((*it).m_long);
    00276                     found++;
    00277                 }
    00278                 else if ( (*it).m_uds == KIO::UDS_SIZE )
    00279                     {
    00280                     if ( filesize_t((*it).m_long) > d->maximumSize &&
    00281                          !d->currentItem.plugin->property("IgnoreMaximumSize").toBool() )
    00282                     {
    00283                         determineNextFile();
    00284                         return;
    00285                     }
    00286                     found++;
    00287                 }
    00288             }
    00289
    00290             if ( !d->currentItem.plugin->property( "CacheThumbnail" ).toBool() )
    00291             {
    00292                 // This preview will not be cached, no need to look for a saved thumbnail
    00293                 // Just create it, and be done
    00294                 getOrCreateThumbnail();
    00295                 return;
    00296             }
    00297
    00298             if ( statResultThumbnail() )
    00299                 return;
    00300
    00301             getOrCreateThumbnail();
    00302             return;
    00303         }
    00304         case PreviewJobPrivate::STATE_GETORIG:
    00305         {
    00306             if (job->error())
    00307             {
    00308                 determineNextFile();
    00309                 return;
    00310             }
    00311
    00312             createThumbnail( static_cast<KIO::FileCopyJob*>(job)->destURL().path() );
    00313             return;
    00314         }
    00315         case PreviewJobPrivate::STATE_CREATETHUMB:
    00316         {
    00317             if (!d->tempName.isEmpty())
    00318             {
    00319                 QFile::remove(d->tempName);
    00320                 d->tempName = QString::null;
    00321             }
    00322             determineNextFile();
    00323             return;
    00324         }
    00325     }
    00326 }
    00327
    00328 bool PreviewJob::statResultThumbnail()
    00329 {
    00330     if ( d->thumbPath.isEmpty() )
    00331         return false;
    00332
    00333     KURL url = d->currentItem.item->url();
    00334     // Don't include the password if any
    00335     url.setPass(QString::null);
    00336     // The TMS defines local files as file:///path/to/file instead of KDE's
    00337     // way (file:/path/to/file)
    00338     if (url.protocol() == "file") d->origName = "file://" + url.path();
    00339     else d->origName = url.url();
    00340
    00341     KMD5 md5( QFile::encodeName( d->origName ) );
    00342     d->thumbName = QFile::encodeName( md5.hexDigest() ) + ".png";
    00343
    00344     QImage thumb;
    00345     if ( !thumb.load( d->thumbPath + d->thumbName ) ) return false;
    00346
    00347     if ( thumb.text( "Thumb::URI", 0 ) != d->origName ||
    00348          thumb.text( "Thumb::MTime", 0 ).toInt() != d->tOrig ) return false;
    00349
    00350     // Found it, use it
    00351     emitPreview( thumb );
    00352     d->succeeded = true;
    00353     determineNextFile();
    00354     return true;
    00355 }
    00356
    00357
    00358 void PreviewJob::getOrCreateThumbnail()
    00359 {
    00360     // We still need to load the orig file ! (This is getting tedious) :)
    00361     KURL currentURL = d->currentItem.item->url();
    00362     if ( currentURL.isLocalFile() )
    00363         createThumbnail( currentURL.path() );
    00364     else
    00365     {
    00366         d->state = PreviewJobPrivate::STATE_GETORIG;
    00367         KTempFile localFile;
    00368         KURL localURL;
    00369         localURL.setPath( d->tempName = localFile.name() );
    00370         KIO::Job * job = KIO::file_copy( currentURL, localURL, -1, true,
    00371                                          false, false /* No GUI */ );
    00372         job->addMetaData("thumbnail","1");
    00373         addSubjob(job);
    00374     }
    00375 }
    00376
    00377 void PreviewJob::createThumbnail( QString pixPath )
    00378 {
    00379     d->state = PreviewJobPrivate::STATE_CREATETHUMB;
    00380     KURL thumbURL;
    00381     thumbURL.setProtocol("thumbnail");
    00382     thumbURL.setPath(pixPath);
    00383     KIO::TransferJob *job = KIO::get(thumbURL, false, false);
    00384     addSubjob(job);
    00385     connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)), SLOT(slotThumbData(KIO::Job *, const QByteArray &)));
    00386     bool save = d->bSave && d->currentItem.plugin->property("CacheThumbnail").toBool();
    00387     job->addMetaData("mimeType", d->currentItem.item->mimetype());
    00388     job->addMetaData("width", QString().setNum(save ? d->cacheWidth : d->width));
    00389     job->addMetaData("height", QString().setNum(save ? d->cacheHeight : d->height));
    00390     job->addMetaData("iconSize", QString().setNum(save ? 64 : d->iconSize));
    00391     job->addMetaData("iconAlpha", QString().setNum(d->iconAlpha));
    00392     job->addMetaData("plugin", d->currentItem.plugin->library());
    00393     if (d->shmid == -1)
    00394     {
    00395         if (d->shmaddr) {
    00396             shmdt((char*)d->shmaddr);
    00397             shmctl(d->shmid, IPC_RMID, 0);
    00398         }
    00399         d->shmid = shmget(IPC_PRIVATE, d->cacheWidth * d->cacheHeight * 4, IPC_CREAT|0600);
    00400         if (d->shmid != -1)
    00401         {
    00402             d->shmaddr = static_cast<uchar *>(shmat(d->shmid, 0, SHM_RDONLY));
    00403             if (d->shmaddr == (uchar *)-1)
    00404             {
    00405                 shmctl(d->shmid, IPC_RMID, 0);
    00406                 d->shmaddr = 0;
    00407                 d->shmid = -1;
    00408             }
    00409         }
    00410         else
    00411             d->shmaddr = 0;
    00412     }
    00413     if (d->shmid != -1)
    00414         job->addMetaData("shmid", QString().setNum(d->shmid));
    00415 }
    00416
    00417 void PreviewJob::slotThumbData(KIO::Job *, const QByteArray &data)
    00418 {
    00419     bool save = d->bSave &&
    00420                 d->currentItem.plugin->property("CacheThumbnail").toBool() &&
    00421                 (d->currentItem.item->url().protocol() != "file" ||
    00422                  !d->currentItem.item->url().directory( false ).startsWith(d->thumbRoot));
    00423     QImage thumb;
    00424     if (d->shmaddr)
    00425     {
    00426         QDataStream str(data, IO_ReadOnly);
    00427         int width, height, depth;
    00428         bool alpha;
    00429         str >> width >> height >> depth >> alpha;
    00430         thumb = QImage(d->shmaddr, width, height, depth, 0, 0, QImage::IgnoreEndian);
    00431         thumb.setAlphaBuffer(alpha);
    00432     }
    00433     else thumb.loadFromData(data);
    00434     if (save)
    00435     {
    00436         thumb.setText("Thumb::URI", 0, d->origName);
    00437         thumb.setText("Thumb::MTime", 0, QString::number(d->tOrig));
    00438         thumb.setText("Thumb::Size", 0, number(d->currentItem.item->size()));
    00439         thumb.setText("Thumb::Mimetype", 0, d->currentItem.item->mimetype());
    00440         thumb.setText("Software", 0, "KDE Thumbnail Generator");
    00441         KTempFile temp(d->thumbPath + "kde-tmp-", ".png");
    00442         if (temp.status() == 0) //Only try to write out the thumbnail if we
    00443         {                       //actually created the temp file.
    00444             thumb.save(temp.name(), "PNG");
    00445             rename(QFile::encodeName(temp.name()), QFile::encodeName(d->thumbPath + d->thumbName));
    00446         }
    00447     }
    00448     emitPreview( thumb );
    00449     d->succeeded = true;
    00450 }
    00451
    00452 void PreviewJob::emitPreview(const QImage &thumb)
    00453 {
    00454     QPixmap pix;
    00455     if (thumb.width() > d->width || thumb.height() > d->height)
    00456     {
    00457         double imgRatio = (double)thumb.height() / (double)thumb.width();
    00458         if (imgRatio > (double)d->height / (double)d->width)
    00459             pix.convertFromImage(
    00460                 thumb.smoothScale((int)QMAX((double)d->height / imgRatio, 1), d->height));
    00461         else pix.convertFromImage(
    00462             thumb.smoothScale(d->width, (int)QMAX((double)d->width * imgRatio, 1)));
    00463     }
    00464     else pix.convertFromImage(thumb);
    00465     emit gotPreview(d->currentItem.item, pix);
    00466 }
    00467
    00468 void PreviewJob::emitFailed(const KFileItem *item)
    00469 {
    00470     if (!item)
    00471         item = d->currentItem.item;
    00472     emit failed(item);
    00473 }
    00474
    00475 QStringList PreviewJob::availablePlugins()
    00476 {
    00477     QStringList result;
    00478     KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
    00479     for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
    00480         if (!result.contains((*it)->desktopEntryName()))
    00481             result.append((*it)->desktopEntryName());
    00482     return result;
    00483 }
    00484
    00485 QStringList PreviewJob::supportedMimeTypes()
    00486 {
    00487     QStringList result;
    00488     KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
    00489     for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
    00490         result += (*it)->property("MimeTypes").toStringList();
    00491     return result;
    00492 }
    00493
    00494 PreviewJob *KIO::filePreview( const KFileItemList &items, int width, int height,
    00495     int iconSize, int iconAlpha, bool scale, bool save,
    00496     const QStringList *enabledPlugins )
    00497 {
    00498     return new PreviewJob(items, width, height, iconSize, iconAlpha,
    00499                           scale, save, enabledPlugins);
    00500 }
    00501
    00502 PreviewJob *KIO::filePreview( const KURL::List &items, int width, int height,
    00503     int iconSize, int iconAlpha, bool scale, bool save,
    00504     const QStringList *enabledPlugins )
    00505 {
    00506     KFileItemList fileItems;
    00507     for (KURL::List::ConstIterator it = items.begin(); it != items.end(); ++it)
    00508         fileItems.append(new KFileItem(KFileItem::Unknown, KFileItem::Unknown, *it, true));
    00509     return new PreviewJob(fileItems, width, height, iconSize, iconAlpha,
    00510                           scale, save, enabledPlugins, true);
    00511 }
    00512
    00513 void PreviewJob::virtual_hook( int id, void* data )
    00514 { KIO::Job::virtual_hook( id, data ); }
    00515

This file is part of the documentation for kdelibs Version 3.1.5.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Wed Jan 28 13:14:36 2004 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001