kio Library: previewjob.cpp Source File (kio Library)

| # kio Library API Documentation
Overview | Class Hierarchy | Classes | Classes (annotated) | Members | Namespaces | 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 "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 };
    00116
    00117 PreviewJob::PreviewJob( const KFileItemList &items, int width, int height,
    00118     int iconSize, int iconAlpha, bool scale, bool save,
    00119     const QStringList *enabledPlugins, bool deleteItems )
    00120     : KIO::Job( false /* no GUI */ )
    00121 {
    00122     d = new PreviewJobPrivate;
    00123     d->tOrig = 0;
    00124     d->shmid = -1;
    00125     d->shmaddr = 0;
    00126     d->initialItems = items;
    00127     d->enabledPlugins = enabledPlugins;
    00128     d->width = width;
    00129     d->height = height ? height : width;
    00130     d->cacheWidth = d->width;
    00131     d->cacheHeight = d->height;
    00132     d->iconSize = iconSize;
    00133     d->iconAlpha = iconAlpha;
    00134     d->deleteItems = deleteItems;
    00135     d->bScale = scale;
    00136     d->bSave = save && scale;
    00137     d->succeeded = false;
    00138     d->currentItem.item = 0;
    00139     d->thumbRoot = QDir::homeDirPath() + "/.thumbnails/";
    00140     d->ignoreMaximumSize = false;
    00141
    00142     // Return to event loop first, determineNextFile() might delete this;
    00143     QTimer::singleShot(0, this, SLOT(startPreview()));
    00144 }
    00145
    00146 PreviewJob::~PreviewJob()
    00147 {
    00148 #ifdef Q_OS_UNIX
    00149     if (d->shmaddr) {
    00150         shmdt((char*)d->shmaddr);
    00151         shmctl(d->shmid, IPC_RMID, 0);
    00152     }
    00153 #endif
    00154     delete d;
    00155 }
    00156
    00157 void PreviewJob::startPreview()
    00158 {
    00159     // Load the list of plugins to determine which mimetypes are supported
    00160     KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
    00161     QMap<QString, KService::Ptr> mimeMap;
    00162
    00163     for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
    00164         if (!d->enabledPlugins || d->enabledPlugins->contains((*it)->desktopEntryName()))
    00165     {
    00166         QStringList mimeTypes = (*it)->property("MimeTypes").toStringList();
    00167         for (QStringList::ConstIterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt)
    00168             mimeMap.insert(*mt, *it);
    00169     }
    00170
    00171     // Look for images and store the items in our todo list :)
    00172     bool bNeedCache = false;
    00173     for (KFileItemListIterator it(d->initialItems); it.current(); ++it )
    00174     {
    00175         PreviewItem item;
    00176         item.item = it.current();
    00177         QMap<QString, KService::Ptr>::ConstIterator plugin = mimeMap.find(it.current()->mimetype());
    00178         if (plugin == mimeMap.end())
    00179         {
    00180             QString mimeType = it.current()->mimetype();
    00181             plugin = mimeMap.find(mimeType.replace(QRegExp("/.*"), "/*"));
    00182         }
    00183         if (plugin != mimeMap.end())
    00184         {
    00185             item.plugin = *plugin;
    00186             d->items.append(item);
    00187             if (!bNeedCache && d->bSave &&
    00188                 (it.current()->url().protocol() != "file" ||
    00189                  !it.current()->url().directory( false ).startsWith(d->thumbRoot)) &&
    00190                 (*plugin)->property("CacheThumbnail").toBool())
    00191                 bNeedCache = true;
    00192         }
    00193         else
    00194         {
    00195             emitFailed(it.current());
    00196             if (d->deleteItems)
    00197                 delete it.current();
    00198         }
    00199     }
    00200
    00201   // Read configuration value for the maximum allowed size
    00202     KConfig * config = KGlobal::config();
    00203     KConfigGroupSaver cgs( config, "PreviewSettings" );
    00204     d->maximumSize = config->readNumEntry( "MaximumSize", 1024*1024 /* 1MB */ );
    00205
    00206     if (bNeedCache)
    00207     {
    00208         if (d->width <= 128 && d->height <= 128) d->cacheWidth = d->cacheHeight = 128;
    00209         else d->cacheWidth = d->cacheHeight = 256;
    00210         d->thumbPath = d->thumbRoot + (d->cacheWidth == 128 ? "normal/" : "large/");
    00211         KStandardDirs::makeDir(d->thumbPath, 0700);
    00212     }
    00213     else
    00214         d->bSave = false;
    00215     determineNextFile();
    00216 }
    00217
    00218 void PreviewJob::removeItem( const KFileItem *item )
    00219 {
    00220     for (QValueList<PreviewItem>::Iterator it = d->items.begin(); it != d->items.end(); ++it)
    00221         if ((*it).item == item)
    00222         {
    00223             d->items.remove(it);
    00224             break;
    00225         }
    00226
    00227     if (d->currentItem.item == item)
    00228     {
    00229         subjobs.first()->kill();
    00230         subjobs.removeFirst();
    00231         determineNextFile();
    00232     }
    00233 }
    00234
    00235 void PreviewJob::setIgnoreMaximumSize(bool ignoreSize)
    00236 {
    00237     d->ignoreMaximumSize = ignoreSize;
    00238 }
    00239
    00240 void PreviewJob::determineNextFile()
    00241 {
    00242     if (d->currentItem.item)
    00243     {
    00244         if (!d->succeeded)
    00245             emitFailed();
    00246         if (d->deleteItems) {
    00247             delete d->currentItem.item;
    00248             d->currentItem.item = 0L;
    00249         }
    00250     }
    00251     // No more items ?
    00252     if ( d->items.isEmpty() )
    00253     {
    00254         emitResult();
    00255         return;
    00256     }
    00257     else
    00258     {
    00259         // First, stat the orig file
    00260         d->state = PreviewJobPrivate::STATE_STATORIG;
    00261         d->currentItem = d->items.first();
    00262         d->succeeded = false;
    00263         d->items.remove(d->items.begin());
    00264         KIO::Job *job = KIO::stat( d->currentItem.item->url(), false );
    00265         job->addMetaData( "no-auth-prompt", "true" );
    00266         addSubjob(job);
    00267     }
    00268 }
    00269
    00270 void PreviewJob::slotResult( KIO::Job *job )
    00271 {
    00272     subjobs.remove( job );
    00273     Q_ASSERT ( subjobs.isEmpty() ); // We should have only one job at a time ...
    00274     switch ( d->state )
    00275     {
    00276         case PreviewJobPrivate::STATE_STATORIG:
    00277         {
    00278             if (job->error()) // that's no good news...
    00279             {
    00280                 // Drop this one and move on to the next one
    00281                 determineNextFile();
    00282                 return;
    00283             }
    00284             KIO::UDSEntry entry = ((KIO::StatJob*)job)->statResult();
    00285             KIO::UDSEntry::ConstIterator it = entry.begin();
    00286             d->tOrig = 0;
    00287             int found = 0;
    00288             for( ; it != entry.end() && found < 2; it++ )
    00289             {
    00290                 if ( (*it).m_uds == KIO::UDS_MODIFICATION_TIME )
    00291                 {
    00292                     d->tOrig = (time_t)((*it).m_long);
    00293                     found++;
    00294                 }
    00295                 else if ( (*it).m_uds == KIO::UDS_SIZE )
    00296                     {
    00297                     if ( filesize_t((*it).m_long) > d->maximumSize &&
    00298                          !d->ignoreMaximumSize &&
    00299                          !d->currentItem.plugin->property("IgnoreMaximumSize").toBool() )
    00300                     {
    00301                         determineNextFile();
    00302                         return;
    00303                     }
    00304                     found++;
    00305                 }
    00306             }
    00307
    00308             if ( !d->currentItem.plugin->property( "CacheThumbnail" ).toBool() )
    00309             {
    00310                 // This preview will not be cached, no need to look for a saved thumbnail
    00311                 // Just create it, and be done
    00312                 getOrCreateThumbnail();
    00313                 return;
    00314             }
    00315
    00316             if ( statResultThumbnail() )
    00317                 return;
    00318
    00319             getOrCreateThumbnail();
    00320             return;
    00321         }
    00322         case PreviewJobPrivate::STATE_GETORIG:
    00323         {
    00324             if (job->error())
    00325             {
    00326                 determineNextFile();
    00327                 return;
    00328             }
    00329
    00330             createThumbnail( static_cast<KIO::FileCopyJob*>(job)->destURL().path() );
    00331             return;
    00332         }
    00333         case PreviewJobPrivate::STATE_CREATETHUMB:
    00334         {
    00335             if (!d->tempName.isEmpty())
    00336             {
    00337                 QFile::remove(d->tempName);
    00338                 d->tempName = QString::null;
    00339             }
    00340             determineNextFile();
    00341             return;
    00342         }
    00343     }
    00344 }
    00345
    00346 bool PreviewJob::statResultThumbnail()
    00347 {
    00348     if ( d->thumbPath.isEmpty() )
    00349         return false;
    00350
    00351     KURL url = d->currentItem.item->url();
    00352     // Don't include the password if any
    00353     url.setPass(QString::null);
    00354     // The TMS defines local files as file:///path/to/file instead of KDE's
    00355     // way (file:/path/to/file)
    00356 #ifdef KURL_TRIPLE_SLASH_FILE_PROT
    00357     d->origName = url.url();
    00358 #else
    00359     if (url.protocol() == "file") d->origName = "file://" + url.path();
    00360     else d->origName = url.url();
    00361 #endif
    00362
    00363     KMD5 md5( QFile::encodeName( d->origName ) );
    00364     d->thumbName = QFile::encodeName( md5.hexDigest() ) + ".png";
    00365
    00366     QImage thumb;
    00367     if ( !thumb.load( d->thumbPath + d->thumbName ) ) return false;
    00368
    00369     if ( thumb.text( "Thumb::URI", 0 ) != d->origName ||
    00370          thumb.text( "Thumb::MTime", 0 ).toInt() != d->tOrig ) return false;
    00371
    00372     // Found it, use it
    00373     emitPreview( thumb );
    00374     d->succeeded = true;
    00375     determineNextFile();
    00376     return true;
    00377 }
    00378
    00379
    00380 void PreviewJob::getOrCreateThumbnail()
    00381 {
    00382     // We still need to load the orig file ! (This is getting tedious) :)
    00383     KURL currentURL = d->currentItem.item->url();
    00384     if ( currentURL.isLocalFile() )
    00385         createThumbnail( currentURL.path() );
    00386     else
    00387     {
    00388         d->state = PreviewJobPrivate::STATE_GETORIG;
    00389         KTempFile localFile;
    00390         KURL localURL;
    00391         localURL.setPath( d->tempName = localFile.name() );
    00392         KIO::Job * job = KIO::file_copy( currentURL, localURL, -1, true,
    00393                                          false, false /* No GUI */ );
    00394         job->addMetaData("thumbnail","1");
    00395         addSubjob(job);
    00396     }
    00397 }
    00398
    00399 // KDE 4: Make it const QString &
    00400 void PreviewJob::createThumbnail( QString pixPath )
    00401 {
    00402     d->state = PreviewJobPrivate::STATE_CREATETHUMB;
    00403     KURL thumbURL;
    00404     thumbURL.setProtocol("thumbnail");
    00405     thumbURL.setPath(pixPath);
    00406     KIO::TransferJob *job = KIO::get(thumbURL, false, false);
    00407     addSubjob(job);
    00408     connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)), SLOT(slotThumbData(KIO::Job *, const QByteArray &)));
    00409     bool save = d->bSave && d->currentItem.plugin->property("CacheThumbnail").toBool();
    00410     job->addMetaData("mimeType", d->currentItem.item->mimetype());
    00411     job->addMetaData("width", QString().setNum(save ? d->cacheWidth : d->width));
    00412     job->addMetaData("height", QString().setNum(save ? d->cacheHeight : d->height));
    00413     job->addMetaData("iconSize", QString().setNum(save ? 64 : d->iconSize));
    00414     job->addMetaData("iconAlpha", QString().setNum(d->iconAlpha));
    00415     job->addMetaData("plugin", d->currentItem.plugin->library());
    00416 #ifdef Q_OS_UNIX
    00417     if (d->shmid == -1)
    00418     {
    00419         if (d->shmaddr) {
    00420             shmdt((char*)d->shmaddr);
    00421             shmctl(d->shmid, IPC_RMID, 0);
    00422         }
    00423         d->shmid = shmget(IPC_PRIVATE, d->cacheWidth * d->cacheHeight * 4, IPC_CREAT|0600);
    00424         if (d->shmid != -1)
    00425         {
    00426             d->shmaddr = static_cast<uchar *>(shmat(d->shmid, 0, SHM_RDONLY));
    00427             if (d->shmaddr == (uchar *)-1)
    00428             {
    00429                 shmctl(d->shmid, IPC_RMID, 0);
    00430                 d->shmaddr = 0;
    00431                 d->shmid = -1;
    00432             }
    00433         }
    00434         else
    00435             d->shmaddr = 0;
    00436     }
    00437     if (d->shmid != -1)
    00438         job->addMetaData("shmid", QString().setNum(d->shmid));
    00439 #endif
    00440 }
    00441
    00442 void PreviewJob::slotThumbData(KIO::Job *, const QByteArray &data)
    00443 {
    00444     bool save = d->bSave &&
    00445                 d->currentItem.plugin->property("CacheThumbnail").toBool() &&
    00446                 (d->currentItem.item->url().protocol() != "file" ||
    00447                  !d->currentItem.item->url().directory( false ).startsWith(d->thumbRoot));
    00448     QImage thumb;
    00449 #ifdef Q_OS_UNIX
    00450     if (d->shmaddr)
    00451     {
    00452         QDataStream str(data, IO_ReadOnly);
    00453         int width, height, depth;
    00454         bool alpha;
    00455         str >> width >> height >> depth >> alpha;
    00456         thumb = QImage(d->shmaddr, width, height, depth, 0, 0, QImage::IgnoreEndian);
    00457         thumb.setAlphaBuffer(alpha);
    00458     }
    00459     else
    00460 #endif
    00461         thumb.loadFromData(data);
    00462
    00463     if (save)
    00464     {
    00465         thumb.setText("Thumb::URI", 0, d->origName);
    00466         thumb.setText("Thumb::MTime", 0, QString::number(d->tOrig));
    00467         thumb.setText("Thumb::Size", 0, number(d->currentItem.item->size()));
    00468         thumb.setText("Thumb::Mimetype", 0, d->currentItem.item->mimetype());
    00469         thumb.setText("Software", 0, "KDE Thumbnail Generator");
    00470         KTempFile temp(d->thumbPath + "kde-tmp-", ".png");
    00471         if (temp.status() == 0) //Only try to write out the thumbnail if we
    00472         {                       //actually created the temp file.
    00473             thumb.save(temp.name(), "PNG");
    00474             rename(QFile::encodeName(temp.name()), QFile::encodeName(d->thumbPath + d->thumbName));
    00475         }
    00476     }
    00477     emitPreview( thumb );
    00478     d->succeeded = true;
    00479 }
    00480
    00481 void PreviewJob::emitPreview(const QImage &thumb)
    00482 {
    00483     QPixmap pix;
    00484     if (thumb.width() > d->width || thumb.height() > d->height)
    00485     {
    00486         double imgRatio = (double)thumb.height() / (double)thumb.width();
    00487         if (imgRatio > (double)d->height / (double)d->width)
    00488             pix.convertFromImage(
    00489                 thumb.smoothScale((int)QMAX((double)d->height / imgRatio, 1), d->height));
    00490         else pix.convertFromImage(
    00491             thumb.smoothScale(d->width, (int)QMAX((double)d->width * imgRatio, 1)));
    00492     }
    00493     else pix.convertFromImage(thumb);
    00494     emit gotPreview(d->currentItem.item, pix);
    00495 }
    00496
    00497 void PreviewJob::emitFailed(const KFileItem *item)
    00498 {
    00499     if (!item)
    00500         item = d->currentItem.item;
    00501     emit failed(item);
    00502 }
    00503
    00504 QStringList PreviewJob::availablePlugins()
    00505 {
    00506     QStringList result;
    00507     KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
    00508     for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
    00509         if (!result.contains((*it)->desktopEntryName()))
    00510             result.append((*it)->desktopEntryName());
    00511     return result;
    00512 }
    00513
    00514 QStringList PreviewJob::supportedMimeTypes()
    00515 {
    00516     QStringList result;
    00517     KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
    00518     for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
    00519         result += (*it)->property("MimeTypes").toStringList();
    00520     return result;
    00521 }
    00522
    00523 PreviewJob *KIO::filePreview( const KFileItemList &items, int width, int height,
    00524     int iconSize, int iconAlpha, bool scale, bool save,
    00525     const QStringList *enabledPlugins )
    00526 {
    00527     return new PreviewJob(items, width, height, iconSize, iconAlpha,
    00528                           scale, save, enabledPlugins);
    00529 }
    00530
    00531 PreviewJob *KIO::filePreview( const KURL::List &items, int width, int height,
    00532     int iconSize, int iconAlpha, bool scale, bool save,
    00533     const QStringList *enabledPlugins )
    00534 {
    00535     KFileItemList fileItems;
    00536     for (KURL::List::ConstIterator it = items.begin(); it != items.end(); ++it)
    00537         fileItems.append(new KFileItem(KFileItem::Unknown, KFileItem::Unknown, *it, true));
    00538     return new PreviewJob(fileItems, width, height, iconSize, iconAlpha,
    00539                           scale, save, enabledPlugins, true);
    00540 }
    00541
    00542 void PreviewJob::virtual_hook( int id, void* data )
    00543 { KIO::Job::virtual_hook( id, data ); }
    00544

This file is part of the documentation for kio Library Version 3.4.1.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sat Jun 11 20:24:06 2005 by doxygen 1.4.1 written by Dimitri van Heesch, © 1997-2003