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 <kdebug.h>
00042 #include <ktempfile.h>
00043 #include <ktrader.h>
00044 #include <kmdcodec.h>
00045 #include <kglobal.h>
00046 #include <kstandarddirs.h>
00047 
00048 #include <kio/kservice.h>
00049 
00050 #include "previewjob.moc"
00051 
00052 namespace KIO { struct PreviewItem; }
00053 using namespace KIO;
00054 
00055 struct KIO::PreviewItem
00056 {
00057     KFileItem *item;
00058     KService::Ptr plugin;
00059 };
00060 
00061 struct KIO::PreviewJobPrivate
00062 {
00063     enum { STATE_STATORIG, // if the thumbnail exists
00064            STATE_GETORIG, // if we create it
00065            STATE_CREATETHUMB // thumbnail:/ slave
00066     } state;
00067     KFileItemList initialItems;
00068     const QStringList *enabledPlugins;
00069     // Our todo list :)
00070     QValueList<PreviewItem> items;
00071     // The current item
00072     PreviewItem currentItem;
00073     // The modification time of that URL
00074     time_t tOrig;
00075     // Path to thumbnail cache for this directory
00076     QString thumbPath;
00077     // Size of thumbnail
00078     int width;
00079     int height;
00080     // named thumbnail size for caching
00081     QString sizeName;
00082     // Whether the thumbnail should be scaled
00083     bool bScale;
00084     // Whether we should save the thumbnail
00085     bool bSave;
00086     // If the file to create a thumb for was a temp file, this is its name
00087     QString tempName;
00088     // Over that, it's too much
00089     unsigned long maximumSize;
00090     // the size for the icon overlay
00091     int iconSize;
00092     // the transparency of the blended mimetype icon
00093     int iconAlpha;
00094     // Shared memory segment Id. The segment is allocated to a size
00095     // of extent x extent x 4 (32 bit image) on first need.
00096     int shmid;
00097     // And the data area
00098     uchar *shmaddr;
00099     // Delete the KFileItems when done?
00100     bool deleteItems;
00101     bool succeeded;
00102 };
00103 
00104 PreviewJob::PreviewJob( const KFileItemList &items, int width, int height,
00105     int iconSize, int iconAlpha, bool scale, bool save,
00106     const QStringList *enabledPlugins, bool deleteItems )
00107     : KIO::Job( false /* no GUI */ )
00108 {
00109     d = new PreviewJobPrivate;
00110     d->tOrig = 0;
00111     d->shmid = -1;
00112     d->shmaddr = 0;
00113     d->initialItems = items;
00114     d->enabledPlugins = enabledPlugins;
00115     d->width = width;
00116     d->height = height ? height : width;
00117     d->iconSize = iconSize;
00118     d->iconAlpha = iconAlpha;
00119     d->deleteItems = deleteItems;
00120     d->bScale = scale;
00121     d->bSave = save && scale;
00122     d->succeeded = false;
00123     d->currentItem.item = 0;
00124 
00125     // Return to event loop first, determineNextFile() might delete this;
00126     QTimer::singleShot(0, this, SLOT(startPreview()));
00127 }
00128 
00129 PreviewJob::~PreviewJob()
00130 {
00131     if (d->shmaddr) {
00132         shmdt((char*)d->shmaddr);
00133         shmctl(d->shmid, IPC_RMID, 0);
00134     }
00135     delete d;
00136 }
00137 
00138 void PreviewJob::startPreview()
00139 {
00140     // Load the list of plugins to determine which mimetypes are supported
00141     KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
00142     QMap<QString, KService::Ptr> mimeMap;
00143 
00144     for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00145         if (!d->enabledPlugins || d->enabledPlugins->contains((*it)->desktopEntryName()))
00146     {
00147         QStringList mimeTypes = (*it)->property("MimeTypes").toStringList();
00148         for (QStringList::ConstIterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt)
00149             mimeMap.insert(*mt, *it);
00150     }
00151 
00152     // Look for images and store the items in our todo list :)
00153     bool bNeedCache = false;
00154     for (KFileItemListIterator it(d->initialItems); it.current(); ++it )
00155     {
00156         PreviewItem item;
00157         item.item = it.current();
00158         QMap<QString, KService::Ptr>::ConstIterator plugin = mimeMap.find(it.current()->mimetype());
00159         if (plugin == mimeMap.end())
00160         {
00161             QString mimeType = it.current()->mimetype();
00162             plugin = mimeMap.find(mimeType.replace(QRegExp("/.*"), "/*"));
00163         }
00164         if (plugin != mimeMap.end())
00165         {
00166             item.plugin = *plugin;
00167             d->items.append(item);
00168             if (d->bSave && !bNeedCache && (*plugin)->property("CacheThumbnail").toBool())
00169                 bNeedCache = true;
00170         }
00171         else
00172         {
00173             emitFailed(it.current());
00174             if (d->deleteItems)
00175                 delete it.current();
00176         }
00177     }
00178 
00179   // Read configuration value for the maximum allowed size
00180     KConfig * config = KGlobal::config();
00181     KConfigGroupSaver cgs( config, "PreviewSettings" );
00182     d->maximumSize = config->readNumEntry( "MaximumSize", 1024*1024 /* 1MB */ );
00183 
00184     if (bNeedCache)
00185     {
00186         KGlobal::dirs()->addResourceType( "thumbnails", "share/thumbnails/" );
00187         if (d->width == d->height)
00188         {
00189             if (d->width == 48)
00190                 d->sizeName = "small";
00191             else if (d->width == 64)
00192                 d->sizeName = "med";
00193             else if (d->width == 90)
00194                 d->sizeName = "large";
00195             else if (d->width == 112)
00196                 d->sizeName = "xxl";
00197             else
00198                 d->sizeName.setNum(d->width);
00199         }
00200         else
00201             d->sizeName = QString::fromLatin1("%1x%2").arg(d->width).arg(d->height);
00202     }
00203     else
00204         d->bSave = false;
00205 /*    // Check if we're in a thumbnail dir already
00206     if ( m_iconView->url().isLocalFile() )
00207     {
00208       // there's exactly one path
00209       QString cachePath = QDir::cleanDirPath( KGlobal::dirs()->resourceDirs( "thumbnails" )[0] );
00210       QString dir = QDir::cleanDirPath( m_iconView->url().directory() );
00211       if ( dir.startsWith( cachePath ) )
00212         thumbPath = dir.mid( cachePath.length() );
00213     }*/
00214     determineNextFile();
00215 }
00216 
00217 void PreviewJob::removeItem( const KFileItem *item )
00218 {
00219     for (QValueList<PreviewItem>::Iterator it = d->items.begin(); it != d->items.end(); ++it)
00220         if ((*it).item == item)
00221         {
00222             d->items.remove(it);
00223             break;
00224         }
00225 
00226     if (d->currentItem.item == item)
00227     {
00228         subjobs.first()->kill();
00229         subjobs.removeFirst();
00230         determineNextFile();
00231     }
00232 }
00233 
00234 void PreviewJob::determineNextFile()
00235 {
00236     kdDebug() << k_funcinfo << endl;
00237     KURL oldURL;
00238     if (d->currentItem.item)
00239     {
00240         if (!d->succeeded)
00241             emitFailed();
00242         if (d->deleteItems)
00243             delete d->currentItem.item;
00244     }
00245     // No more items ?
00246     if ( d->items.isEmpty() )
00247     {
00248         emitResult();
00249         return;
00250     }
00251     else
00252     {
00253         // First, stat the orig file
00254         d->state = PreviewJobPrivate::STATE_STATORIG;
00255         d->currentItem = d->items.first();
00256         d->succeeded = false;
00257         d->items.remove(d->items.begin());
00258         if (d->bSave)
00259         {
00260             oldURL.setPath(QDir::cleanDirPath(oldURL.directory()));
00261             KURL newURL = d->currentItem.item->url();
00262             newURL.setPath(QDir::cleanDirPath(newURL.directory()));
00263             // different path, determine cache dir
00264             if (oldURL != newURL)
00265             {
00266                 KMD5 md5(QFile::encodeName(newURL.url()));
00267                 QCString hash = md5.hexDigest();
00268                 d->thumbPath = locateLocal("thumbnails",
00269                     QString::fromLatin1( hash.data(), 4 ) + "/" +
00270                     QString::fromLatin1( hash.data()+4, 4 ) + "/" +
00271                     QString::fromLatin1( hash.data()+8 ) + "/" +
00272                     d->sizeName + "/");
00273             }
00274         }
00275         kdDebug() << k_funcinfo << " stating "  << d->currentItem.item->url().prettyURL() << endl;
00276         KIO::Job *job = KIO::stat( d->currentItem.item->url(), false );
00277         addSubjob(job);
00278     }
00279 }
00280 
00281 void PreviewJob::slotResult( KIO::Job *job )
00282 {
00283     subjobs.remove( job );
00284     Q_ASSERT ( subjobs.isEmpty() ); // We should have only one job at a time ...
00285     switch ( d->state )
00286     {
00287         case PreviewJobPrivate::STATE_STATORIG:
00288         {
00289             if (job->error()) // that's no good news...
00290             {
00291                 // Drop this one and move on to the next one
00292                 determineNextFile();
00293                 return;
00294             }
00295             KIO::UDSEntry entry = ((KIO::StatJob*)job)->statResult();
00296             KIO::UDSEntry::ConstIterator it = entry.begin();
00297             d->tOrig = 0;
00298             int found = 0;
00299             for( ; it != entry.end() && found < 2; it++ )
00300             {
00301                 if ( (*it).m_uds == KIO::UDS_MODIFICATION_TIME )
00302                 {
00303                     d->tOrig = (time_t)((*it).m_long);
00304                     found++;
00305                 }
00306                 else if ( (*it).m_uds == KIO::UDS_SIZE )
00307                     {
00308                     if ( static_cast<unsigned long>((*it).m_long) > d->maximumSize && !d->currentItem.plugin->property("IgnoreMaximumSize").toBool() )
00309                     {
00310                         determineNextFile();
00311                         return;
00312                     }
00313                     found++;
00314                 }
00315             }
00316 
00317             if ( !d->currentItem.plugin->property( "CacheThumbnail" ).toBool() )
00318             {
00319                 // This preview will not be cached, no need to look for a saved thumbnail
00320                 // Just create it, and be done
00321                 getOrCreateThumbnail();
00322                 return;
00323             }
00324 
00325             if ( statResultThumbnail() )
00326                 return;
00327 
00328             getOrCreateThumbnail();
00329             return;
00330         }
00331         case PreviewJobPrivate::STATE_GETORIG:
00332         {
00333             if (job->error())
00334             {
00335                 determineNextFile();
00336                 return;
00337             }
00338 
00339             createThumbnail( static_cast<KIO::FileCopyJob*>(job)->destURL().path() );
00340             return;
00341         }
00342         case PreviewJobPrivate::STATE_CREATETHUMB:
00343         {
00344             if (!d->tempName.isEmpty())
00345             {
00346                 QFile::remove(d->tempName);
00347                 d->tempName = QString::null;
00348             }
00349             determineNextFile();
00350             return;
00351         }
00352     }
00353 }
00354 
00355 bool PreviewJob::statResultThumbnail()
00356 {
00357     if ( d->thumbPath.isEmpty() )
00358         return false;
00359 
00360     QString thumbPath = d->thumbPath + d->currentItem.item->url().fileName();
00361     struct stat st;
00362     if ( ::stat( QFile::encodeName( thumbPath ), &st ) != 0 ||
00363          st.st_mtime < d->tOrig )
00364         return false;
00365 
00366     QPixmap pix;
00367     if ( !pix.load( thumbPath ) )
00368         return false;
00369 
00370     // Found it, use it
00371     emitPreview(pix);
00372     d->succeeded = true;
00373     determineNextFile();
00374     return true;
00375 }
00376 
00377 
00378 void PreviewJob::getOrCreateThumbnail()
00379 {
00380     kdDebug() << k_funcinfo << endl;
00381     // We still need to load the orig file ! (This is getting tedious) :)
00382     KURL currentURL = d->currentItem.item->url();
00383     if ( currentURL.isLocalFile() )
00384         createThumbnail( currentURL.path() );
00385     else
00386     {
00387         d->state = PreviewJobPrivate::STATE_GETORIG;
00388         KTempFile localFile;
00389         KURL localURL;
00390         localURL.setPath( d->tempName = localFile.name() );
00391         KIO::Job * job = KIO::file_copy( currentURL, localURL, -1, true,
00392                                          false, false /* No GUI */ );
00393         job->addMetaData("thumbnail","1");
00394         addSubjob(job);
00395     }
00396 }
00397 
00398 void PreviewJob::createThumbnail( QString pixPath )
00399 {
00400     d->state = PreviewJobPrivate::STATE_CREATETHUMB;
00401     KURL thumbURL;
00402     thumbURL.setProtocol("thumbnail");
00403     thumbURL.setPath(pixPath);
00404     KIO::TransferJob *job = KIO::get(thumbURL, false, false);
00405     addSubjob(job);
00406     connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)), SLOT(slotThumbData(KIO::Job *, const QByteArray &)));
00407     job->addMetaData("mimeType", d->currentItem.item->mimetype());
00408     job->addMetaData("width", QString().setNum(d->width));
00409     job->addMetaData("height", QString().setNum(d->height));
00410     job->addMetaData("iconSize", QString().setNum(d->iconSize));
00411     job->addMetaData("iconAlpha", QString().setNum(d->iconAlpha));
00412     job->addMetaData("plugin", d->currentItem.plugin->library());
00413     if (d->shmid == -1)
00414     {
00415         if (d->shmaddr) {
00416             shmdt((char*)d->shmaddr);
00417             shmctl(d->shmid, IPC_RMID, 0);
00418         }
00419         d->shmid = shmget(IPC_PRIVATE, d->width * d->height * 4, IPC_CREAT|0600);
00420         if (d->shmid != -1)
00421         {
00422             d->shmaddr = static_cast<uchar *>(shmat(d->shmid, 0, SHM_RDONLY));
00423             if (d->shmaddr == (uchar *)-1)
00424             {
00425                 shmctl(d->shmid, IPC_RMID, 0);
00426                 d->shmaddr = 0;
00427                 d->shmid = -1;
00428             }
00429         }
00430         else
00431             d->shmaddr = 0;
00432     }
00433     if (d->shmid != -1)
00434         job->addMetaData("shmid", QString().setNum(d->shmid));
00435 }
00436 
00437 void PreviewJob::slotThumbData(KIO::Job *, const QByteArray &data)
00438 {
00439     bool save = d->bSave && d->currentItem.plugin->property("CacheThumbnail").toBool();
00440     kdDebug() << k_funcinfo << "save=" << save << endl;
00441     QPixmap pix;
00442     if (d->shmaddr)
00443     {
00444         QDataStream str(data, IO_ReadOnly);
00445         int width, height, depth;
00446         bool alpha;
00447         str >> width >> height >> depth >> alpha;
00448         QImage img(d->shmaddr, width, height, depth, 0, 0, QImage::IgnoreEndian);
00449         img.setAlphaBuffer(alpha);
00450         pix.convertFromImage(img);
00451         if (save)
00452             img.save( d->thumbPath + d->currentItem.item->url().fileName(), "PNG" );
00453     }
00454     else
00455     {
00456         pix.loadFromData(data);
00457         if (save)
00458             saveThumbnail(data);
00459     }
00460     emitPreview(pix);
00461     d->succeeded = true;
00462 }
00463 
00464 void PreviewJob::emitPreview(const QPixmap &pix)
00465 {
00466     kdDebug() << k_funcinfo << endl;
00467     emit gotPreview(d->currentItem.item, pix);
00468 }
00469 
00470 void PreviewJob::emitFailed(const KFileItem *item)
00471 {
00472     kdDebug() << k_funcinfo << endl;
00473     if (!item)
00474         item = d->currentItem.item;
00475     emit failed(item);
00476 }
00477 
00478 void PreviewJob::saveThumbnail(const QByteArray &imgData)
00479 {
00480     QFile file( d->thumbPath + d->currentItem.item->url().fileName() );
00481     if ( file.open(IO_WriteOnly) )
00482     {
00483         file.writeBlock( imgData.data(), imgData.size() );
00484         file.close();
00485     }
00486 }
00487 
00488 QStringList PreviewJob::availablePlugins()
00489 {
00490     QStringList result;
00491     KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
00492     for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00493         result.append((*it)->desktopEntryName());
00494     return result;
00495 }
00496 
00497 QStringList PreviewJob::supportedMimeTypes()
00498 {
00499     QStringList result;
00500     KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
00501     for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00502         result += (*it)->property("MimeTypes").toStringList();
00503     return result;
00504 }
00505 
00506 PreviewJob *KIO::filePreview( const KFileItemList &items, int width, int height,
00507     int iconSize, int iconAlpha, bool scale, bool save,
00508     const QStringList *enabledPlugins )
00509 {
00510     return new PreviewJob(items, width, height, iconSize, iconAlpha,
00511                           scale, save, enabledPlugins);
00512 }
00513 
00514 PreviewJob *KIO::filePreview( const KURL::List &items, int width, int height,
00515     int iconSize, int iconAlpha, bool scale, bool save,
00516     const QStringList *enabledPlugins )
00517 {
00518     KFileItemList fileItems;
00519     for (KURL::List::ConstIterator it = items.begin(); it != items.end(); ++it)
00520         fileItems.append(new KFileItem(KFileItem::Unknown, KFileItem::Unknown, *it, true));
00521     return new PreviewJob(fileItems, width, height, iconSize, iconAlpha,
00522                           scale, save, enabledPlugins, true);
00523 }
00524 
00525 void PreviewJob::virtual_hook( int id, void* data )
00526 { KIO::Job::virtual_hook( id, data ); }
00527 
KDE Logo
This file is part of the documentation for kdelibs Version 3.1.0.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Wed Oct 8 12:21:33 2003 by doxygen 1.2.18 written by Dimitri van Heesch, © 1997-2001