3 /* This file is part of the KDE libraries
4 Copyright (C) 2000 David Faure <faure@kde.org>
5 2000 Carsten Pfeiffer <pfeiffer@kde.org>
6 2001 Malte Starostik <malte.starostik@t-online.de>
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
24 #include "previewjob.h"
28 #include <machine/param.h>
30 #include <sys/types.h>
37 #include <QtCore/QDir>
38 #include <QtCore/QFile>
39 #include <QtGui/QImage>
40 #include <QtCore/QTimer>
41 #include <QtCore/QRegExp>
43 #include <kfileitem.h>
44 #include <kapplication.h>
46 #include <ktemporaryfile.h>
47 #include <kservicetypetrader.h>
50 #include <kstandarddirs.h>
52 #include <QtCore/QLinkedList>
53 #include <kconfiggroup.h>
55 #include "jobuidelegate.h"
58 namespace KIO
{ struct PreviewItem
; }
61 struct KIO::PreviewItem
67 class KIO::PreviewJobPrivate
: public KIO::JobPrivate
70 enum { STATE_STATORIG
, // if the thumbnail exists
71 STATE_GETORIG
, // if we create it
72 STATE_CREATETHUMB
// thumbnail:/ slave
76 KFileItemList initialItems
;
77 QStringList enabledPlugins
;
79 // We remove the first item at every step, so use QLinkedList
80 QLinkedList
<PreviewItem
> items
;
82 PreviewItem currentItem
;
83 // The modification time of that URL
85 // Path to thumbnail cache for the current size
87 // Original URL of current item in TMS format
88 // (file:///path/to/file instead of file:/path/to/file)
90 // Thumbnail file name for current item
95 // Unscaled size of thumbnail (128 or 256 if cache is enabled)
98 // Whether the thumbnail should be scaled
100 // Whether we should save the thumbnail
102 bool ignoreMaximumSize
;
104 // If the file to create a thumb for was a temp file, this is its name
106 // Over that, it's too much
107 KIO::filesize_t maximumSize
;
108 // the size for the icon overlay
110 // the transparency of the blended mimetype icon
112 // Shared memory segment Id. The segment is allocated to a size
113 // of extent x extent x 4 (32 bit image) on first need.
117 // Root of thumbnail cache
120 void getOrCreateThumbnail();
121 bool statResultThumbnail();
122 void createThumbnail( const QString
& );
123 void determineNextFile();
124 void emitPreview(const QImage
&thumb
);
127 void slotThumbData(KIO::Job
*, const QByteArray
&);
129 Q_DECLARE_PUBLIC(PreviewJob
)
132 PreviewJob::PreviewJob( const KFileItemList
&items
, int width
, int height
,
133 int iconSize
, int iconAlpha
, bool scale
, bool save
,
134 const QStringList
*enabledPlugins
)
135 : KIO::Job(*new PreviewJobPrivate
)
141 d
->initialItems
= items
;
142 d
->enabledPlugins
= enabledPlugins
? *enabledPlugins
: QStringList();
144 d
->height
= height
? height
: width
;
145 d
->cacheWidth
= d
->width
;
146 d
->cacheHeight
= d
->height
;
147 d
->iconSize
= iconSize
;
148 d
->iconAlpha
= iconAlpha
;
150 d
->bSave
= save
&& scale
;
151 d
->succeeded
= false;
152 d
->thumbRoot
= QDir::homePath() + "/.thumbnails/";
153 d
->ignoreMaximumSize
= false;
155 // Return to event loop first, determineNextFile() might delete this;
156 QTimer::singleShot(0, this, SLOT(startPreview()));
159 PreviewJob::~PreviewJob()
164 shmdt((char*)d
->shmaddr
);
165 shmctl(d
->shmid
, IPC_RMID
, 0);
170 void PreviewJobPrivate::startPreview()
173 // Load the list of plugins to determine which mimetypes are supported
174 const KService::List plugins
= KServiceTypeTrader::self()->query("ThumbCreator");
175 QMap
<QString
, KService::Ptr
> mimeMap
;
177 for (KService::List::ConstIterator it
= plugins
.constBegin(); it
!= plugins
.constEnd(); ++it
) {
178 if (enabledPlugins
.isEmpty() || enabledPlugins
.contains((*it
)->desktopEntryName()))
180 const QStringList mimeTypes
= (*it
)->serviceTypes();
181 for (QStringList::ConstIterator mt
= mimeTypes
.constBegin(); mt
!= mimeTypes
.constEnd(); ++mt
)
182 mimeMap
.insert(*mt
, *it
);
186 // Look for images and store the items in our todo list :)
187 bool bNeedCache
= false;
188 KFileItemList::const_iterator kit
= initialItems
.constBegin();
189 const KFileItemList::const_iterator kend
= initialItems
.constEnd();
190 for ( ; kit
!= kend
; ++kit
)
194 const QString mimeType
= item
.item
.mimetype();
195 QMap
<QString
, KService::Ptr
>::ConstIterator plugin
= mimeMap
.constFind(mimeType
);
196 if (plugin
== mimeMap
.constEnd())
199 QString groupMimeType
= mimeType
;
200 groupMimeType
.replace(QRegExp("/.*"), "/*");
201 plugin
= mimeMap
.constFind(groupMimeType
);
203 if (plugin
== mimeMap
.constEnd())
205 // check mime type inheritance, resolve aliases
206 const KMimeType::Ptr mimeInfo
= KMimeType::mimeType(mimeType
, KMimeType::ResolveAliases
);
208 const QStringList parentMimeTypes
= mimeInfo
->allParentMimeTypes();
209 Q_FOREACH(const QString
& parentMimeType
, parentMimeTypes
) {
210 plugin
= mimeMap
.constFind(parentMimeType
);
211 if (plugin
!= mimeMap
.constEnd()) break;
215 #if 0 // KDE4: should be covered by inheritance above, all text mimetypes inherit from text/plain
216 // if that's not enough, we need to invent something else
217 if (plugin
== mimeMap
.end())
219 // check X-KDE-Text property
220 KMimeType::Ptr mimeInfo
= KMimeType::mimeType(mimeType
);
221 QVariant textProperty
= mimeInfo
->property("X-KDE-text");
222 if (textProperty
.isValid() && textProperty
.type() == QVariant::Bool
)
224 if (textProperty
.toBool())
226 plugin
= mimeMap
.find("text/plain");
227 if (plugin
== mimeMap
.end())
229 plugin
= mimeMap
.find( "text/*" );
237 if (plugin
!= mimeMap
.constEnd())
239 item
.plugin
= *plugin
;
241 if (!bNeedCache
&& bSave
&&
242 ((*kit
).url().protocol() != "file" ||
243 !(*kit
).url().directory( KUrl::AppendTrailingSlash
).startsWith(thumbRoot
)) &&
244 (*plugin
)->property("CacheThumbnail").toBool())
249 emit q
->failed( *kit
);
253 // Read configuration value for the maximum allowed size
254 maximumSize
= PreviewJob::maximumFileSize();
258 if (width
<= 128 && height
<= 128) cacheWidth
= cacheHeight
= 128;
259 else cacheWidth
= cacheHeight
= 256;
260 thumbPath
= thumbRoot
+ (cacheWidth
== 128 ? "normal/" : "large/");
261 KStandardDirs::makeDir(thumbPath
, 0700);
266 initialItems
.clear();
270 void PreviewJob::removeItem( const KUrl
& url
)
273 for (QLinkedList
<PreviewItem
>::Iterator it
= d
->items
.begin(); it
!= d
->items
.end(); ++it
)
274 if ((*it
).item
.url() == url
)
280 if (d
->currentItem
.item
.url() == url
)
282 KJob
* job
= subjobs().first();
285 d
->determineNextFile();
289 void PreviewJob::setIgnoreMaximumSize(bool ignoreSize
)
291 d_func()->ignoreMaximumSize
= ignoreSize
;
294 void PreviewJobPrivate::determineNextFile()
297 if (!currentItem
.item
.isNull())
300 emit q
->failed( currentItem
.item
);
303 if ( items
.isEmpty() )
310 // First, stat the orig file
311 state
= PreviewJobPrivate::STATE_STATORIG
;
312 currentItem
= items
.first();
315 KIO::Job
*job
= KIO::stat( currentItem
.item
.url(), KIO::HideProgressInfo
);
316 job
->addMetaData( "no-auth-prompt", "true" );
321 void PreviewJob::slotResult( KJob
*job
)
326 Q_ASSERT ( !hasSubjobs() ); // We should have only one job at a time ...
329 case PreviewJobPrivate::STATE_STATORIG
:
331 if (job
->error()) // that's no good news...
333 // Drop this one and move on to the next one
334 d
->determineNextFile();
337 const KIO::UDSEntry entry
= static_cast<KIO::StatJob
*>(job
)->statResult();
338 d
->tOrig
= entry
.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME
, 0 );
339 if ( !d
->ignoreMaximumSize
&&
340 (KIO::filesize_t
)entry
.numberValue( KIO::UDSEntry::UDS_SIZE
, 0 ) > d
->maximumSize
&&
341 !d
->currentItem
.plugin
->property("IgnoreMaximumSize").toBool()
343 d
->determineNextFile();
347 if ( !d
->currentItem
.plugin
->property( "CacheThumbnail" ).toBool() )
349 // This preview will not be cached, no need to look for a saved thumbnail
350 // Just create it, and be done
351 d
->getOrCreateThumbnail();
355 if ( d
->statResultThumbnail() )
358 d
->getOrCreateThumbnail();
361 case PreviewJobPrivate::STATE_GETORIG
:
365 d
->determineNextFile();
369 d
->createThumbnail( static_cast<KIO::FileCopyJob
*>(job
)->destUrl().toLocalFile() );
372 case PreviewJobPrivate::STATE_CREATETHUMB
:
374 if (!d
->tempName
.isEmpty())
376 QFile::remove(d
->tempName
);
379 d
->determineNextFile();
385 bool PreviewJobPrivate::statResultThumbnail()
387 if ( thumbPath
.isEmpty() )
390 KUrl url
= currentItem
.item
.url();
391 // Don't include the password if any
392 url
.setPass(QString());
393 origName
= url
.url();
395 KMD5
md5( QFile::encodeName( origName
) );
396 thumbName
= QFile::encodeName( md5
.hexDigest() ) + ".png";
399 if ( !thumb
.load( thumbPath
+ thumbName
) ) return false;
401 if ( thumb
.text( "Thumb::URI", 0 ) != origName
||
402 thumb
.text( "Thumb::MTime", 0 ).toInt() != tOrig
) return false;
405 emitPreview( thumb
);
412 void PreviewJobPrivate::getOrCreateThumbnail()
415 // We still need to load the orig file ! (This is getting tedious) :)
416 const KFileItem
& item
= currentItem
.item
;
417 const QString localPath
= item
.localPath();
418 if ( !localPath
.isEmpty() )
419 createThumbnail( localPath
);
422 state
= PreviewJobPrivate::STATE_GETORIG
;
423 KTemporaryFile localFile
;
424 localFile
.setAutoRemove(false);
427 localURL
.setPath( tempName
= localFile
.fileName() );
428 const KUrl currentURL
= item
.url();
429 KIO::Job
* job
= KIO::file_copy( currentURL
, localURL
, -1, KIO::Overwrite
| KIO::HideProgressInfo
/* No GUI */ );
430 job
->addMetaData("thumbnail","1");
435 void PreviewJobPrivate::createThumbnail( const QString
&pixPath
)
438 state
= PreviewJobPrivate::STATE_CREATETHUMB
;
440 thumbURL
.setProtocol("thumbnail");
441 thumbURL
.setPath(pixPath
);
442 KIO::TransferJob
*job
= KIO::get(thumbURL
, NoReload
, HideProgressInfo
);
444 q
->connect(job
, SIGNAL(data(KIO::Job
*, const QByteArray
&)), SLOT(slotThumbData(KIO::Job
*, const QByteArray
&)));
445 bool save
= bSave
&& currentItem
.plugin
->property("CacheThumbnail").toBool();
446 job
->addMetaData("mimeType", currentItem
.item
.mimetype());
447 job
->addMetaData("width", QString().setNum(save
? cacheWidth
: width
));
448 job
->addMetaData("height", QString().setNum(save
? cacheHeight
: height
));
449 job
->addMetaData("iconSize", QString().setNum(save
? 64 : iconSize
));
450 job
->addMetaData("iconAlpha", QString().setNum(iconAlpha
));
451 job
->addMetaData("plugin", currentItem
.plugin
->library());
456 shmdt((char*)shmaddr
);
457 shmctl(shmid
, IPC_RMID
, 0);
459 shmid
= shmget(IPC_PRIVATE
, cacheWidth
* cacheHeight
* 4, IPC_CREAT
|0600);
462 shmaddr
= (uchar
*)(shmat(shmid
, 0, SHM_RDONLY
));
463 if (shmaddr
== (uchar
*)-1)
465 shmctl(shmid
, IPC_RMID
, 0);
474 job
->addMetaData("shmid", QString().setNum(shmid
));
478 void PreviewJobPrivate::slotThumbData(KIO::Job
*, const QByteArray
&data
)
481 currentItem
.plugin
->property("CacheThumbnail").toBool() &&
482 (currentItem
.item
.url().protocol() != "file" ||
483 !currentItem
.item
.url().directory( KUrl::AppendTrailingSlash
).startsWith(thumbRoot
));
488 // Keep this in sync with kdebase/kioslave/thumbnail.cpp
489 QDataStream
str(data
);
492 str
>> width
>> height
>> iFormat
;
493 QImage::Format format
= static_cast<QImage::Format
>( iFormat
);
494 thumb
= QImage(shmaddr
, width
, height
, format
);
498 thumb
.loadFromData(data
);
500 if (thumb
.isNull()) {
507 thumb
.setText("Thumb::URI", origName
);
508 thumb
.setText("Thumb::MTime", QString::number(tOrig
));
509 thumb
.setText("Thumb::Size", number(currentItem
.item
.size()));
510 thumb
.setText("Thumb::Mimetype", currentItem
.item
.mimetype());
511 thumb
.setText("Software", "KDE Thumbnail Generator");
513 temp
.setPrefix(thumbPath
+ "kde-tmp-");
514 temp
.setSuffix(".png");
515 temp
.setAutoRemove(false);
516 if (temp
.open()) //Only try to write out the thumbnail if we
517 { //actually created the temp file.
518 thumb
.save(temp
.fileName(), "PNG");
519 KDE::rename(temp
.fileName(), thumbPath
+ thumbName
);
522 emitPreview( thumb
);
526 void PreviewJobPrivate::emitPreview(const QImage
&thumb
)
530 if (thumb
.width() > width
|| thumb
.height() > height
)
531 pix
= QPixmap::fromImage( thumb
.scaled(QSize(width
, height
), Qt::KeepAspectRatio
, Qt::SmoothTransformation
) );
533 pix
= QPixmap::fromImage( thumb
);
534 emit q
->gotPreview(currentItem
.item
, pix
);
537 QStringList
PreviewJob::availablePlugins()
540 const KService::List plugins
= KServiceTypeTrader::self()->query("ThumbCreator");
541 for (KService::List::ConstIterator it
= plugins
.begin(); it
!= plugins
.end(); ++it
)
542 if (!result
.contains((*it
)->desktopEntryName()))
543 result
.append((*it
)->desktopEntryName());
547 QStringList
PreviewJob::supportedMimeTypes()
550 const KService::List plugins
= KServiceTypeTrader::self()->query("ThumbCreator");
551 for (KService::List::ConstIterator it
= plugins
.begin(); it
!= plugins
.end(); ++it
)
552 result
+= (*it
)->serviceTypes();
556 PreviewJob
*KIO::filePreview( const KFileItemList
&items
, int width
, int height
,
557 int iconSize
, int iconAlpha
, bool scale
, bool save
,
558 const QStringList
*enabledPlugins
)
560 return new PreviewJob(items
, width
, height
, iconSize
, iconAlpha
,
561 scale
, save
, enabledPlugins
);
564 PreviewJob
*KIO::filePreview( const KUrl::List
&items
, int width
, int height
,
565 int iconSize
, int iconAlpha
, bool scale
, bool save
,
566 const QStringList
*enabledPlugins
)
568 KFileItemList fileItems
;
569 for (KUrl::List::ConstIterator it
= items
.begin(); it
!= items
.end(); ++it
) {
570 Q_ASSERT( (*it
).isValid() ); // please call us with valid urls only
571 fileItems
.append(KFileItem(KFileItem::Unknown
, KFileItem::Unknown
, *it
, true));
573 return new PreviewJob(fileItems
, width
, height
, iconSize
, iconAlpha
,
574 scale
, save
, enabledPlugins
);
577 KIO::filesize_t
PreviewJob::maximumFileSize()
579 KConfigGroup
cg( KGlobal::config(), "PreviewSettings" );
580 return cg
.readEntry( "MaximumSize", 5*1024*1024LL /* 5MB */ );
583 #include "previewjob.moc"