• Skip to content
  • Skip to link menu
KDE 3.5 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

kio

previewjob.cpp

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

kio

Skip menu "kio"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • dcop
  • DNSSD
  • interfaces
  • Kate
  • kconf_update
  • KDECore
  • KDED
  • kdefx
  • KDEsu
  • kdeui
  • KDocTools
  • KHTML
  • KImgIO
  • KInit
  • kio
  • kioslave
  • KJS
  • KNewStuff
  • KParts
  • KUtils
Generated for API Reference by doxygen 1.5.9
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal