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 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.1.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sun Oct 17 11:29:31 2004 by doxygen 1.3.8 written by Dimitri van Heesch, © 1997-2003