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