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 <QtCore/QDir>
00038 #include <QtCore/QFile>
00039 #include <QtGui/QImage>
00040 #include <QtCore/QTimer>
00041 #include <QtCore/QRegExp>
00042
00043 #include <kfileitem.h>
00044 #include <kapplication.h>
00045 #include <kde_file.h>
00046 #include <ktemporaryfile.h>
00047 #include <kservicetypetrader.h>
00048 #include <kcodecs.h>
00049 #include <kglobal.h>
00050 #include <kstandarddirs.h>
00051 #include <kservice.h>
00052 #include <QtCore/QLinkedList>
00053 #include <kconfiggroup.h>
00054
00055 #include "jobuidelegate.h"
00056 #include "job_p.h"
00057
00058 namespace KIO { struct PreviewItem; }
00059 using namespace KIO;
00060
00061 struct KIO::PreviewItem
00062 {
00063 KFileItem item;
00064 KService::Ptr plugin;
00065 };
00066
00067 class KIO::PreviewJobPrivate: public KIO::JobPrivate
00068 {
00069 public:
00070 enum { STATE_STATORIG,
00071 STATE_GETORIG,
00072 STATE_CREATETHUMB
00073 } state;
00074 PreviewJob *q;
00075
00076 KFileItemList initialItems;
00077 QStringList enabledPlugins;
00078
00079
00080 QLinkedList<PreviewItem> items;
00081
00082 PreviewItem currentItem;
00083
00084 time_t tOrig;
00085
00086 QString thumbPath;
00087
00088
00089 QString origName;
00090
00091 QString thumbName;
00092
00093 int width;
00094 int height;
00095
00096 int cacheWidth;
00097 int cacheHeight;
00098
00099 bool bScale;
00100
00101 bool bSave;
00102 bool ignoreMaximumSize;
00103 int sequenceIndex;
00104 bool succeeded;
00105
00106 QString tempName;
00107
00108 KIO::filesize_t maximumSize;
00109
00110 int iconSize;
00111
00112 int iconAlpha;
00113
00114
00115 int shmid;
00116
00117 uchar *shmaddr;
00118
00119 QString thumbRoot;
00120
00121 void getOrCreateThumbnail();
00122 bool statResultThumbnail();
00123 void createThumbnail( const QString& );
00124 void determineNextFile();
00125 void emitPreview(const QImage &thumb);
00126
00127 void startPreview();
00128 void slotThumbData(KIO::Job *, const QByteArray &);
00129
00130 Q_DECLARE_PUBLIC(PreviewJob)
00131 };
00132
00133 PreviewJob::PreviewJob( const KFileItemList &items, int width, int height,
00134 int iconSize, int iconAlpha, bool scale, bool save,
00135 const QStringList *enabledPlugins )
00136 : KIO::Job(*new PreviewJobPrivate)
00137 {
00138 Q_D(PreviewJob);
00139 d->tOrig = 0;
00140 d->shmid = -1;
00141 d->shmaddr = 0;
00142 d->initialItems = items;
00143 d->enabledPlugins = enabledPlugins ? *enabledPlugins : QStringList();
00144 d->width = width;
00145 d->height = height ? height : width;
00146 d->cacheWidth = d->width;
00147 d->cacheHeight = d->height;
00148 d->iconSize = iconSize;
00149 d->iconAlpha = iconAlpha;
00150 d->bScale = scale;
00151 d->bSave = save && scale;
00152 d->succeeded = false;
00153 d->thumbRoot = QDir::homePath() + "/.thumbnails/";
00154 d->ignoreMaximumSize = false;
00155 d->sequenceIndex = 0;
00156
00157
00158 QTimer::singleShot(0, this, SLOT(startPreview()));
00159 }
00160
00161 PreviewJob::~PreviewJob()
00162 {
00163 #ifdef Q_OS_UNIX
00164 Q_D(PreviewJob);
00165 if (d->shmaddr) {
00166 shmdt((char*)d->shmaddr);
00167 shmctl(d->shmid, IPC_RMID, 0);
00168 }
00169 #endif
00170 }
00171
00172 void PreviewJobPrivate::startPreview()
00173 {
00174 Q_Q(PreviewJob);
00175
00176 const KService::List plugins = KServiceTypeTrader::self()->query("ThumbCreator");
00177 QMap<QString, KService::Ptr> mimeMap;
00178
00179 for (KService::List::ConstIterator it = plugins.constBegin(); it != plugins.constEnd(); ++it) {
00180 if (enabledPlugins.isEmpty() || enabledPlugins.contains((*it)->desktopEntryName()))
00181 {
00182 const QStringList mimeTypes = (*it)->serviceTypes();
00183 for (QStringList::ConstIterator mt = mimeTypes.constBegin(); mt != mimeTypes.constEnd(); ++mt)
00184 mimeMap.insert(*mt, *it);
00185 }
00186 }
00187
00188
00189 bool bNeedCache = false;
00190 KFileItemList::const_iterator kit = initialItems.constBegin();
00191 const KFileItemList::const_iterator kend = initialItems.constEnd();
00192 for ( ; kit != kend; ++kit )
00193 {
00194 PreviewItem item;
00195 item.item = *kit;
00196 const QString mimeType = item.item.mimetype();
00197 QMap<QString, KService::Ptr>::ConstIterator plugin = mimeMap.constFind(mimeType);
00198 if (plugin == mimeMap.constEnd())
00199
00200 {
00201 QString groupMimeType = mimeType;
00202 groupMimeType.replace(QRegExp("/.*"), "/*");
00203 plugin = mimeMap.constFind(groupMimeType);
00204
00205 if (plugin == mimeMap.constEnd())
00206 {
00207
00208 const KMimeType::Ptr mimeInfo = KMimeType::mimeType(mimeType, KMimeType::ResolveAliases);
00209 if (mimeInfo) {
00210 const QStringList parentMimeTypes = mimeInfo->allParentMimeTypes();
00211 Q_FOREACH(const QString& parentMimeType, parentMimeTypes) {
00212 plugin = mimeMap.constFind(parentMimeType);
00213 if (plugin != mimeMap.constEnd()) break;
00214 }
00215 }
00216 }
00217 #if 0 // KDE4: should be covered by inheritance above, all text mimetypes inherit from text/plain
00218
00219 if (plugin == mimeMap.end())
00220 {
00221
00222 KMimeType::Ptr mimeInfo = KMimeType::mimeType(mimeType);
00223 QVariant textProperty = mimeInfo->property("X-KDE-text");
00224 if (textProperty.isValid() && textProperty.type() == QVariant::Bool)
00225 {
00226 if (textProperty.toBool())
00227 {
00228 plugin = mimeMap.find("text/plain");
00229 if (plugin == mimeMap.end())
00230 {
00231 plugin = mimeMap.find( "text/*" );
00232 }
00233 }
00234 }
00235 }
00236 #endif
00237 }
00238
00239 if (plugin != mimeMap.constEnd())
00240 {
00241 item.plugin = *plugin;
00242 items.append(item);
00243 if (!bNeedCache && bSave &&
00244 ((*kit).url().protocol() != "file" ||
00245 !(*kit).url().directory( KUrl::AppendTrailingSlash ).startsWith(thumbRoot)) &&
00246 (*plugin)->property("CacheThumbnail").toBool())
00247 bNeedCache = true;
00248 }
00249 else
00250 {
00251 emit q->failed( *kit );
00252 }
00253 }
00254
00255
00256 maximumSize = PreviewJob::maximumFileSize();
00257
00258 if (bNeedCache)
00259 {
00260 if (width <= 128 && height <= 128) cacheWidth = cacheHeight = 128;
00261 else cacheWidth = cacheHeight = 256;
00262 thumbPath = thumbRoot + (cacheWidth == 128 ? "normal/" : "large/");
00263 KStandardDirs::makeDir(thumbPath, 0700);
00264 }
00265 else
00266 bSave = false;
00267
00268 initialItems.clear();
00269 determineNextFile();
00270 }
00271
00272 void PreviewJob::removeItem( const KUrl& url )
00273 {
00274 Q_D(PreviewJob);
00275 for (QLinkedList<PreviewItem>::Iterator it = d->items.begin(); it != d->items.end(); ++it)
00276 if ((*it).item.url() == url)
00277 {
00278 d->items.erase(it);
00279 break;
00280 }
00281
00282 if (d->currentItem.item.url() == url)
00283 {
00284 KJob* job = subjobs().first();
00285 job->kill();
00286 removeSubjob( job );
00287 d->determineNextFile();
00288 }
00289 }
00290
00291 void KIO::PreviewJob::setSequenceIndex(int index) {
00292 d_func()->sequenceIndex = index;
00293 }
00294
00295 int KIO::PreviewJob::sequenceIndex() const {
00296 return d_func()->sequenceIndex;
00297 }
00298
00299 void PreviewJob::setIgnoreMaximumSize(bool ignoreSize)
00300 {
00301 d_func()->ignoreMaximumSize = ignoreSize;
00302 }
00303
00304 void PreviewJobPrivate::determineNextFile()
00305 {
00306 Q_Q(PreviewJob);
00307 if (!currentItem.item.isNull())
00308 {
00309 if (!succeeded)
00310 emit q->failed( currentItem.item );
00311 }
00312
00313 if ( items.isEmpty() )
00314 {
00315 q->emitResult();
00316 return;
00317 }
00318 else
00319 {
00320
00321 state = PreviewJobPrivate::STATE_STATORIG;
00322 currentItem = items.first();
00323 succeeded = false;
00324 items.removeFirst();
00325 KIO::Job *job = KIO::stat( currentItem.item.url(), KIO::HideProgressInfo );
00326 job->addMetaData( "no-auth-prompt", "true" );
00327 q->addSubjob(job);
00328 }
00329 }
00330
00331 void PreviewJob::slotResult( KJob *job )
00332 {
00333 Q_D(PreviewJob);
00334
00335 removeSubjob(job);
00336 Q_ASSERT ( !hasSubjobs() );
00337 switch ( d->state )
00338 {
00339 case PreviewJobPrivate::STATE_STATORIG:
00340 {
00341 if (job->error())
00342 {
00343
00344 d->determineNextFile();
00345 return;
00346 }
00347 const KIO::UDSEntry entry = static_cast<KIO::StatJob*>(job)->statResult();
00348 d->tOrig = entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, 0 );
00349 if ( !d->ignoreMaximumSize &&
00350 (KIO::filesize_t)entry.numberValue( KIO::UDSEntry::UDS_SIZE, 0 ) > d->maximumSize &&
00351 !d->currentItem.plugin->property("IgnoreMaximumSize").toBool()
00352 ) {
00353 d->determineNextFile();
00354 return;
00355 }
00356
00357 if ( !d->currentItem.plugin->property( "CacheThumbnail" ).toBool() || d->sequenceIndex )
00358 {
00359
00360
00361 d->getOrCreateThumbnail();
00362 return;
00363 }
00364
00365 if ( d->statResultThumbnail() )
00366 return;
00367
00368 d->getOrCreateThumbnail();
00369 return;
00370 }
00371 case PreviewJobPrivate::STATE_GETORIG:
00372 {
00373 if (job->error())
00374 {
00375 d->determineNextFile();
00376 return;
00377 }
00378
00379 d->createThumbnail( static_cast<KIO::FileCopyJob*>(job)->destUrl().toLocalFile() );
00380 return;
00381 }
00382 case PreviewJobPrivate::STATE_CREATETHUMB:
00383 {
00384 if (!d->tempName.isEmpty())
00385 {
00386 QFile::remove(d->tempName);
00387 d->tempName.clear();
00388 }
00389 d->determineNextFile();
00390 return;
00391 }
00392 }
00393 }
00394
00395 bool PreviewJobPrivate::statResultThumbnail()
00396 {
00397 if ( thumbPath.isEmpty() )
00398 return false;
00399
00400 KUrl url = currentItem.item.url();
00401
00402 url.setPass(QString());
00403 origName = url.url();
00404
00405 KMD5 md5( QFile::encodeName( origName ) );
00406 thumbName = QFile::encodeName( md5.hexDigest() ) + ".png";
00407
00408 QImage thumb;
00409 if ( !thumb.load( thumbPath + thumbName ) ) return false;
00410
00411 if ( thumb.text( "Thumb::URI", 0 ) != origName ||
00412 thumb.text( "Thumb::MTime", 0 ).toInt() != tOrig ) return false;
00413
00414
00415 emitPreview( thumb );
00416 succeeded = true;
00417 determineNextFile();
00418 return true;
00419 }
00420
00421
00422 void PreviewJobPrivate::getOrCreateThumbnail()
00423 {
00424 Q_Q(PreviewJob);
00425
00426 const KFileItem& item = currentItem.item;
00427 const QString localPath = item.localPath();
00428 if ( !localPath.isEmpty() )
00429 createThumbnail( localPath );
00430 else
00431 {
00432 state = PreviewJobPrivate::STATE_GETORIG;
00433 KTemporaryFile localFile;
00434 localFile.setAutoRemove(false);
00435 localFile.open();
00436 KUrl localURL;
00437 localURL.setPath( tempName = localFile.fileName() );
00438 const KUrl currentURL = item.url();
00439 KIO::Job * job = KIO::file_copy( currentURL, localURL, -1, KIO::Overwrite | KIO::HideProgressInfo );
00440 job->addMetaData("thumbnail","1");
00441 q->addSubjob(job);
00442 }
00443 }
00444
00445 void PreviewJobPrivate::createThumbnail( const QString &pixPath )
00446 {
00447 Q_Q(PreviewJob);
00448 state = PreviewJobPrivate::STATE_CREATETHUMB;
00449 KUrl thumbURL;
00450 thumbURL.setProtocol("thumbnail");
00451 thumbURL.setPath(pixPath);
00452 KIO::TransferJob *job = KIO::get(thumbURL, NoReload, HideProgressInfo);
00453 q->addSubjob(job);
00454 q->connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)), SLOT(slotThumbData(KIO::Job *, const QByteArray &)));
00455 bool save = bSave && currentItem.plugin->property("CacheThumbnail").toBool() && !sequenceIndex;
00456 job->addMetaData("mimeType", currentItem.item.mimetype());
00457 job->addMetaData("width", QString().setNum(save ? cacheWidth : width));
00458 job->addMetaData("height", QString().setNum(save ? cacheHeight : height));
00459 job->addMetaData("iconSize", QString().setNum(save ? 64 : iconSize));
00460 job->addMetaData("iconAlpha", QString().setNum(iconAlpha));
00461 job->addMetaData("plugin", currentItem.plugin->library());
00462 if(sequenceIndex)
00463 job->addMetaData("sequence-index", QString().setNum(sequenceIndex));
00464
00465 #ifdef Q_OS_UNIX
00466 if (shmid == -1)
00467 {
00468 if (shmaddr) {
00469 shmdt((char*)shmaddr);
00470 shmctl(shmid, IPC_RMID, 0);
00471 }
00472 shmid = shmget(IPC_PRIVATE, cacheWidth * cacheHeight * 4, IPC_CREAT|0600);
00473 if (shmid != -1)
00474 {
00475 shmaddr = (uchar *)(shmat(shmid, 0, SHM_RDONLY));
00476 if (shmaddr == (uchar *)-1)
00477 {
00478 shmctl(shmid, IPC_RMID, 0);
00479 shmaddr = 0;
00480 shmid = -1;
00481 }
00482 }
00483 else
00484 shmaddr = 0;
00485 }
00486 if (shmid != -1)
00487 job->addMetaData("shmid", QString().setNum(shmid));
00488 #endif
00489 }
00490
00491 void PreviewJobPrivate::slotThumbData(KIO::Job *, const QByteArray &data)
00492 {
00493 bool save = bSave &&
00494 currentItem.plugin->property("CacheThumbnail").toBool() &&
00495 (currentItem.item.url().protocol() != "file" ||
00496 !currentItem.item.url().directory( KUrl::AppendTrailingSlash ).startsWith(thumbRoot)) && !sequenceIndex;
00497 QImage thumb;
00498 #ifdef Q_OS_UNIX
00499 if (shmaddr)
00500 {
00501
00502 QDataStream str(data);
00503 int width, height;
00504 quint8 iFormat;
00505 str >> width >> height >> iFormat;
00506 QImage::Format format = static_cast<QImage::Format>( iFormat );
00507 thumb = QImage(shmaddr, width, height, format ).copy();
00508 }
00509 else
00510 #endif
00511 thumb.loadFromData(data);
00512
00513 if (thumb.isNull()) {
00514 QDataStream s(data);
00515 s >> thumb;
00516 }
00517
00518 QString tempFileName;
00519 bool savedCorrectly = false;
00520 if (save)
00521 {
00522 thumb.setText("Thumb::URI", origName);
00523 thumb.setText("Thumb::MTime", QString::number(tOrig));
00524 thumb.setText("Thumb::Size", number(currentItem.item.size()));
00525 thumb.setText("Thumb::Mimetype", currentItem.item.mimetype());
00526 thumb.setText("Software", "KDE Thumbnail Generator");
00527 KTemporaryFile temp;
00528 temp.setPrefix(thumbPath + "kde-tmp-");
00529 temp.setSuffix(".png");
00530 temp.setAutoRemove(false);
00531 if (temp.open())
00532 {
00533 tempFileName = temp.fileName();
00534 savedCorrectly = thumb.save(tempFileName, "PNG");
00535 }
00536 }
00537 if(savedCorrectly)
00538 {
00539 Q_ASSERT(!tempFileName.isEmpty());
00540 KDE::rename(tempFileName, thumbPath + thumbName);
00541 }
00542 emitPreview( thumb );
00543 succeeded = true;
00544 }
00545
00546 void PreviewJobPrivate::emitPreview(const QImage &thumb)
00547 {
00548 Q_Q(PreviewJob);
00549 QPixmap pix;
00550 if (thumb.width() > width || thumb.height() > height)
00551 pix = QPixmap::fromImage( thumb.scaled(QSize(width, height), Qt::KeepAspectRatio, Qt::SmoothTransformation) );
00552 else
00553 pix = QPixmap::fromImage( thumb );
00554 emit q->gotPreview(currentItem.item, pix);
00555 }
00556
00557 QStringList PreviewJob::availablePlugins()
00558 {
00559 QStringList result;
00560 const KService::List plugins = KServiceTypeTrader::self()->query("ThumbCreator");
00561 for (KService::List::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00562 if (!result.contains((*it)->desktopEntryName()))
00563 result.append((*it)->desktopEntryName());
00564 return result;
00565 }
00566
00567 QStringList PreviewJob::supportedMimeTypes()
00568 {
00569 QStringList result;
00570 const KService::List plugins = KServiceTypeTrader::self()->query("ThumbCreator");
00571 for (KService::List::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00572 result += (*it)->serviceTypes();
00573 return result;
00574 }
00575
00576 PreviewJob *KIO::filePreview( const KFileItemList &items, int width, int height,
00577 int iconSize, int iconAlpha, bool scale, bool save,
00578 const QStringList *enabledPlugins )
00579 {
00580 return new PreviewJob(items, width, height, iconSize, iconAlpha,
00581 scale, save, enabledPlugins);
00582 }
00583
00584 PreviewJob *KIO::filePreview( const KUrl::List &items, int width, int height,
00585 int iconSize, int iconAlpha, bool scale, bool save,
00586 const QStringList *enabledPlugins )
00587 {
00588 KFileItemList fileItems;
00589 for (KUrl::List::ConstIterator it = items.begin(); it != items.end(); ++it) {
00590 Q_ASSERT( (*it).isValid() );
00591 fileItems.append(KFileItem(KFileItem::Unknown, KFileItem::Unknown, *it, true));
00592 }
00593 return new PreviewJob(fileItems, width, height, iconSize, iconAlpha,
00594 scale, save, enabledPlugins);
00595 }
00596
00597 KIO::filesize_t PreviewJob::maximumFileSize()
00598 {
00599 KConfigGroup cg( KGlobal::config(), "PreviewSettings" );
00600 return cg.readEntry( "MaximumSize", 5*1024*1024LL );
00601 }
00602
00603 #include "previewjob.moc"