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", 2*1024*1024 /* 2MB */ );
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             d->currentItem.item = 0L;
00235         }
00236     }
00237     // No more items ?
00238     if ( d->items.isEmpty() )
00239     {
00240         emitResult();
00241         return;
00242     }
00243     else
00244     {
00245         // First, stat the orig file
00246         d->state = PreviewJobPrivate::STATE_STATORIG;
00247         d->currentItem = d->items.first();
00248         d->succeeded = false;
00249         d->items.remove(d->items.begin());
00250         KIO::Job *job = KIO::stat( d->currentItem.item->url(), false );
00251         job->addMetaData( "no-auth-prompt", "true" );
00252         addSubjob(job);
00253     }
00254 }
00255 
00256 void PreviewJob::slotResult( KIO::Job *job )
00257 {
00258     subjobs.remove( job );
00259     Q_ASSERT ( subjobs.isEmpty() ); // We should have only one job at a time ...
00260     switch ( d->state )
00261     {
00262         case PreviewJobPrivate::STATE_STATORIG:
00263         {
00264             if (job->error()) // that's no good news...
00265             {
00266                 // Drop this one and move on to the next one
00267                 determineNextFile();
00268                 return;
00269             }
00270             KIO::UDSEntry entry = ((KIO::StatJob*)job)->statResult();
00271             KIO::UDSEntry::ConstIterator it = entry.begin();
00272             d->tOrig = 0;
00273             int found = 0;
00274             for( ; it != entry.end() && found < 2; it++ )
00275             {
00276                 if ( (*it).m_uds == KIO::UDS_MODIFICATION_TIME )
00277                 {
00278                     d->tOrig = (time_t)((*it).m_long);
00279                     found++;
00280                 }
00281                 else if ( (*it).m_uds == KIO::UDS_SIZE )
00282                     {
00283                     if ( filesize_t((*it).m_long) > d->maximumSize &&
00284                          !d->currentItem.plugin->property("IgnoreMaximumSize").toBool() )
00285                     {
00286                         determineNextFile();
00287                         return;
00288                     }
00289                     found++;
00290                 }
00291             }
00292 
00293             if ( !d->currentItem.plugin->property( "CacheThumbnail" ).toBool() )
00294             {
00295                 // This preview will not be cached, no need to look for a saved thumbnail
00296                 // Just create it, and be done
00297                 getOrCreateThumbnail();
00298                 return;
00299             }
00300 
00301             if ( statResultThumbnail() )
00302                 return;
00303 
00304             getOrCreateThumbnail();
00305             return;
00306         }
00307         case PreviewJobPrivate::STATE_GETORIG:
00308         {
00309             if (job->error())
00310             {
00311                 determineNextFile();
00312                 return;
00313             }
00314 
00315             createThumbnail( static_cast<KIO::FileCopyJob*>(job)->destURL().path() );
00316             return;
00317         }
00318         case PreviewJobPrivate::STATE_CREATETHUMB:
00319         {
00320             if (!d->tempName.isEmpty())
00321             {
00322                 QFile::remove(d->tempName);
00323                 d->tempName = QString::null;
00324             }
00325             determineNextFile();
00326             return;
00327         }
00328     }
00329 }
00330 
00331 bool PreviewJob::statResultThumbnail()
00332 {
00333     if ( d->thumbPath.isEmpty() )
00334         return false;
00335 
00336     KURL url = d->currentItem.item->url();
00337     // Don't include the password if any
00338     url.setPass(QString::null);
00339     // The TMS defines local files as file:///path/to/file instead of KDE's
00340     // way (file:/path/to/file)
00341     if (url.protocol() == "file") d->origName = "file://" + url.path();
00342     else d->origName = url.url();
00343 
00344     KMD5 md5( QFile::encodeName( d->origName ) );
00345     d->thumbName = QFile::encodeName( md5.hexDigest() ) + ".png";
00346 
00347     QImage thumb;
00348     if ( !thumb.load( d->thumbPath + d->thumbName ) ) return false;
00349 
00350     if ( thumb.text( "Thumb::URI", 0 ) != d->origName ||
00351          thumb.text( "Thumb::MTime", 0 ).toInt() != d->tOrig ) return false;
00352 
00353     // Found it, use it
00354     emitPreview( thumb );
00355     d->succeeded = true;
00356     determineNextFile();
00357     return true;
00358 }
00359 
00360 
00361 void PreviewJob::getOrCreateThumbnail()
00362 {
00363     // We still need to load the orig file ! (This is getting tedious) :)
00364     KURL currentURL = d->currentItem.item->url();
00365     if ( currentURL.isLocalFile() )
00366         createThumbnail( currentURL.path() );
00367     else
00368     {
00369         d->state = PreviewJobPrivate::STATE_GETORIG;
00370         KTempFile localFile;
00371         KURL localURL;
00372         localURL.setPath( d->tempName = localFile.name() );
00373         KIO::Job * job = KIO::file_copy( currentURL, localURL, -1, true,
00374                                          false, false /* No GUI */ );
00375         job->addMetaData("thumbnail","1");
00376         addSubjob(job);
00377     }
00378 }
00379 
00380 // KDE 4: Make it const QString &
00381 void PreviewJob::createThumbnail( QString pixPath )
00382 {
00383     d->state = PreviewJobPrivate::STATE_CREATETHUMB;
00384     KURL thumbURL;
00385     thumbURL.setProtocol("thumbnail");
00386     thumbURL.setPath(pixPath);
00387     KIO::TransferJob *job = KIO::get(thumbURL, false, false);
00388     addSubjob(job);
00389     connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)), SLOT(slotThumbData(KIO::Job *, const QByteArray &)));
00390     bool save = d->bSave && d->currentItem.plugin->property("CacheThumbnail").toBool();
00391     job->addMetaData("mimeType", d->currentItem.item->mimetype());
00392     job->addMetaData("width", QString().setNum(save ? d->cacheWidth : d->width));
00393     job->addMetaData("height", QString().setNum(save ? d->cacheHeight : d->height));
00394     job->addMetaData("iconSize", QString().setNum(save ? 64 : d->iconSize));
00395     job->addMetaData("iconAlpha", QString().setNum(d->iconAlpha));
00396     job->addMetaData("plugin", d->currentItem.plugin->library());
00397     if (d->shmid == -1)
00398     {
00399         if (d->shmaddr) {
00400             shmdt((char*)d->shmaddr);
00401             shmctl(d->shmid, IPC_RMID, 0);
00402         }
00403         d->shmid = shmget(IPC_PRIVATE, d->cacheWidth * d->cacheHeight * 4, IPC_CREAT|0600);
00404         if (d->shmid != -1)
00405         {
00406             d->shmaddr = static_cast<uchar *>(shmat(d->shmid, 0, SHM_RDONLY));
00407             if (d->shmaddr == (uchar *)-1)
00408             {
00409                 shmctl(d->shmid, IPC_RMID, 0);
00410                 d->shmaddr = 0;
00411                 d->shmid = -1;
00412             }
00413         }
00414         else
00415             d->shmaddr = 0;
00416     }
00417     if (d->shmid != -1)
00418         job->addMetaData("shmid", QString().setNum(d->shmid));
00419 }
00420 
00421 void PreviewJob::slotThumbData(KIO::Job *, const QByteArray &data)
00422 {
00423     bool save = d->bSave &&
00424                 d->currentItem.plugin->property("CacheThumbnail").toBool() &&
00425                 (d->currentItem.item->url().protocol() != "file" ||
00426                  !d->currentItem.item->url().directory( false ).startsWith(d->thumbRoot));
00427     QImage thumb;
00428     if (d->shmaddr)
00429     {
00430         QDataStream str(data, IO_ReadOnly);
00431         int width, height, depth;
00432         bool alpha;
00433         str >> width >> height >> depth >> alpha;
00434         thumb = QImage(d->shmaddr, width, height, depth, 0, 0, QImage::IgnoreEndian);
00435         thumb.setAlphaBuffer(alpha);
00436     }
00437     else thumb.loadFromData(data);
00438     if (save)
00439     {
00440         thumb.setText("Thumb::URI", 0, d->origName);
00441         thumb.setText("Thumb::MTime", 0, QString::number(d->tOrig));
00442         thumb.setText("Thumb::Size", 0, number(d->currentItem.item->size()));
00443         thumb.setText("Thumb::Mimetype", 0, d->currentItem.item->mimetype());
00444         thumb.setText("Software", 0, "KDE Thumbnail Generator");
00445         KTempFile temp(d->thumbPath + "kde-tmp-", ".png");
00446         if (temp.status() == 0) //Only try to write out the thumbnail if we 
00447         {                       //actually created the temp file.
00448             thumb.save(temp.name(), "PNG");
00449             rename(QFile::encodeName(temp.name()), QFile::encodeName(d->thumbPath + d->thumbName));
00450         }
00451     }
00452     emitPreview( thumb );
00453     d->succeeded = true;
00454 }
00455 
00456 void PreviewJob::emitPreview(const QImage &thumb)
00457 {
00458     QPixmap pix;
00459     if (thumb.width() > d->width || thumb.height() > d->height)
00460     {
00461         double imgRatio = (double)thumb.height() / (double)thumb.width();
00462         if (imgRatio > (double)d->height / (double)d->width)
00463             pix.convertFromImage(
00464                 thumb.smoothScale((int)QMAX((double)d->height / imgRatio, 1), d->height));
00465         else pix.convertFromImage(
00466             thumb.smoothScale(d->width, (int)QMAX((double)d->width * imgRatio, 1)));
00467     }
00468     else pix.convertFromImage(thumb);
00469     emit gotPreview(d->currentItem.item, pix);
00470 }
00471 
00472 void PreviewJob::emitFailed(const KFileItem *item)
00473 {
00474     if (!item)
00475         item = d->currentItem.item;
00476     emit failed(item);
00477 }
00478 
00479 QStringList PreviewJob::availablePlugins()
00480 {
00481     QStringList result;
00482     KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
00483     for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00484         if (!result.contains((*it)->desktopEntryName()))
00485             result.append((*it)->desktopEntryName());
00486     return result;
00487 }
00488 
00489 QStringList PreviewJob::supportedMimeTypes()
00490 {
00491     QStringList result;
00492     KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
00493     for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00494         result += (*it)->property("MimeTypes").toStringList();
00495     return result;
00496 }
00497 
00498 PreviewJob *KIO::filePreview( const KFileItemList &items, int width, int height,
00499     int iconSize, int iconAlpha, bool scale, bool save,
00500     const QStringList *enabledPlugins )
00501 {
00502     return new PreviewJob(items, width, height, iconSize, iconAlpha,
00503                           scale, save, enabledPlugins);
00504 }
00505 
00506 PreviewJob *KIO::filePreview( const KURL::List &items, int width, int height,
00507     int iconSize, int iconAlpha, bool scale, bool save,
00508     const QStringList *enabledPlugins )
00509 {
00510     KFileItemList fileItems;
00511     for (KURL::List::ConstIterator it = items.begin(); it != items.end(); ++it)
00512         fileItems.append(new KFileItem(KFileItem::Unknown, KFileItem::Unknown, *it, true));
00513     return new PreviewJob(fileItems, width, height, iconSize, iconAlpha,
00514                           scale, save, enabledPlugins, true);
00515 }
00516 
00517 void PreviewJob::virtual_hook( int id, void* data )
00518 { KIO::Job::virtual_hook( id, data ); }
00519 
KDE Logo
This file is part of the documentation for kio Library Version 3.3.90.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Mar 30 10:15:32 2005 by doxygen 1.3.9.1 written by Dimitri van Heesch, © 1997-2003