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