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", 2*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 d->currentItem.item = 0L;
00235 }
00236 }
00237
00238
if ( d->items.isEmpty() )
00239 {
00240
emitResult();
00241
return;
00242 }
00243
else
00244 {
00245
00246 d->state = PreviewJobPrivate::STATE_STATORIG;
00247 d->currentItem = d->items.first();
00248 d->succeeded =
false;
00249 d->items.remove(d->items.begin());
00250
KIO::Job *job =
KIO::stat( d->currentItem.item->url(),
false );
00251 job->
addMetaData(
"no-auth-prompt",
"true" );
00252
addSubjob(job);
00253 }
00254 }
00255
00256 void PreviewJob::slotResult(
KIO::Job *job )
00257 {
00258 subjobs.remove( job );
00259 Q_ASSERT ( subjobs.isEmpty() );
00260
switch ( d->state )
00261 {
00262
case PreviewJobPrivate::STATE_STATORIG:
00263 {
00264
if (job->
error())
00265 {
00266
00267 determineNextFile();
00268
return;
00269 }
00270
KIO::UDSEntry entry = ((
KIO::StatJob*)job)->statResult();
00271 KIO::UDSEntry::ConstIterator it = entry.begin();
00272 d->tOrig = 0;
00273
int found = 0;
00274
for( ; it != entry.end() && found < 2; it++ )
00275 {
00276
if ( (*it).m_uds == KIO::UDS_MODIFICATION_TIME )
00277 {
00278 d->tOrig = (time_t)((*it).m_long);
00279 found++;
00280 }
00281
else if ( (*it).m_uds == KIO::UDS_SIZE )
00282 {
00283
if (
filesize_t((*it).m_long) > d->maximumSize &&
00284 !d->currentItem.plugin->property(
"IgnoreMaximumSize").toBool() )
00285 {
00286 determineNextFile();
00287
return;
00288 }
00289 found++;
00290 }
00291 }
00292
00293
if ( !d->currentItem.plugin->property(
"CacheThumbnail" ).toBool() )
00294 {
00295
00296
00297 getOrCreateThumbnail();
00298
return;
00299 }
00300
00301
if ( statResultThumbnail() )
00302
return;
00303
00304 getOrCreateThumbnail();
00305
return;
00306 }
00307
case PreviewJobPrivate::STATE_GETORIG:
00308 {
00309
if (job->
error())
00310 {
00311 determineNextFile();
00312
return;
00313 }
00314
00315 createThumbnail( static_cast<KIO::FileCopyJob*>(job)->destURL().path() );
00316
return;
00317 }
00318
case PreviewJobPrivate::STATE_CREATETHUMB:
00319 {
00320
if (!d->tempName.isEmpty())
00321 {
00322 QFile::remove(d->tempName);
00323 d->tempName = QString::null;
00324 }
00325 determineNextFile();
00326
return;
00327 }
00328 }
00329 }
00330
00331
bool PreviewJob::statResultThumbnail()
00332 {
00333
if ( d->thumbPath.isEmpty() )
00334
return false;
00335
00336
KURL url = d->currentItem.item->url();
00337
00338 url.
setPass(QString::null);
00339
00340
00341
if (url.
protocol() ==
"file") d->origName =
"file://" + url.
path();
00342
else d->origName = url.
url();
00343
00344
KMD5 md5( QFile::encodeName( d->origName ) );
00345 d->thumbName = QFile::encodeName( md5.
hexDigest() ) +
".png";
00346
00347
QImage thumb;
00348
if ( !thumb.load( d->thumbPath + d->thumbName ) )
return false;
00349
00350
if ( thumb.text(
"Thumb::URI", 0 ) != d->origName ||
00351 thumb.text(
"Thumb::MTime", 0 ).toInt() != d->tOrig )
return false;
00352
00353
00354 emitPreview( thumb );
00355 d->succeeded =
true;
00356 determineNextFile();
00357
return true;
00358 }
00359
00360
00361
void PreviewJob::getOrCreateThumbnail()
00362 {
00363
00364
KURL currentURL = d->currentItem.item->url();
00365
if ( currentURL.
isLocalFile() )
00366 createThumbnail( currentURL.
path() );
00367
else
00368 {
00369 d->state = PreviewJobPrivate::STATE_GETORIG;
00370
KTempFile localFile;
00371
KURL localURL;
00372 localURL.
setPath( d->tempName = localFile.
name() );
00373
KIO::Job * job =
KIO::file_copy( currentURL, localURL, -1,
true,
00374
false,
false );
00375 job->
addMetaData(
"thumbnail",
"1");
00376 addSubjob(job);
00377 }
00378 }
00379
00380
00381
void PreviewJob::createThumbnail(
QString pixPath )
00382 {
00383 d->state = PreviewJobPrivate::STATE_CREATETHUMB;
00384
KURL thumbURL;
00385 thumbURL.
setProtocol(
"thumbnail");
00386 thumbURL.
setPath(pixPath);
00387
KIO::TransferJob *job =
KIO::get(thumbURL,
false,
false);
00388
addSubjob(job);
00389 connect(job, SIGNAL(data(
KIO::Job *,
const QByteArray &)), SLOT(slotThumbData(
KIO::Job *,
const QByteArray &)));
00390
bool save = d->bSave && d->currentItem.plugin->property(
"CacheThumbnail").toBool();
00391 job->
addMetaData(
"mimeType", d->currentItem.item->mimetype());
00392 job->
addMetaData(
"width",
QString().setNum(save ? d->cacheWidth : d->width));
00393 job->
addMetaData(
"height",
QString().setNum(save ? d->cacheHeight : d->height));
00394 job->
addMetaData(
"iconSize",
QString().setNum(save ? 64 : d->iconSize));
00395 job->
addMetaData(
"iconAlpha",
QString().setNum(d->iconAlpha));
00396 job->
addMetaData(
"plugin", d->currentItem.plugin->library());
00397
if (d->shmid == -1)
00398 {
00399
if (d->shmaddr) {
00400 shmdt((
char*)d->shmaddr);
00401 shmctl(d->shmid, IPC_RMID, 0);
00402 }
00403 d->shmid = shmget(IPC_PRIVATE, d->cacheWidth * d->cacheHeight * 4, IPC_CREAT|0600);
00404
if (d->shmid != -1)
00405 {
00406 d->shmaddr = static_cast<uchar *>(shmat(d->shmid, 0, SHM_RDONLY));
00407
if (d->shmaddr == (uchar *)-1)
00408 {
00409 shmctl(d->shmid, IPC_RMID, 0);
00410 d->shmaddr = 0;
00411 d->shmid = -1;
00412 }
00413 }
00414
else
00415 d->shmaddr = 0;
00416 }
00417
if (d->shmid != -1)
00418 job->
addMetaData(
"shmid",
QString().setNum(d->shmid));
00419 }
00420
00421
void PreviewJob::slotThumbData(
KIO::Job *,
const QByteArray &data)
00422 {
00423
bool save = d->bSave &&
00424 d->currentItem.plugin->property(
"CacheThumbnail").toBool() &&
00425 (d->currentItem.item->url().protocol() !=
"file" ||
00426 !d->currentItem.item->url().directory(
false ).startsWith(d->thumbRoot));
00427
QImage thumb;
00428
if (d->shmaddr)
00429 {
00430
QDataStream str(data, IO_ReadOnly);
00431
int width, height, depth;
00432
bool alpha;
00433 str >> width >> height >> depth >> alpha;
00434 thumb =
QImage(d->shmaddr, width, height, depth, 0, 0, QImage::IgnoreEndian);
00435 thumb.setAlphaBuffer(alpha);
00436 }
00437
else thumb.loadFromData(data);
00438
if (
save)
00439 {
00440 thumb.setText(
"Thumb::URI", 0, d->origName);
00441 thumb.setText(
"Thumb::MTime", 0, QString::number(d->tOrig));
00442 thumb.setText(
"Thumb::Size", 0,
number(d->currentItem.item->size()));
00443 thumb.setText(
"Thumb::Mimetype", 0, d->currentItem.item->mimetype());
00444 thumb.setText(
"Software", 0,
"KDE Thumbnail Generator");
00445
KTempFile temp(d->thumbPath +
"kde-tmp-",
".png");
00446
if (temp.
status() == 0)
00447 {
00448 thumb.save(temp.
name(),
"PNG");
00449
rename(QFile::encodeName(temp.
name()), QFile::encodeName(d->thumbPath + d->thumbName));
00450 }
00451 }
00452 emitPreview( thumb );
00453 d->succeeded =
true;
00454 }
00455
00456
void PreviewJob::emitPreview(
const QImage &thumb)
00457 {
00458
QPixmap pix;
00459
if (thumb.width() > d->width || thumb.height() > d->height)
00460 {
00461
double imgRatio = (
double)thumb.height() / (
double)thumb.width();
00462
if (imgRatio > (
double)d->height / (
double)d->width)
00463 pix.convertFromImage(
00464 thumb.smoothScale((
int)QMAX((
double)d->height / imgRatio, 1), d->height));
00465
else pix.convertFromImage(
00466 thumb.smoothScale(d->width, (
int)QMAX((
double)d->width * imgRatio, 1)));
00467 }
00468
else pix.convertFromImage(thumb);
00469 emit
gotPreview(d->currentItem.item, pix);
00470 }
00471
00472
void PreviewJob::emitFailed(
const KFileItem *item)
00473 {
00474
if (!item)
00475 item = d->currentItem.item;
00476 emit
failed(item);
00477 }
00478
00479 QStringList PreviewJob::availablePlugins()
00480 {
00481
QStringList result;
00482
KTrader::OfferList plugins = KTrader::self()->query(
"ThumbCreator");
00483
for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00484
if (!result.contains((*it)->desktopEntryName()))
00485 result.append((*it)->desktopEntryName());
00486
return result;
00487 }
00488
00489 QStringList PreviewJob::supportedMimeTypes()
00490 {
00491
QStringList result;
00492
KTrader::OfferList plugins = KTrader::self()->query(
"ThumbCreator");
00493
for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
00494 result += (*it)->property(
"MimeTypes").toStringList();
00495
return result;
00496 }
00497
00498 PreviewJob *
KIO::filePreview(
const KFileItemList &items,
int width,
int height,
00499
int iconSize,
int iconAlpha,
bool scale,
bool save,
00500
const QStringList *enabledPlugins )
00501 {
00502
return new PreviewJob(items, width, height, iconSize, iconAlpha,
00503 scale, save, enabledPlugins);
00504 }
00505
00506 PreviewJob *
KIO::filePreview(
const KURL::List &items,
int width,
int height,
00507
int iconSize,
int iconAlpha,
bool scale,
bool save,
00508
const QStringList *enabledPlugins )
00509 {
00510
KFileItemList fileItems;
00511
for (KURL::List::ConstIterator it = items.begin(); it != items.end(); ++it)
00512 fileItems.append(
new KFileItem(KFileItem::Unknown, KFileItem::Unknown, *it,
true));
00513
return new PreviewJob(fileItems, width, height, iconSize, iconAlpha,
00514 scale, save, enabledPlugins,
true);
00515 }
00516
00517
void PreviewJob::virtual_hook(
int id,
void* data )
00518 { KIO::Job::virtual_hook(
id, data ); }
00519