diff --git a/lib/thumbnailprovider/thumbnailgenerator.cpp b/lib/thumbnailprovider/thumbnailgenerator.cpp index bb6f3a74..aec58dd3 100644 --- a/lib/thumbnailprovider/thumbnailgenerator.cpp +++ b/lib/thumbnailprovider/thumbnailgenerator.cpp @@ -1,308 +1,321 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2012 Aurélien Gâteau 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, Cambridge, MA 02110-1301, USA. */ // Self #include "thumbnailgenerator.h" // Local #include "jpegcontent.h" #include "gwenviewconfig.h" #include "exiv2imageloader.h" // KDE #include "gwenview_lib_debug.h" #ifdef KDCRAW_FOUND #include #endif // Qt #include #include #include +#include namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) //qCDebug(GWENVIEW_LIB_LOG) << x #else #define LOG(x) ; #endif //------------------------------------------------------------------------ // // ThumbnailContext // //------------------------------------------------------------------------ bool ThumbnailContext::load(const QString &pixPath, int pixelSize) { mImage = QImage(); mNeedCaching = true; QImage originalImage; QSize originalSize; QByteArray formatHint = pixPath.section(QLatin1Char('.'), -1).toLocal8Bit().toLower(); QImageReader reader(pixPath); JpegContent content; QByteArray format; QByteArray data; QBuffer buffer; int previewRatio = 1; #ifdef KDCRAW_FOUND // raw images deserve special treatment if (KDcrawIface::KDcraw::rawFilesList().contains(QString::fromLatin1(formatHint))) { // use KDCraw to extract the preview bool ret = KDcrawIface::KDcraw::loadEmbeddedPreview(data, pixPath); // We need QImage. Loading JpegContent from QImage - exif lost // Loading QImage from JpegContent - unimplemented, would go with loadFromData if (!ret) { // if the embedded preview loading failed, load half preview instead. That's slower... if (!KDcrawIface::KDcraw::loadHalfPreview(data, pixPath)) { qCWarning(GWENVIEW_LIB_LOG) << "unable to get preview for " << pixPath.toUtf8().constData(); return false; } previewRatio = 2; } // And we need JpegContent too because of EXIF (orientation!). if (!content.loadFromData(data)) { qCWarning(GWENVIEW_LIB_LOG) << "unable to load preview for " << pixPath.toUtf8().constData(); return false; } buffer.setBuffer(&data); buffer.open(QIODevice::ReadOnly); reader.setDevice(&buffer); reader.setFormat(formatHint); } else { #else { #endif if (!reader.canRead()) { reader.setDecideFormatFromContent(true); // Set filename again, otherwise QImageReader won't restart from scratch reader.setFileName(pixPath); } if (reader.format() == "jpeg" && GwenviewConfig::applyExifOrientation()) { content.load(pixPath); } } // If there's jpeg content (from jpg or raw files), try to load an embedded thumbnail, if available. if (!content.rawData().isEmpty()) { QImage thumbnail = content.thumbnail(); // If the user does not care about the generated thumbnails (by deleting them on exit), use ANY // embedded thumnail, even if it's too small. if (!thumbnail.isNull() && (GwenviewConfig::lowResourceUsageMode() || qMax(thumbnail.width(), thumbnail.height()) >= pixelSize) ) { mImage = std::move(thumbnail); mOriginalWidth = content.size().width(); mOriginalHeight = content.size().height(); return true; } } // Generate thumbnail from full image originalSize = reader.size(); if (originalSize.isValid() && reader.supportsOption(QImageIOHandler::ScaledSize) && qMax(originalSize.width(), originalSize.height()) >= pixelSize) { QSizeF scaledSize = originalSize; scaledSize.scale(pixelSize, pixelSize, Qt::KeepAspectRatio); if (!scaledSize.isEmpty()) { reader.setScaledSize(scaledSize.toSize()); } } // Rotate if necessary if (GwenviewConfig::applyExifOrientation()) { reader.setAutoTransform(true); } // format() is empty after QImageReader::read() is called format = reader.format(); if (!reader.read(&originalImage)) { return false; } if (!originalSize.isValid()) { originalSize = originalImage.size(); } mOriginalWidth = originalSize.width() * previewRatio; mOriginalHeight = originalSize.height() * previewRatio; if (qMax(mOriginalWidth, mOriginalHeight) <= pixelSize) { mImage = originalImage; mNeedCaching = format != "png"; } else { mImage = originalImage.scaled(pixelSize, pixelSize, Qt::KeepAspectRatio); } if (reader.autoTransform() && (reader.transformation() & QImageIOHandler::TransformationRotate90)) { qSwap(mOriginalWidth, mOriginalHeight); } return true; } //------------------------------------------------------------------------ // // ThumbnailGenerator // //------------------------------------------------------------------------ ThumbnailGenerator::ThumbnailGenerator() : mCancel(false) -{} +{ + connect(qApp, &QCoreApplication::aboutToQuit, this, [=]() { + wait(); + }, Qt::DirectConnection); + start(); +} void ThumbnailGenerator::load( const QString& originalUri, time_t originalTime, KIO::filesize_t originalFileSize, const QString& originalMimeType, const QString& pixPath, const QString& thumbnailPath, ThumbnailGroup::Enum group) { QMutexLocker lock(&mMutex); Q_ASSERT(mPixPath.isNull()); mOriginalUri = originalUri; mOriginalTime = originalTime; mOriginalFileSize = originalFileSize; mOriginalMimeType = originalMimeType; mPixPath = pixPath; mThumbnailPath = thumbnailPath; mThumbnailGroup = group; - if (!isRunning()) start(); mCond.wakeOne(); } QString ThumbnailGenerator::originalUri() const { return mOriginalUri; } +bool ThumbnailGenerator::isStopped() +{ + QMutexLocker lock(&mMutex); + return mStopped; +} + time_t ThumbnailGenerator::originalTime() const { return mOriginalTime; } KIO::filesize_t ThumbnailGenerator::originalFileSize() const { return mOriginalFileSize; } QString ThumbnailGenerator::originalMimeType() const { return mOriginalMimeType; } bool ThumbnailGenerator::testCancel() { QMutexLocker lock(&mMutex); return mCancel; } void ThumbnailGenerator::cancel() { QMutexLocker lock(&mMutex); mCancel = true; mCond.wakeOne(); } void ThumbnailGenerator::run() { - LOG(""); while (!testCancel()) { QString pixPath; int pixelSize; { QMutexLocker lock(&mMutex); // empty mPixPath means nothing to do - LOG("Waiting for mPixPath"); if (mPixPath.isNull()) { - LOG("mPixPath.isNull"); mCond.wait(&mMutex); } } if (testCancel()) { - return; + break; } { QMutexLocker lock(&mMutex); pixPath = mPixPath; pixelSize = ThumbnailGroup::pixelSize(mThumbnailGroup); } Q_ASSERT(!pixPath.isNull()); LOG("Loading" << pixPath); ThumbnailContext context; bool ok = context.load(pixPath, pixelSize); { QMutexLocker lock(&mMutex); if (ok) { mImage = context.mImage; mOriginalWidth = context.mOriginalWidth; mOriginalHeight = context.mOriginalHeight; if (context.mNeedCaching && mThumbnailGroup <= ThumbnailGroup::Large) { cacheThumbnail(); } } else { // avoid emitting the thumb from the previous successful run mImage = QImage(); qCWarning(GWENVIEW_LIB_LOG) << "Could not generate thumbnail for file" << mOriginalUri; } mPixPath.clear(); // done, ready for next } if (testCancel()) { - return; + break; } { QSize size(mOriginalWidth, mOriginalHeight); LOG("emitting done signal, size=" << size); QMutexLocker lock(&mMutex); emit done(mImage, size); LOG("Done"); } } + LOG("Ending thread"); + + QMutexLocker lock(&mMutex); + mStopped = true; + deleteLater(); } void ThumbnailGenerator::cacheThumbnail() { mImage.setText(QStringLiteral("Thumb::URI") , mOriginalUri); mImage.setText(QStringLiteral("Thumb::MTime") , QString::number(mOriginalTime)); mImage.setText(QStringLiteral("Thumb::Size") , QString::number(mOriginalFileSize)); mImage.setText(QStringLiteral("Thumb::Mimetype") , mOriginalMimeType); mImage.setText(QStringLiteral("Thumb::Image::Width") , QString::number(mOriginalWidth)); mImage.setText(QStringLiteral("Thumb::Image::Height"), QString::number(mOriginalHeight)); mImage.setText(QStringLiteral("Software") , QStringLiteral("Gwenview")); emit thumbnailReadyToBeCached(mThumbnailPath, mImage); } } // namespace diff --git a/lib/thumbnailprovider/thumbnailgenerator.h b/lib/thumbnailprovider/thumbnailgenerator.h index af877763..3b8b59ae 100644 --- a/lib/thumbnailprovider/thumbnailgenerator.h +++ b/lib/thumbnailprovider/thumbnailgenerator.h @@ -1,96 +1,101 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2012 Aurélien Gâteau 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, Cambridge, MA 02110-1301, USA. */ #ifndef THUMBNAILGENERATOR_H #define THUMBNAILGENERATOR_H // Local #include // KDE #include // Qt #include #include #include #include namespace Gwenview { struct ThumbnailContext { QImage mImage; int mOriginalWidth; int mOriginalHeight; bool mNeedCaching; bool load(const QString &pixPath, int pixelSize); }; class ThumbnailGenerator : public QThread { Q_OBJECT public: ThumbnailGenerator(); + // Because we override run(), like you're not really supposed to do, we + // can't trust isRunning() + bool isStopped(); + void load( const QString& originalUri, time_t originalTime, KIO::filesize_t originalFileSize, const QString& originalMimeType, const QString& pixPath, const QString& thumbnailPath, ThumbnailGroup::Enum group); void cancel(); QString originalUri() const; time_t originalTime() const; KIO::filesize_t originalFileSize() const; QString originalMimeType() const; protected: void run() override; Q_SIGNALS: void done(const QImage&, const QSize&); void thumbnailReadyToBeCached(const QString& thumbnailPath, const QImage&); private: bool testCancel(); void cacheThumbnail(); QImage mImage; QString mPixPath; QString mThumbnailPath; QString mOriginalUri; time_t mOriginalTime; KIO::filesize_t mOriginalFileSize; QString mOriginalMimeType; int mOriginalWidth; int mOriginalHeight; QMutex mMutex; QWaitCondition mCond; ThumbnailGroup::Enum mThumbnailGroup; bool mCancel; + bool mStopped = false; }; } // namespace #endif /* THUMBNAILGENERATOR_H */ diff --git a/lib/thumbnailprovider/thumbnailprovider.cpp b/lib/thumbnailprovider/thumbnailprovider.cpp index 9b09ec03..4ccdad93 100644 --- a/lib/thumbnailprovider/thumbnailprovider.cpp +++ b/lib/thumbnailprovider/thumbnailprovider.cpp @@ -1,577 +1,576 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview - A simple image viewer for KDE Copyright 2000-2007 Aurélien Gâteau This class is based on the ImagePreviewJob class from Konqueror. */ /* This file is part of the KDE project Copyright (C) 2000 David Faure 2000 Carsten Pfeiffer 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 "thumbnailprovider.h" #include #include #include // Qt #include #include #include #include #include #include "gwenview_lib_debug.h" #include #include #include // KDE #include #include #include // 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) qCDebug(GWENVIEW_LIB_LOG) << x #else #define LOG(x) ; #endif Q_GLOBAL_STATIC(ThumbnailWriter, sThumbnailWriter) static QString generateOriginalUri(const QUrl &url_) { QUrl url = url_; return url.adjusted(QUrl::RemovePassword).url(); } static QString generateThumbnailPath(const QString& uri, ThumbnailGroup::Enum group) { QString baseDir = ThumbnailProvider::thumbnailBaseDir(group); QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(QFile::encodeName(uri)); return baseDir + QFile::encodeName(QString::fromLatin1(md5.result().toHex())) + QStringLiteral(".png"); } //------------------------------------------------------------------------ // // ThumbnailProvider static methods // //------------------------------------------------------------------------ static QString sThumbnailBaseDir; QString ThumbnailProvider::thumbnailBaseDir() { if (sThumbnailBaseDir.isEmpty()) { const QByteArray customDir = qgetenv("GV_THUMBNAIL_DIR"); if (customDir.isEmpty()) { sThumbnailBaseDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/thumbnails/"); } else { sThumbnailBaseDir = QFile::decodeName(customDir) + QLatin1Char('/'); } } 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 += QStringLiteral("normal/"); break; case ThumbnailGroup::Large: dir += QStringLiteral("large/"); break; case ThumbnailGroup::Large2x: default: dir += "x-gwenview/"; // Should never be hit, but just in case } return dir; } void ThumbnailProvider::deleteImageThumbnail(const QUrl &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; } thumb.setText(QStringLiteral("Thumb::URI"), newUri); thumb.save(newPath, "png"); QFile::remove(QFile::encodeName(oldPath)); } void ThumbnailProvider::moveThumbnail(const QUrl &oldUrl, const QUrl& 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); QDir().mkpath(thumbnailDirNormal); QDir().mkpath(thumbnailDirLarge); QFile::setPermissions(thumbnailDirNormal, QFileDevice::WriteOwner | QFileDevice::ReadOwner | QFileDevice::ExeOwner); QFile::setPermissions(thumbnailDirLarge, QFileDevice::WriteOwner | QFileDevice::ReadOwner | QFileDevice::ExeOwner); // 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, nullptr, this, nullptr); disconnect(mThumbnailGenerator, nullptr, sThumbnailWriter, nullptr); - connect(mThumbnailGenerator, SIGNAL(finished()), mThumbnailGenerator, SLOT(deleteLater())); + abortSubjob(); + mThumbnailGenerator->cancel(); if (mPreviousThumbnailGenerator) { disconnect(mPreviousThumbnailGenerator, nullptr, sThumbnailWriter, nullptr); } 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) { + if (!mThumbnailGenerator->isStopped() && !mPreviousThumbnailGenerator) { mPreviousThumbnailGenerator = mThumbnailGenerator; mPreviousThumbnailGenerator->cancel(); disconnect(mPreviousThumbnailGenerator, nullptr, this, nullptr); 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 itemSet; for (const KFileItem & item : qAsConst(mItems)) { itemSet.insert(item.url().url()); } for (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; } for (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(); emit finished(); return; } mCurrentItem = mItems.takeFirst(); LOG("mCurrentItem.url=" << mCurrentItem.url()); // First, stat the orig file mState = STATE_STATORIG; mCurrentUrl = mCurrentItem.url().adjusted(QUrl::NormalizePathSegments); mOriginalFileSize = mCurrentItem.size(); // Do direct stat instead of using KIO if the file is local (faster) if (UrlUtils::urlIsFastLocalFile(mCurrentUrl)) { QFileInfo fileInfo(mCurrentUrl.toLocalFile()); mOriginalTime = fileInfo.lastModified().toSecsSinceEpoch(); QMetaObject::invokeMethod(this, "checkThumbnail", Qt::QueuedConnection); } else { KIO::Job* job = KIO::stat(mCurrentUrl, KIO::HideProgressInfo); KJobWidgets::setWindow(job, qApp->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(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 { if (mThumbnailGroup > ThumbnailGroup::Large) { return QImage(); } 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); const QStringList textKeys = largeImage.textKeys(); for (const QString& key : textKeys) { QString text = largeImage.text(key); image.setText(key, text); } 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.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path().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(); KIO::filesize_t fileSize = thumb.text(QStringLiteral("Thumb::Size")).toULongLong(); if (!thumb.isNull()) { if (thumb.text(QStringLiteral("Thumb::URI")) == mOriginalUri && thumb.text(QStringLiteral("Thumb::MTime")).toInt() == mOriginalTime && (fileSize == 0 || fileSize == mOriginalFileSize)) { int width = 0, height = 0; QSize size; bool ok; width = thumb.text(QStringLiteral("Thumb::Image::Width")).toInt(&ok); if (ok) height = thumb.text(QStringLiteral("Thumb::Image::Height")).toInt(&ok); if (ok) { size = QSize(width, height); } else { LOG("Thumbnail for" << mOriginalUri << "does not contain correct image size information"); // Don't try to determine the size of a video, it probably won't work and // will cause high I/O usage with big files (bug #307007). if (MimeTypeUtils::urlKind(mCurrentUrl) == MimeTypeUtils::KIND_VIDEO) { emitThumbnailLoaded(thumb, QSize()); determineNextIcon(); return; } } emitThumbnailLoaded(thumb, size); determineNextIcon(); return; } } // 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; QTemporaryFile tempFile; tempFile.setAutoRemove(false); if (!tempFile.open()) { qCWarning(GWENVIEW_LIB_LOG) << "Couldn't create temp file to download " << mCurrentUrl.toDisplayString(); emitThumbnailLoadingFailed(); determineNextIcon(); return; } mTempPath = tempFile.fileName(); QUrl url = QUrl::fromLocalFile(mTempPath); KIO::Job* job = KIO::file_copy(mCurrentUrl, url, -1, KIO::Overwrite | KIO::HideProgressInfo); KJobWidgets::setWindow(job, qApp->activeWindow()); LOG("Download remote file" << mCurrentUrl.toDisplayString() << "to" << url.toDisplayString()); 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); //KJobWidgets::setWindow(job, qApp->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() && + if (mPreviousThumbnailGenerator && !mPreviousThumbnailGenerator->isStopped() && 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