mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-24 19:02:53 +00:00
586 lines
19 KiB
C++
586 lines
19 KiB
C++
// vim: set tabstop=4 shiftwidth=4 expandtab:
|
|
/* Gwenview - A simple image viewer for KDE
|
|
Copyright 2000-2007 Aurélien Gâteau <agateau@kde.org>
|
|
This class is based on the ImagePreviewJob class from Konqueror.
|
|
*/
|
|
/* This file is part of the KDE project
|
|
Copyright (C) 2000 David Faure <faure@kde.org>
|
|
2000 Carsten Pfeiffer <pfeiffer@kde.org>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
#include "moc_thumbnailprovider.cpp"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
// Qt
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QImage>
|
|
#include <QPixmap>
|
|
#include <QCryptographicHash>
|
|
|
|
// KDE
|
|
#include <KApplication>
|
|
#include <KDebug>
|
|
#include <kde_file.h>
|
|
#include <KFileItem>
|
|
#include <KIO/JobUiDelegate>
|
|
#include <KIO/PreviewJob>
|
|
#include <KStandardDirs>
|
|
#include <KTemporaryFile>
|
|
|
|
// Local
|
|
#include "mimetypeutils.h"
|
|
#include "thumbnailwriter.h"
|
|
#include "thumbnailgenerator.h"
|
|
#include "urlutils.h"
|
|
|
|
namespace Gwenview
|
|
{
|
|
|
|
#undef ENABLE_LOG
|
|
#undef LOG
|
|
//#define ENABLE_LOG
|
|
#ifdef ENABLE_LOG
|
|
#define LOG(x) kDebug() << x
|
|
#else
|
|
#define LOG(x) ;
|
|
#endif
|
|
|
|
K_GLOBAL_STATIC(ThumbnailWriter, sThumbnailWriter)
|
|
|
|
static QString generateOriginalUri(const KUrl& url_)
|
|
{
|
|
KUrl url = url_;
|
|
// Don't include the password if any
|
|
url.setPass(QString());
|
|
return url.url();
|
|
}
|
|
|
|
static QString generateThumbnailPath(const QString& uri, ThumbnailGroup::Enum group)
|
|
{
|
|
QByteArray md5 = QCryptographicHash::hash(QFile::encodeName(uri), QCryptographicHash::Md5);
|
|
QString baseDir = ThumbnailProvider::thumbnailBaseDir(group);
|
|
return baseDir + QString(QFile::encodeName(md5.toHex())) + ".png";
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
//
|
|
// ThumbnailProvider static methods
|
|
//
|
|
//------------------------------------------------------------------------
|
|
static QString sThumbnailBaseDir;
|
|
QString ThumbnailProvider::thumbnailBaseDir()
|
|
{
|
|
if (sThumbnailBaseDir.isEmpty()) {
|
|
const QByteArray customDir = qgetenv("GV_THUMBNAIL_DIR");
|
|
if (customDir.isEmpty()) {
|
|
sThumbnailBaseDir = QDir::homePath() + "/.thumbnails/";
|
|
} else {
|
|
sThumbnailBaseDir = QString::fromLocal8Bit(customDir.constData()) + '/';
|
|
}
|
|
}
|
|
return sThumbnailBaseDir;
|
|
}
|
|
|
|
void ThumbnailProvider::setThumbnailBaseDir(const QString& dir)
|
|
{
|
|
sThumbnailBaseDir = dir;
|
|
}
|
|
|
|
QString ThumbnailProvider::thumbnailBaseDir(ThumbnailGroup::Enum group)
|
|
{
|
|
QString dir = thumbnailBaseDir();
|
|
switch (group) {
|
|
case ThumbnailGroup::Normal:
|
|
dir += "normal/";
|
|
break;
|
|
case ThumbnailGroup::Large:
|
|
dir += "large/";
|
|
break;
|
|
}
|
|
return dir;
|
|
}
|
|
|
|
void ThumbnailProvider::deleteImageThumbnail(const KUrl& url)
|
|
{
|
|
QString uri = generateOriginalUri(url);
|
|
QFile::remove(generateThumbnailPath(uri, ThumbnailGroup::Normal));
|
|
QFile::remove(generateThumbnailPath(uri, ThumbnailGroup::Large));
|
|
}
|
|
|
|
static void moveThumbnailHelper(const QString& oldUri, const QString& newUri, ThumbnailGroup::Enum group)
|
|
{
|
|
QString oldPath = generateThumbnailPath(oldUri, group);
|
|
QString newPath = generateThumbnailPath(newUri, group);
|
|
QImage thumb;
|
|
if (!thumb.load(oldPath)) {
|
|
return;
|
|
}
|
|
#ifndef QT_NO_IMAGE_TEXT
|
|
thumb.setText("Thumb::URI", newUri);
|
|
#endif
|
|
thumb.save(newPath, "png");
|
|
QFile::remove(QFile::encodeName(oldPath));
|
|
}
|
|
|
|
void ThumbnailProvider::moveThumbnail(const KUrl& oldUrl, const KUrl& newUrl)
|
|
{
|
|
QString oldUri = generateOriginalUri(oldUrl);
|
|
QString newUri = generateOriginalUri(newUrl);
|
|
moveThumbnailHelper(oldUri, newUri, ThumbnailGroup::Normal);
|
|
moveThumbnailHelper(oldUri, newUri, ThumbnailGroup::Large);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
//
|
|
// ThumbnailProvider implementation
|
|
//
|
|
//------------------------------------------------------------------------
|
|
ThumbnailProvider::ThumbnailProvider()
|
|
: KIO::Job()
|
|
, mState(STATE_NEXTTHUMB)
|
|
, mOriginalTime(0)
|
|
{
|
|
LOG(this);
|
|
|
|
// Make sure we have a place to store our thumbnails
|
|
QString thumbnailDirNormal = ThumbnailProvider::thumbnailBaseDir(ThumbnailGroup::Normal);
|
|
QString thumbnailDirLarge = ThumbnailProvider::thumbnailBaseDir(ThumbnailGroup::Large);
|
|
KStandardDirs::makeDir(thumbnailDirNormal, 0700);
|
|
KStandardDirs::makeDir(thumbnailDirLarge, 0700);
|
|
|
|
// Look for images and store the items in our todo list
|
|
mCurrentItem = KFileItem();
|
|
mThumbnailGroup = ThumbnailGroup::Large;
|
|
createNewThumbnailGenerator();
|
|
}
|
|
|
|
ThumbnailProvider::~ThumbnailProvider()
|
|
{
|
|
LOG(this);
|
|
abortSubjob();
|
|
mThumbnailGenerator->cancel();
|
|
disconnect(mThumbnailGenerator, 0, this, 0);
|
|
disconnect(mThumbnailGenerator, 0, sThumbnailWriter, 0);
|
|
connect(mThumbnailGenerator, SIGNAL(finished()), mThumbnailGenerator, SLOT(deleteLater()));
|
|
if (mPreviousThumbnailGenerator) {
|
|
disconnect(mPreviousThumbnailGenerator, 0, sThumbnailWriter, 0);
|
|
}
|
|
sThumbnailWriter->wait();
|
|
}
|
|
|
|
void ThumbnailProvider::stop()
|
|
{
|
|
// Clear mItems and create a new ThumbnailGenerator if mThumbnailGenerator is running,
|
|
// but also make sure that at most two ThumbnailGenerators are running.
|
|
// startCreatingThumbnail() will take care that these two threads won't work on the same item.
|
|
mItems.clear();
|
|
abortSubjob();
|
|
if (mThumbnailGenerator->isRunning() && !mPreviousThumbnailGenerator) {
|
|
mPreviousThumbnailGenerator = mThumbnailGenerator;
|
|
mPreviousThumbnailGenerator->cancel();
|
|
disconnect(mPreviousThumbnailGenerator, 0, this, 0);
|
|
connect(mPreviousThumbnailGenerator, SIGNAL(finished()), mPreviousThumbnailGenerator, SLOT(deleteLater()));
|
|
createNewThumbnailGenerator();
|
|
mCurrentItem = KFileItem();
|
|
}
|
|
}
|
|
|
|
const KFileItemList& ThumbnailProvider::pendingItems() const
|
|
{
|
|
return mItems;
|
|
}
|
|
|
|
void ThumbnailProvider::setThumbnailGroup(ThumbnailGroup::Enum group)
|
|
{
|
|
mThumbnailGroup = group;
|
|
}
|
|
|
|
void ThumbnailProvider::appendItems(const KFileItemList& items)
|
|
{
|
|
if (!mItems.isEmpty()) {
|
|
QSet<QString> itemSet;
|
|
Q_FOREACH(const KFileItem & item, mItems) {
|
|
itemSet.insert(item.url().url());
|
|
}
|
|
|
|
Q_FOREACH(const KFileItem & item, items) {
|
|
if (!itemSet.contains(item.url().url())) {
|
|
mItems.append(item);
|
|
}
|
|
}
|
|
} else {
|
|
mItems = items;
|
|
}
|
|
|
|
if (mCurrentItem.isNull()) {
|
|
determineNextIcon();
|
|
}
|
|
}
|
|
|
|
void ThumbnailProvider::removeItems(const KFileItemList& itemList)
|
|
{
|
|
if (mItems.isEmpty()) {
|
|
return;
|
|
}
|
|
Q_FOREACH(const KFileItem & item, itemList) {
|
|
// If we are removing the next item, update to be the item after or the
|
|
// first if we removed the last item
|
|
mItems.removeAll(item);
|
|
|
|
if (item == mCurrentItem) {
|
|
abortSubjob();
|
|
}
|
|
}
|
|
|
|
// No more current item, carry on to the next remaining item
|
|
if (mCurrentItem.isNull()) {
|
|
determineNextIcon();
|
|
}
|
|
}
|
|
|
|
void ThumbnailProvider::removePendingItems()
|
|
{
|
|
mItems.clear();
|
|
}
|
|
|
|
bool ThumbnailProvider::isRunning() const
|
|
{
|
|
return !mCurrentItem.isNull();
|
|
}
|
|
|
|
//-Internal--------------------------------------------------------------
|
|
void ThumbnailProvider::createNewThumbnailGenerator()
|
|
{
|
|
mThumbnailGenerator = new ThumbnailGenerator;
|
|
connect(mThumbnailGenerator, SIGNAL(done(QImage,QSize)),
|
|
SLOT(thumbnailReady(QImage,QSize)),
|
|
Qt::QueuedConnection);
|
|
|
|
connect(mThumbnailGenerator, SIGNAL(thumbnailReadyToBeCached(QString,QImage)),
|
|
sThumbnailWriter, SLOT(queueThumbnail(QString,QImage)),
|
|
Qt::QueuedConnection);
|
|
}
|
|
|
|
void ThumbnailProvider::abortSubjob()
|
|
{
|
|
if (hasSubjobs()) {
|
|
LOG("Killing subjob");
|
|
KJob* job = subjobs().first();
|
|
job->kill();
|
|
removeSubjob(job);
|
|
mCurrentItem = KFileItem();
|
|
}
|
|
}
|
|
|
|
void ThumbnailProvider::determineNextIcon()
|
|
{
|
|
LOG(this);
|
|
mState = STATE_NEXTTHUMB;
|
|
|
|
// No more items ?
|
|
if (mItems.isEmpty()) {
|
|
LOG("No more items. Nothing to do");
|
|
mCurrentItem = KFileItem();
|
|
finished();
|
|
return;
|
|
}
|
|
|
|
mCurrentItem = mItems.takeFirst();
|
|
LOG("mCurrentItem.url=" << mCurrentItem.url());
|
|
|
|
// First, stat the orig file
|
|
mState = STATE_STATORIG;
|
|
mCurrentUrl = mCurrentItem.url();
|
|
mCurrentUrl.cleanPath();
|
|
mOriginalFileSize = mCurrentItem.size();
|
|
|
|
// Do direct stat instead of using KIO if the file is local (faster)
|
|
bool directStatOk = false;
|
|
if (UrlUtils::urlIsFastLocalFile(mCurrentUrl)) {
|
|
KDE_struct_stat buff;
|
|
if (KDE::stat(mCurrentUrl.toLocalFile(), &buff) == 0) {
|
|
directStatOk = true;
|
|
mOriginalTime = buff.st_mtime;
|
|
QMetaObject::invokeMethod(this, "checkThumbnail", Qt::QueuedConnection);
|
|
}
|
|
}
|
|
if (!directStatOk) {
|
|
KIO::Job* job = KIO::stat(mCurrentUrl, KIO::HideProgressInfo);
|
|
job->ui()->setWindow(KApplication::kApplication()->activeWindow());
|
|
LOG("KIO::stat orig" << mCurrentUrl.url());
|
|
addSubjob(job);
|
|
}
|
|
LOG("/determineNextIcon" << this);
|
|
}
|
|
|
|
void ThumbnailProvider::slotResult(KJob * job)
|
|
{
|
|
LOG(mState);
|
|
removeSubjob(job);
|
|
Q_ASSERT(subjobs().isEmpty()); // We should have only one job at a time
|
|
|
|
switch (mState) {
|
|
case STATE_NEXTTHUMB:
|
|
Q_ASSERT(false);
|
|
determineNextIcon();
|
|
return;
|
|
|
|
case STATE_STATORIG: {
|
|
// Could not stat original, drop this one and move on to the next one
|
|
if (job->error()) {
|
|
emitThumbnailLoadingFailed();
|
|
determineNextIcon();
|
|
return;
|
|
}
|
|
|
|
// Get modification time of the original file
|
|
KIO::UDSEntry entry = static_cast<KIO::StatJob*>(job)->statResult();
|
|
mOriginalTime = entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
|
|
checkThumbnail();
|
|
return;
|
|
}
|
|
|
|
case STATE_DOWNLOADORIG:
|
|
if (job->error()) {
|
|
emitThumbnailLoadingFailed();
|
|
LOG("Delete temp file" << mTempPath);
|
|
QFile::remove(mTempPath);
|
|
mTempPath.clear();
|
|
determineNextIcon();
|
|
} else {
|
|
startCreatingThumbnail(mTempPath);
|
|
}
|
|
return;
|
|
|
|
case STATE_PREVIEWJOB:
|
|
determineNextIcon();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ThumbnailProvider::thumbnailReady(const QImage& _img, const QSize& _size)
|
|
{
|
|
QImage img = _img;
|
|
QSize size = _size;
|
|
if (!img.isNull()) {
|
|
emitThumbnailLoaded(img, size);
|
|
} else {
|
|
emitThumbnailLoadingFailed();
|
|
}
|
|
if (!mTempPath.isEmpty()) {
|
|
LOG("Delete temp file" << mTempPath);
|
|
QFile::remove(mTempPath);
|
|
mTempPath.clear();
|
|
}
|
|
determineNextIcon();
|
|
}
|
|
|
|
QImage ThumbnailProvider::loadThumbnailFromCache() const
|
|
{
|
|
QImage image = sThumbnailWriter->value(mThumbnailPath);
|
|
if (!image.isNull()) {
|
|
return image;
|
|
}
|
|
|
|
image = QImage(mThumbnailPath);
|
|
if (image.isNull() && mThumbnailGroup == ThumbnailGroup::Normal) {
|
|
// If there is a large-sized thumbnail, generate the normal-sized version from it
|
|
QString largeThumbnailPath = generateThumbnailPath(mOriginalUri, ThumbnailGroup::Large);
|
|
QImage largeImage(largeThumbnailPath);
|
|
if (largeImage.isNull()) {
|
|
return image;
|
|
}
|
|
int size = ThumbnailGroup::pixelSize(ThumbnailGroup::Normal);
|
|
image = largeImage.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
#ifndef QT_NO_IMAGE_TEXT
|
|
Q_FOREACH(const QString& key, largeImage.textKeys()) {
|
|
QString text = largeImage.text(key);
|
|
image.setText(key, text);
|
|
}
|
|
#endif
|
|
sThumbnailWriter->queueThumbnail(mThumbnailPath, image);
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
void ThumbnailProvider::checkThumbnail()
|
|
{
|
|
if (mCurrentItem.isNull()) {
|
|
// This can happen if current item has been removed by removeItems()
|
|
determineNextIcon();
|
|
return;
|
|
}
|
|
|
|
// If we are in the thumbnail dir, just load the file
|
|
if (mCurrentUrl.isLocalFile()
|
|
&& mCurrentUrl.directory().startsWith(thumbnailBaseDir())) {
|
|
QImage image(mCurrentUrl.toLocalFile());
|
|
emitThumbnailLoaded(image, image.size());
|
|
determineNextIcon();
|
|
return;
|
|
}
|
|
|
|
mOriginalUri = generateOriginalUri(mCurrentUrl);
|
|
mThumbnailPath = generateThumbnailPath(mOriginalUri, mThumbnailGroup);
|
|
|
|
LOG("Stat thumb" << mThumbnailPath);
|
|
|
|
QImage thumb = loadThumbnailFromCache();
|
|
#ifndef QT_NO_IMAGE_TEXT
|
|
KIO::filesize_t fileSize = thumb.text(QLatin1String("Thumb::Size")).toULongLong();
|
|
if (!thumb.isNull()) {
|
|
if (thumb.text(QLatin1String("Thumb::URI")) == mOriginalUri &&
|
|
thumb.text(QLatin1String("Thumb::MTime")).toInt() == mOriginalTime &&
|
|
(fileSize == 0 || fileSize == mOriginalFileSize)) {
|
|
int width = 0, height = 0;
|
|
QSize size;
|
|
bool ok;
|
|
|
|
width = thumb.text(QLatin1String("Thumb::Image::Width")).toInt(&ok);
|
|
if (ok) height = thumb.text(QLatin1String("Thumb::Image::Height")).toInt(&ok);
|
|
if (ok) {
|
|
size = QSize(width, height);
|
|
} else {
|
|
kWarning() << "Thumbnail for" << mOriginalUri << "does not contain correct image size information";
|
|
KFileMetaInfo fmi(mCurrentUrl);
|
|
if (fmi.isValid()) {
|
|
KFileMetaInfoItem item = fmi.item("Dimensions");
|
|
if (item.isValid()) {
|
|
size = item.value().toSize();
|
|
} else {
|
|
kWarning() << "KFileMetaInfoItem for" << mOriginalUri << "did not get image size information";
|
|
}
|
|
} else {
|
|
kWarning() << "Could not get a valid KFileMetaInfo instance for" << mOriginalUri;
|
|
}
|
|
}
|
|
emitThumbnailLoaded(thumb, size);
|
|
determineNextIcon();
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Thumbnail not found or not valid
|
|
if (MimeTypeUtils::fileItemKind(mCurrentItem) == MimeTypeUtils::KIND_RASTER_IMAGE) {
|
|
if (mCurrentUrl.isLocalFile()) {
|
|
// Original is a local file, create the thumbnail
|
|
startCreatingThumbnail(mCurrentUrl.toLocalFile());
|
|
} else {
|
|
// Original is remote, download it
|
|
mState = STATE_DOWNLOADORIG;
|
|
|
|
KTemporaryFile tempFile;
|
|
tempFile.setAutoRemove(false);
|
|
if (!tempFile.open()) {
|
|
kWarning() << "Couldn't create temp file to download " << mCurrentUrl.prettyUrl();
|
|
emitThumbnailLoadingFailed();
|
|
determineNextIcon();
|
|
return;
|
|
}
|
|
mTempPath = tempFile.fileName();
|
|
|
|
KUrl url;
|
|
url.setPath(mTempPath);
|
|
KIO::Job* job = KIO::file_copy(mCurrentUrl, url, -1, KIO::Overwrite | KIO::HideProgressInfo);
|
|
job->ui()->setWindow(KApplication::kApplication()->activeWindow());
|
|
LOG("Download remote file" << mCurrentUrl.prettyUrl() << "to" << url.pathOrUrl());
|
|
addSubjob(job);
|
|
}
|
|
} else {
|
|
// Not a raster image, use a KPreviewJob
|
|
LOG("Starting a KPreviewJob for" << mCurrentItem.url());
|
|
mState = STATE_PREVIEWJOB;
|
|
KFileItemList list;
|
|
list.append(mCurrentItem);
|
|
const int pixelSize = ThumbnailGroup::pixelSize(mThumbnailGroup);
|
|
if (mPreviewPlugins.isEmpty()) {
|
|
mPreviewPlugins = KIO::PreviewJob::availablePlugins();
|
|
}
|
|
KIO::Job* job = KIO::filePreview(list, QSize(pixelSize, pixelSize), &mPreviewPlugins);
|
|
//job->ui()->setWindow(KApplication::kApplication()->activeWindow());
|
|
connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)),
|
|
this, SLOT(slotGotPreview(KFileItem,QPixmap)));
|
|
connect(job, SIGNAL(failed(KFileItem)),
|
|
this, SLOT(emitThumbnailLoadingFailed()));
|
|
addSubjob(job);
|
|
}
|
|
}
|
|
|
|
void ThumbnailProvider::startCreatingThumbnail(const QString& pixPath)
|
|
{
|
|
LOG("Creating thumbnail from" << pixPath);
|
|
// If mPreviousThumbnailGenerator is already working on our current item
|
|
// its thumbnail will be passed to sThumbnailWriter when ready. So we
|
|
// connect mPreviousThumbnailGenerator's signal "finished" to determineNextIcon
|
|
// which will load the thumbnail from sThumbnailWriter or from disk
|
|
// (because we re-add mCurrentItem to mItems).
|
|
if (mPreviousThumbnailGenerator && mPreviousThumbnailGenerator->isRunning() &&
|
|
mOriginalUri == mPreviousThumbnailGenerator->originalUri() &&
|
|
mOriginalTime == mPreviousThumbnailGenerator->originalTime() &&
|
|
mOriginalFileSize == mPreviousThumbnailGenerator->originalFileSize() &&
|
|
mCurrentItem.mimetype() == mPreviousThumbnailGenerator->originalMimeType()) {
|
|
connect(mPreviousThumbnailGenerator, SIGNAL(finished()), SLOT(determineNextIcon()));
|
|
mItems.prepend(mCurrentItem);
|
|
return;
|
|
}
|
|
mThumbnailGenerator->load(mOriginalUri, mOriginalTime, mOriginalFileSize,
|
|
mCurrentItem.mimetype(), pixPath, mThumbnailPath, mThumbnailGroup);
|
|
}
|
|
|
|
void ThumbnailProvider::slotGotPreview(const KFileItem& item, const QPixmap& pixmap)
|
|
{
|
|
if (mCurrentItem.isNull()) {
|
|
// This can happen if current item has been removed by removeItems()
|
|
return;
|
|
}
|
|
LOG(mCurrentItem.url());
|
|
QSize size;
|
|
emit thumbnailLoaded(item, pixmap, size, mOriginalFileSize);
|
|
}
|
|
|
|
void ThumbnailProvider::emitThumbnailLoaded(const QImage& img, const QSize& size)
|
|
{
|
|
if (mCurrentItem.isNull()) {
|
|
// This can happen if current item has been removed by removeItems()
|
|
return;
|
|
}
|
|
LOG(mCurrentItem.url());
|
|
QPixmap thumb = QPixmap::fromImage(img);
|
|
emit thumbnailLoaded(mCurrentItem, thumb, size, mOriginalFileSize);
|
|
}
|
|
|
|
void ThumbnailProvider::emitThumbnailLoadingFailed()
|
|
{
|
|
if (mCurrentItem.isNull()) {
|
|
// This can happen if current item has been removed by removeItems()
|
|
return;
|
|
}
|
|
LOG(mCurrentItem.url());
|
|
emit thumbnailLoadingFailed(mCurrentItem);
|
|
}
|
|
|
|
bool ThumbnailProvider::isThumbnailWriterEmpty()
|
|
{
|
|
return sThumbnailWriter->isEmpty();
|
|
}
|
|
|
|
} // namespace
|