kio Library API Documentation

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 
KDE Logo
This file is part of the documentation for kdelibs Version 3.1.4.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Sun Feb 27 22:15:33 2005 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001