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