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 "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", 2*1024*1024 /* 2MB */ ); 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
KDE Logo
This file is part of the documentation for kio Library Version 3.4.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Apr 14 00:20:32 2005 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003