00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
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>
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,
00068 STATE_GETORIG,
00069 STATE_CREATETHUMB
00070 } state;
00071 KFileItemList initialItems;
00072 const QStringList *enabledPlugins;
00073
00074 QValueList<PreviewItem> items;
00075
00076 PreviewItem currentItem;
00077
00078 time_t tOrig;
00079
00080 QString thumbPath;
00081
00082
00083 QString origName;
00084
00085 QString thumbName;
00086
00087 int width;
00088 int height;
00089
00090 int cacheWidth;
00091 int cacheHeight;
00092
00093 bool bScale;
00094
00095 bool bSave;
00096
00097 QString tempName;
00098
00099 unsigned long maximumSize;
00100
00101 int iconSize;
00102
00103 int iconAlpha;
00104
00105
00106 int shmid;
00107
00108 uchar *shmaddr;
00109
00110 bool deleteItems;
00111 bool succeeded;
00112
00113 QString thumbRoot;
00114 bool ignoreMaximumSize;
00115 QTimer startPreviewTimer;
00116 };
00117
00118 PreviewJob::PreviewJob( const KFileItemList &items, int width, int height,
00119 int iconSize, int iconAlpha, bool scale, bool save,
00120 const QStringList *enabledPlugins, bool deleteItems )
00121 : KIO::Job( false )
00122 {
00123 d = new PreviewJobPrivate;
00124 d->tOrig = 0;
00125 d->shmid = -1;
00126 d->shmaddr = 0;
00127 d->initialItems = items;
00128 d->enabledPlugins = enabledPlugins;
00129 d->width = width;
00130 d->height = height ? height : width;
00131 d->cacheWidth = d->width;
00132 d->cacheHeight = d->height;
00133 d->iconSize = iconSize;
00134 d->iconAlpha = iconAlpha;
00135 d->deleteItems = deleteItems;
00136 d->bScale = scale;
00137 d->bSave = save && scale;
00138 d->succeeded = false;
00139 d->currentItem.item = 0;
00140 d->thumbRoot = QDir::homeDirPath() + "/.thumbnails/";
00141 d->ignoreMaximumSize = false;
00142
00143
00144 connect(&d->startPreviewTimer, SIGNAL(timeout()), SLOT(startPreview()) );
00145 d->startPreviewTimer.start(0, true);
00146 }
00147
00148 PreviewJob::~PreviewJob()
00149 {
00150 #ifdef Q_OS_UNIX
00151 if (d->shmaddr) {
00152 shmdt((char*)d->shmaddr);
00153 shmctl(d->shmid, IPC_RMID, 0);
00154 }
00155 #endif
00156 delete d;
00157 }
00158
00159 void PreviewJob::startPreview()
00160 {
00161
00162 KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
00163 QMap<QString, KService::Ptr> mimeMap;
00164
00165 for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00166 if (!d->enabledPlugins || d->enabledPlugins->contains((*it)->desktopEntryName()))
00167 {
00168 QStringList mimeTypes = (*it)->property("MimeTypes").toStringList();
00169 for (QStringList::ConstIterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt)
00170 mimeMap.insert(*mt, *it);
00171 }
00172
00173
00174 bool bNeedCache = false;
00175 for (KFileItemListIterator it(d->initialItems); it.current(); ++it )
00176 {
00177 PreviewItem item;
00178 item.item = it.current();
00179 QMap<QString, KService::Ptr>::ConstIterator plugin = mimeMap.find(it.current()->mimetype());
00180 if (plugin == mimeMap.end() && it.current()->mimetype() != "application/x-desktop")
00181 {
00182 QString mimeType = it.current()->mimetype();
00183 plugin = mimeMap.find(mimeType.replace(QRegExp("/.*"), "/*"));
00184
00185 if (plugin == mimeMap.end())
00186 {
00187
00188 KMimeType::Ptr mimeInfo = KMimeType::mimeType(it.current()->mimetype());
00189 QString parentMimeType = mimeInfo->parentMimeType();
00190 while (!parentMimeType.isEmpty())
00191 {
00192 plugin = mimeMap.find(parentMimeType);
00193 if (plugin != mimeMap.end()) break;
00194
00195 KMimeType::Ptr parentMimeInfo = KMimeType::mimeType(parentMimeType);
00196 if (!parentMimeInfo) break;
00197
00198 parentMimeType = parentMimeInfo->parentMimeType();
00199 }
00200 }
00201
00202 if (plugin == mimeMap.end())
00203 {
00204
00205 KMimeType::Ptr mimeInfo = KMimeType::mimeType(it.current()->mimetype());
00206 QVariant textProperty = mimeInfo->property("X-KDE-text");
00207 if (textProperty.isValid() && textProperty.type() == QVariant::Bool)
00208 {
00209 if (textProperty.toBool())
00210 {
00211 plugin = mimeMap.find("text/plain");
00212 if (plugin == mimeMap.end())
00213 {
00214 plugin = mimeMap.find( "text/*" );
00215 }
00216 }
00217 }
00218 }
00219 }
00220
00221 if (plugin != mimeMap.end())
00222 {
00223 item.plugin = *plugin;
00224 d->items.append(item);
00225 if (!bNeedCache && d->bSave &&
00226 (it.current()->url().protocol() != "file" ||
00227 !it.current()->url().directory( false ).startsWith(d->thumbRoot)) &&
00228 (*plugin)->property("CacheThumbnail").toBool())
00229 bNeedCache = true;
00230 }
00231 else
00232 {
00233 emitFailed(it.current());
00234 if (d->deleteItems)
00235 delete it.current();
00236 }
00237 }
00238
00239
00240 KConfig * config = KGlobal::config();
00241 KConfigGroupSaver cgs( config, "PreviewSettings" );
00242 d->maximumSize = config->readNumEntry( "MaximumSize", 1024*1024 );
00243
00244 if (bNeedCache)
00245 {
00246 if (d->width <= 128 && d->height <= 128) d->cacheWidth = d->cacheHeight = 128;
00247 else d->cacheWidth = d->cacheHeight = 256;
00248 d->thumbPath = d->thumbRoot + (d->cacheWidth == 128 ? "normal/" : "large/");
00249 KStandardDirs::makeDir(d->thumbPath, 0700);
00250 }
00251 else
00252 d->bSave = false;
00253 determineNextFile();
00254 }
00255
00256 void PreviewJob::removeItem( const KFileItem *item )
00257 {
00258 for (QValueList<PreviewItem>::Iterator it = d->items.begin(); it != d->items.end(); ++it)
00259 if ((*it).item == item)
00260 {
00261 d->items.remove(it);
00262 break;
00263 }
00264
00265 if (d->currentItem.item == item)
00266 {
00267 subjobs.first()->kill();
00268 subjobs.removeFirst();
00269 determineNextFile();
00270 }
00271 }
00272
00273 void PreviewJob::setIgnoreMaximumSize(bool ignoreSize)
00274 {
00275 d->ignoreMaximumSize = ignoreSize;
00276 }
00277
00278 void PreviewJob::determineNextFile()
00279 {
00280 if (d->currentItem.item)
00281 {
00282 if (!d->succeeded)
00283 emitFailed();
00284 if (d->deleteItems) {
00285 delete d->currentItem.item;
00286 d->currentItem.item = 0L;
00287 }
00288 }
00289
00290 if ( d->items.isEmpty() )
00291 {
00292 emitResult();
00293 return;
00294 }
00295 else
00296 {
00297
00298 d->state = PreviewJobPrivate::STATE_STATORIG;
00299 d->currentItem = d->items.first();
00300 d->succeeded = false;
00301 d->items.remove(d->items.begin());
00302 KIO::Job *job = KIO::stat( d->currentItem.item->url(), false );
00303 job->addMetaData( "no-auth-prompt", "true" );
00304 addSubjob(job);
00305 }
00306 }
00307
00308 void PreviewJob::slotResult( KIO::Job *job )
00309 {
00310 subjobs.remove( job );
00311 Q_ASSERT ( subjobs.isEmpty() );
00312 switch ( d->state )
00313 {
00314 case PreviewJobPrivate::STATE_STATORIG:
00315 {
00316 if (job->error())
00317 {
00318
00319 determineNextFile();
00320 return;
00321 }
00322 KIO::UDSEntry entry = ((KIO::StatJob*)job)->statResult();
00323 KIO::UDSEntry::ConstIterator it = entry.begin();
00324 d->tOrig = 0;
00325 int found = 0;
00326 for( ; it != entry.end() && found < 2; it++ )
00327 {
00328 if ( (*it).m_uds == KIO::UDS_MODIFICATION_TIME )
00329 {
00330 d->tOrig = (time_t)((*it).m_long);
00331 found++;
00332 }
00333 else if ( (*it).m_uds == KIO::UDS_SIZE )
00334 {
00335 if ( filesize_t((*it).m_long) > d->maximumSize &&
00336 !d->ignoreMaximumSize &&
00337 !d->currentItem.plugin->property("IgnoreMaximumSize").toBool() )
00338 {
00339 determineNextFile();
00340 return;
00341 }
00342 found++;
00343 }
00344 }
00345
00346 if ( !d->currentItem.plugin->property( "CacheThumbnail" ).toBool() )
00347 {
00348
00349
00350 getOrCreateThumbnail();
00351 return;
00352 }
00353
00354 if ( statResultThumbnail() )
00355 return;
00356
00357 getOrCreateThumbnail();
00358 return;
00359 }
00360 case PreviewJobPrivate::STATE_GETORIG:
00361 {
00362 if (job->error())
00363 {
00364 determineNextFile();
00365 return;
00366 }
00367
00368 createThumbnail( static_cast<KIO::FileCopyJob*>(job)->destURL().path() );
00369 return;
00370 }
00371 case PreviewJobPrivate::STATE_CREATETHUMB:
00372 {
00373 if (!d->tempName.isEmpty())
00374 {
00375 QFile::remove(d->tempName);
00376 d->tempName = QString::null;
00377 }
00378 determineNextFile();
00379 return;
00380 }
00381 }
00382 }
00383
00384 bool PreviewJob::statResultThumbnail()
00385 {
00386 if ( d->thumbPath.isEmpty() )
00387 return false;
00388
00389 KURL url = d->currentItem.item->url();
00390
00391 url.setPass(QString::null);
00392
00393
00394 #ifdef KURL_TRIPLE_SLASH_FILE_PROT
00395 d->origName = url.url();
00396 #else
00397 if (url.protocol() == "file") d->origName = "file://" + url.path();
00398 else d->origName = url.url();
00399 #endif
00400
00401 KMD5 md5( QFile::encodeName( d->origName ) );
00402 d->thumbName = QFile::encodeName( md5.hexDigest() ) + ".png";
00403
00404 QImage thumb;
00405 if ( !thumb.load( d->thumbPath + d->thumbName ) ) return false;
00406
00407 if ( thumb.text( "Thumb::URI", 0 ) != d->origName ||
00408 thumb.text( "Thumb::MTime", 0 ).toInt() != d->tOrig ) return false;
00409
00410
00411 emitPreview( thumb );
00412 d->succeeded = true;
00413 determineNextFile();
00414 return true;
00415 }
00416
00417
00418 void PreviewJob::getOrCreateThumbnail()
00419 {
00420
00421 const KFileItem* item = d->currentItem.item;
00422 const QString localPath = item->localPath();
00423 if ( !localPath.isEmpty() )
00424 createThumbnail( localPath );
00425 else
00426 {
00427 d->state = PreviewJobPrivate::STATE_GETORIG;
00428 KTempFile localFile;
00429 KURL localURL;
00430 localURL.setPath( d->tempName = localFile.name() );
00431 const KURL currentURL = item->url();
00432 KIO::Job * job = KIO::file_copy( currentURL, localURL, -1, true,
00433 false, false );
00434 job->addMetaData("thumbnail","1");
00435 addSubjob(job);
00436 }
00437 }
00438
00439
00440 void PreviewJob::createThumbnail( QString pixPath )
00441 {
00442 d->state = PreviewJobPrivate::STATE_CREATETHUMB;
00443 KURL thumbURL;
00444 thumbURL.setProtocol("thumbnail");
00445 thumbURL.setPath(pixPath);
00446 KIO::TransferJob *job = KIO::get(thumbURL, false, false);
00447 addSubjob(job);
00448 connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)), SLOT(slotThumbData(KIO::Job *, const QByteArray &)));
00449 bool save = d->bSave && d->currentItem.plugin->property("CacheThumbnail").toBool();
00450 job->addMetaData("mimeType", d->currentItem.item->mimetype());
00451 job->addMetaData("width", QString().setNum(save ? d->cacheWidth : d->width));
00452 job->addMetaData("height", QString().setNum(save ? d->cacheHeight : d->height));
00453 job->addMetaData("iconSize", QString().setNum(save ? 64 : d->iconSize));
00454 job->addMetaData("iconAlpha", QString().setNum(d->iconAlpha));
00455 job->addMetaData("plugin", d->currentItem.plugin->library());
00456 #ifdef Q_OS_UNIX
00457 if (d->shmid == -1)
00458 {
00459 if (d->shmaddr) {
00460 shmdt((char*)d->shmaddr);
00461 shmctl(d->shmid, IPC_RMID, 0);
00462 }
00463 d->shmid = shmget(IPC_PRIVATE, d->cacheWidth * d->cacheHeight * 4, IPC_CREAT|0600);
00464 if (d->shmid != -1)
00465 {
00466 d->shmaddr = (uchar *)(shmat(d->shmid, 0, SHM_RDONLY));
00467 if (d->shmaddr == (uchar *)-1)
00468 {
00469 shmctl(d->shmid, IPC_RMID, 0);
00470 d->shmaddr = 0;
00471 d->shmid = -1;
00472 }
00473 }
00474 else
00475 d->shmaddr = 0;
00476 }
00477 if (d->shmid != -1)
00478 job->addMetaData("shmid", QString().setNum(d->shmid));
00479 #endif
00480 }
00481
00482 void PreviewJob::slotThumbData(KIO::Job *, const QByteArray &data)
00483 {
00484 bool save = d->bSave &&
00485 d->currentItem.plugin->property("CacheThumbnail").toBool() &&
00486 (d->currentItem.item->url().protocol() != "file" ||
00487 !d->currentItem.item->url().directory( false ).startsWith(d->thumbRoot));
00488 QImage thumb;
00489 #ifdef Q_OS_UNIX
00490 if (d->shmaddr)
00491 {
00492 QDataStream str(data, IO_ReadOnly);
00493 int width, height, depth;
00494 bool alpha;
00495 str >> width >> height >> depth >> alpha;
00496 thumb = QImage(d->shmaddr, width, height, depth, 0, 0, QImage::IgnoreEndian);
00497 thumb.setAlphaBuffer(alpha);
00498 }
00499 else
00500 #endif
00501 thumb.loadFromData(data);
00502
00503 if (save)
00504 {
00505 thumb.setText("Thumb::URI", 0, d->origName);
00506 thumb.setText("Thumb::MTime", 0, QString::number(d->tOrig));
00507 thumb.setText("Thumb::Size", 0, number(d->currentItem.item->size()));
00508 thumb.setText("Thumb::Mimetype", 0, d->currentItem.item->mimetype());
00509 thumb.setText("Software", 0, "KDE Thumbnail Generator");
00510 KTempFile temp(d->thumbPath + "kde-tmp-", ".png");
00511 if (temp.status() == 0)
00512 {
00513 thumb.save(temp.name(), "PNG");
00514 rename(QFile::encodeName(temp.name()), QFile::encodeName(d->thumbPath + d->thumbName));
00515 }
00516 }
00517 emitPreview( thumb );
00518 d->succeeded = true;
00519 }
00520
00521 void PreviewJob::emitPreview(const QImage &thumb)
00522 {
00523 QPixmap pix;
00524 if (thumb.width() > d->width || thumb.height() > d->height)
00525 {
00526 double imgRatio = (double)thumb.height() / (double)thumb.width();
00527 if (imgRatio > (double)d->height / (double)d->width)
00528 pix.convertFromImage(
00529 thumb.smoothScale((int)QMAX((double)d->height / imgRatio, 1), d->height));
00530 else pix.convertFromImage(
00531 thumb.smoothScale(d->width, (int)QMAX((double)d->width * imgRatio, 1)));
00532 }
00533 else pix.convertFromImage(thumb);
00534 emit gotPreview(d->currentItem.item, pix);
00535 }
00536
00537 void PreviewJob::emitFailed(const KFileItem *item)
00538 {
00539 if (!item)
00540 item = d->currentItem.item;
00541 emit failed(item);
00542 }
00543
00544 QStringList PreviewJob::availablePlugins()
00545 {
00546 QStringList result;
00547 KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
00548 for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00549 if (!result.contains((*it)->desktopEntryName()))
00550 result.append((*it)->desktopEntryName());
00551 return result;
00552 }
00553
00554 QStringList PreviewJob::supportedMimeTypes()
00555 {
00556 QStringList result;
00557 KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
00558 for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00559 result += (*it)->property("MimeTypes").toStringList();
00560 return result;
00561 }
00562
00563 void PreviewJob::kill( bool quietly )
00564 {
00565 d->startPreviewTimer.stop();
00566 Job::kill( quietly );
00567 }
00568
00569 PreviewJob *KIO::filePreview( const KFileItemList &items, int width, int height,
00570 int iconSize, int iconAlpha, bool scale, bool save,
00571 const QStringList *enabledPlugins )
00572 {
00573 return new PreviewJob(items, width, height, iconSize, iconAlpha,
00574 scale, save, enabledPlugins);
00575 }
00576
00577 PreviewJob *KIO::filePreview( const KURL::List &items, int width, int height,
00578 int iconSize, int iconAlpha, bool scale, bool save,
00579 const QStringList *enabledPlugins )
00580 {
00581 KFileItemList fileItems;
00582 for (KURL::List::ConstIterator it = items.begin(); it != items.end(); ++it)
00583 fileItems.append(new KFileItem(KFileItem::Unknown, KFileItem::Unknown, *it, true));
00584 return new PreviewJob(fileItems, width, height, iconSize, iconAlpha,
00585 scale, save, enabledPlugins, true);
00586 }
00587
00588 void PreviewJob::virtual_hook( int id, void* data )
00589 { KIO::Job::virtual_hook( id, data ); }
00590