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 <sys/stat.h>
00025 #ifdef __FreeBSD__
00026 #include <machine/param.h>
00027 #endif
00028 #include <sys/types.h>
00029 #include <sys/ipc.h>
00030 #include <sys/shm.h>
00031
00032 #include <qdir.h>
00033 #include <qfile.h>
00034 #include <qimage.h>
00035 #include <qtimer.h>
00036 #include <qregexp.h>
00037
00038 #include <kdatastream.h>
00039 #include <kfileitem.h>
00040 #include <kapplication.h>
00041 #include <ktempfile.h>
00042 #include <ktrader.h>
00043 #include <kmdcodec.h>
00044 #include <kglobal.h>
00045 #include <kstandarddirs.h>
00046
00047 #include <kio/kservice.h>
00048
00049 #include "previewjob.moc"
00050
00051 namespace KIO { struct PreviewItem; }
00052 using namespace KIO;
00053
00054 struct KIO::PreviewItem
00055 {
00056 KFileItem *item;
00057 KService::Ptr plugin;
00058 };
00059
00060 struct KIO::PreviewJobPrivate
00061 {
00062 enum { STATE_STATORIG,
00063 STATE_GETORIG,
00064 STATE_CREATETHUMB
00065 } state;
00066 KFileItemList initialItems;
00067 const QStringList *enabledPlugins;
00068
00069 QValueList<PreviewItem> items;
00070
00071 PreviewItem currentItem;
00072
00073 time_t tOrig;
00074
00075 QString thumbPath;
00076
00077
00078 QString origName;
00079
00080 QString thumbName;
00081
00082 int width;
00083 int height;
00084
00085 int cacheWidth;
00086 int cacheHeight;
00087
00088 bool bScale;
00089
00090 bool bSave;
00091
00092 QString tempName;
00093
00094 unsigned long maximumSize;
00095
00096 int iconSize;
00097
00098 int iconAlpha;
00099
00100
00101 int shmid;
00102
00103 uchar *shmaddr;
00104
00105 bool deleteItems;
00106 bool succeeded;
00107
00108 QString thumbRoot;
00109 };
00110
00111 PreviewJob::PreviewJob( const KFileItemList &items, int width, int height,
00112 int iconSize, int iconAlpha, bool scale, bool save,
00113 const QStringList *enabledPlugins, bool deleteItems )
00114 : KIO::Job( false )
00115 {
00116 d = new PreviewJobPrivate;
00117 d->tOrig = 0;
00118 d->shmid = -1;
00119 d->shmaddr = 0;
00120 d->initialItems = items;
00121 d->enabledPlugins = enabledPlugins;
00122 d->width = width;
00123 d->height = height ? height : width;
00124 d->cacheWidth = d->width;
00125 d->cacheHeight = d->height;
00126 d->iconSize = iconSize;
00127 d->iconAlpha = iconAlpha;
00128 d->deleteItems = deleteItems;
00129 d->bScale = scale;
00130 d->bSave = save && scale;
00131 d->succeeded = false;
00132 d->currentItem.item = 0;
00133 d->thumbRoot = QDir::homeDirPath() + "/.thumbnails/";
00134
00135
00136 QTimer::singleShot(0, this, SLOT(startPreview()));
00137 }
00138
00139 PreviewJob::~PreviewJob()
00140 {
00141 if (d->shmaddr) {
00142 shmdt((char*)d->shmaddr);
00143 shmctl(d->shmid, IPC_RMID, 0);
00144 }
00145 delete d;
00146 }
00147
00148 void PreviewJob::startPreview()
00149 {
00150
00151 KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
00152 QMap<QString, KService::Ptr> mimeMap;
00153
00154 for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00155 if (!d->enabledPlugins || d->enabledPlugins->contains((*it)->desktopEntryName()))
00156 {
00157 QStringList mimeTypes = (*it)->property("MimeTypes").toStringList();
00158 for (QStringList::ConstIterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt)
00159 mimeMap.insert(*mt, *it);
00160 }
00161
00162
00163 bool bNeedCache = false;
00164 for (KFileItemListIterator it(d->initialItems); it.current(); ++it )
00165 {
00166 PreviewItem item;
00167 item.item = it.current();
00168 QMap<QString, KService::Ptr>::ConstIterator plugin = mimeMap.find(it.current()->mimetype());
00169 if (plugin == mimeMap.end())
00170 {
00171 QString mimeType = it.current()->mimetype();
00172 plugin = mimeMap.find(mimeType.replace(QRegExp("/.*"), "/*"));
00173 }
00174 if (plugin != mimeMap.end())
00175 {
00176 item.plugin = *plugin;
00177 d->items.append(item);
00178 if (!bNeedCache && d->bSave &&
00179 (it.current()->url().protocol() != "file" ||
00180 !it.current()->url().directory( false ).startsWith(d->thumbRoot)) &&
00181 (*plugin)->property("CacheThumbnail").toBool())
00182 bNeedCache = true;
00183 }
00184 else
00185 {
00186 emitFailed(it.current());
00187 if (d->deleteItems)
00188 delete it.current();
00189 }
00190 }
00191
00192
00193 KConfig * config = KGlobal::config();
00194 KConfigGroupSaver cgs( config, "PreviewSettings" );
00195 d->maximumSize = config->readNumEntry( "MaximumSize", 1024*1024 );
00196
00197 if (bNeedCache)
00198 {
00199 if (d->width <= 128 && d->height <= 128) d->cacheWidth = d->cacheHeight = 128;
00200 else d->cacheWidth = d->cacheHeight = 256;
00201 d->thumbPath = d->thumbRoot + (d->cacheWidth == 128 ? "normal/" : "large/");
00202 KStandardDirs::makeDir(d->thumbPath, 0700);
00203 }
00204 else
00205 d->bSave = false;
00206 determineNextFile();
00207 }
00208
00209 void PreviewJob::removeItem( const KFileItem *item )
00210 {
00211 for (QValueList<PreviewItem>::Iterator it = d->items.begin(); it != d->items.end(); ++it)
00212 if ((*it).item == item)
00213 {
00214 d->items.remove(it);
00215 break;
00216 }
00217
00218 if (d->currentItem.item == item)
00219 {
00220 subjobs.first()->kill();
00221 subjobs.removeFirst();
00222 determineNextFile();
00223 }
00224 }
00225
00226 void PreviewJob::determineNextFile()
00227 {
00228 if (d->currentItem.item)
00229 {
00230 if (!d->succeeded)
00231 emitFailed();
00232 if (d->deleteItems)
00233 delete d->currentItem.item;
00234 }
00235
00236 if ( d->items.isEmpty() )
00237 {
00238 emitResult();
00239 return;
00240 }
00241 else
00242 {
00243
00244 d->state = PreviewJobPrivate::STATE_STATORIG;
00245 d->currentItem = d->items.first();
00246 d->succeeded = false;
00247 d->items.remove(d->items.begin());
00248 KIO::Job *job = KIO::stat( d->currentItem.item->url(), false );
00249 addSubjob(job);
00250 }
00251 }
00252
00253 void PreviewJob::slotResult( KIO::Job *job )
00254 {
00255 subjobs.remove( job );
00256 Q_ASSERT ( subjobs.isEmpty() );
00257 switch ( d->state )
00258 {
00259 case PreviewJobPrivate::STATE_STATORIG:
00260 {
00261 if (job->error())
00262 {
00263
00264 determineNextFile();
00265 return;
00266 }
00267 KIO::UDSEntry entry = ((KIO::StatJob*)job)->statResult();
00268 KIO::UDSEntry::ConstIterator it = entry.begin();
00269 d->tOrig = 0;
00270 int found = 0;
00271 for( ; it != entry.end() && found < 2; it++ )
00272 {
00273 if ( (*it).m_uds == KIO::UDS_MODIFICATION_TIME )
00274 {
00275 d->tOrig = (time_t)((*it).m_long);
00276 found++;
00277 }
00278 else if ( (*it).m_uds == KIO::UDS_SIZE )
00279 {
00280 if ( filesize_t((*it).m_long) > d->maximumSize &&
00281 !d->currentItem.plugin->property("IgnoreMaximumSize").toBool() )
00282 {
00283 determineNextFile();
00284 return;
00285 }
00286 found++;
00287 }
00288 }
00289
00290 if ( !d->currentItem.plugin->property( "CacheThumbnail" ).toBool() )
00291 {
00292
00293
00294 getOrCreateThumbnail();
00295 return;
00296 }
00297
00298 if ( statResultThumbnail() )
00299 return;
00300
00301 getOrCreateThumbnail();
00302 return;
00303 }
00304 case PreviewJobPrivate::STATE_GETORIG:
00305 {
00306 if (job->error())
00307 {
00308 determineNextFile();
00309 return;
00310 }
00311
00312 createThumbnail( static_cast<KIO::FileCopyJob*>(job)->destURL().path() );
00313 return;
00314 }
00315 case PreviewJobPrivate::STATE_CREATETHUMB:
00316 {
00317 if (!d->tempName.isEmpty())
00318 {
00319 QFile::remove(d->tempName);
00320 d->tempName = QString::null;
00321 }
00322 determineNextFile();
00323 return;
00324 }
00325 }
00326 }
00327
00328 bool PreviewJob::statResultThumbnail()
00329 {
00330 if ( d->thumbPath.isEmpty() )
00331 return false;
00332
00333 KURL url = d->currentItem.item->url();
00334
00335 url.setPass(QString::null);
00336
00337
00338 if (url.protocol() == "file") d->origName = "file://" + url.path();
00339 else d->origName = url.url();
00340
00341 KMD5 md5( QFile::encodeName( d->origName ) );
00342 d->thumbName = QFile::encodeName( md5.hexDigest() ) + ".png";
00343
00344 QImage thumb;
00345 if ( !thumb.load( d->thumbPath + d->thumbName ) ) return false;
00346
00347 if ( thumb.text( "Thumb::URI", 0 ) != d->origName ||
00348 thumb.text( "Thumb::MTime", 0 ).toInt() != d->tOrig ) return false;
00349
00350
00351 emitPreview( thumb );
00352 d->succeeded = true;
00353 determineNextFile();
00354 return true;
00355 }
00356
00357
00358 void PreviewJob::getOrCreateThumbnail()
00359 {
00360
00361 KURL currentURL = d->currentItem.item->url();
00362 if ( currentURL.isLocalFile() )
00363 createThumbnail( currentURL.path() );
00364 else
00365 {
00366 d->state = PreviewJobPrivate::STATE_GETORIG;
00367 KTempFile localFile;
00368 KURL localURL;
00369 localURL.setPath( d->tempName = localFile.name() );
00370 KIO::Job * job = KIO::file_copy( currentURL, localURL, -1, true,
00371 false, false );
00372 job->addMetaData("thumbnail","1");
00373 addSubjob(job);
00374 }
00375 }
00376
00377 void PreviewJob::createThumbnail( QString pixPath )
00378 {
00379 d->state = PreviewJobPrivate::STATE_CREATETHUMB;
00380 KURL thumbURL;
00381 thumbURL.setProtocol("thumbnail");
00382 thumbURL.setPath(pixPath);
00383 KIO::TransferJob *job = KIO::get(thumbURL, false, false);
00384 addSubjob(job);
00385 connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)), SLOT(slotThumbData(KIO::Job *, const QByteArray &)));
00386 bool save = d->bSave && d->currentItem.plugin->property("CacheThumbnail").toBool();
00387 job->addMetaData("mimeType", d->currentItem.item->mimetype());
00388 job->addMetaData("width", QString().setNum(save ? d->cacheWidth : d->width));
00389 job->addMetaData("height", QString().setNum(save ? d->cacheHeight : d->height));
00390 job->addMetaData("iconSize", QString().setNum(save ? 64 : d->iconSize));
00391 job->addMetaData("iconAlpha", QString().setNum(d->iconAlpha));
00392 job->addMetaData("plugin", d->currentItem.plugin->library());
00393 if (d->shmid == -1)
00394 {
00395 if (d->shmaddr) {
00396 shmdt((char*)d->shmaddr);
00397 shmctl(d->shmid, IPC_RMID, 0);
00398 }
00399 d->shmid = shmget(IPC_PRIVATE, d->cacheWidth * d->cacheHeight * 4, IPC_CREAT|0600);
00400 if (d->shmid != -1)
00401 {
00402 d->shmaddr = static_cast<uchar *>(shmat(d->shmid, 0, SHM_RDONLY));
00403 if (d->shmaddr == (uchar *)-1)
00404 {
00405 shmctl(d->shmid, IPC_RMID, 0);
00406 d->shmaddr = 0;
00407 d->shmid = -1;
00408 }
00409 }
00410 else
00411 d->shmaddr = 0;
00412 }
00413 if (d->shmid != -1)
00414 job->addMetaData("shmid", QString().setNum(d->shmid));
00415 }
00416
00417 void PreviewJob::slotThumbData(KIO::Job *, const QByteArray &data)
00418 {
00419 bool save = d->bSave &&
00420 d->currentItem.plugin->property("CacheThumbnail").toBool() &&
00421 (d->currentItem.item->url().protocol() != "file" ||
00422 !d->currentItem.item->url().directory( false ).startsWith(d->thumbRoot));
00423 QImage thumb;
00424 if (d->shmaddr)
00425 {
00426 QDataStream str(data, IO_ReadOnly);
00427 int width, height, depth;
00428 bool alpha;
00429 str >> width >> height >> depth >> alpha;
00430 thumb = QImage(d->shmaddr, width, height, depth, 0, 0, QImage::IgnoreEndian);
00431 thumb.setAlphaBuffer(alpha);
00432 }
00433 else thumb.loadFromData(data);
00434 if (save)
00435 {
00436 thumb.setText("Thumb::URI", 0, d->origName);
00437 thumb.setText("Thumb::MTime", 0, QString::number(d->tOrig));
00438 thumb.setText("Thumb::Size", 0, number(d->currentItem.item->size()));
00439 thumb.setText("Thumb::Mimetype", 0, d->currentItem.item->mimetype());
00440 thumb.setText("Software", 0, "KDE Thumbnail Generator");
00441 KTempFile temp(d->thumbPath + "kde-tmp-", ".png");
00442 if (temp.status() == 0)
00443 {
00444 thumb.save(temp.name(), "PNG");
00445 rename(QFile::encodeName(temp.name()), QFile::encodeName(d->thumbPath + d->thumbName));
00446 }
00447 }
00448 emitPreview( thumb );
00449 d->succeeded = true;
00450 }
00451
00452 void PreviewJob::emitPreview(const QImage &thumb)
00453 {
00454 QPixmap pix;
00455 if (thumb.width() > d->width || thumb.height() > d->height)
00456 {
00457 double imgRatio = (double)thumb.height() / (double)thumb.width();
00458 if (imgRatio > (double)d->height / (double)d->width)
00459 pix.convertFromImage(
00460 thumb.smoothScale((int)QMAX((double)d->height / imgRatio, 1), d->height));
00461 else pix.convertFromImage(
00462 thumb.smoothScale(d->width, (int)QMAX((double)d->width * imgRatio, 1)));
00463 }
00464 else pix.convertFromImage(thumb);
00465 emit gotPreview(d->currentItem.item, pix);
00466 }
00467
00468 void PreviewJob::emitFailed(const KFileItem *item)
00469 {
00470 if (!item)
00471 item = d->currentItem.item;
00472 emit failed(item);
00473 }
00474
00475 QStringList PreviewJob::availablePlugins()
00476 {
00477 QStringList result;
00478 KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
00479 for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00480 if (!result.contains((*it)->desktopEntryName()))
00481 result.append((*it)->desktopEntryName());
00482 return result;
00483 }
00484
00485 QStringList PreviewJob::supportedMimeTypes()
00486 {
00487 QStringList result;
00488 KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
00489 for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00490 result += (*it)->property("MimeTypes").toStringList();
00491 return result;
00492 }
00493
00494 PreviewJob *KIO::filePreview( const KFileItemList &items, int width, int height,
00495 int iconSize, int iconAlpha, bool scale, bool save,
00496 const QStringList *enabledPlugins )
00497 {
00498 return new PreviewJob(items, width, height, iconSize, iconAlpha,
00499 scale, save, enabledPlugins);
00500 }
00501
00502 PreviewJob *KIO::filePreview( const KURL::List &items, int width, int height,
00503 int iconSize, int iconAlpha, bool scale, bool save,
00504 const QStringList *enabledPlugins )
00505 {
00506 KFileItemList fileItems;
00507 for (KURL::List::ConstIterator it = items.begin(); it != items.end(); ++it)
00508 fileItems.append(new KFileItem(KFileItem::Unknown, KFileItem::Unknown, *it, true));
00509 return new PreviewJob(fileItems, width, height, iconSize, iconAlpha,
00510 scale, save, enabledPlugins, true);
00511 }
00512
00513 void PreviewJob::virtual_hook( int id, void* data )
00514 { KIO::Job::virtual_hook( id, data ); }
00515