File indexing completed on 2025-01-26 04:15:00

0001 /*
0002  * Copyright (C) 2015 Dan Leinir Turthra Jensen <admin@leinir.dk>
0003  *
0004  * This library is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU Lesser General Public
0006  * License as published by the Free Software Foundation; either
0007  * version 2.1 of the License, or (at your option) version 3, or any
0008  * later version accepted by the membership of KDE e.V. (or its
0009  * successor approved by the membership of KDE e.V.), which shall
0010  * act as a proxy defined in Section 6 of version 3 of the license.
0011  *
0012  * This library is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  * Lesser General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU Lesser General Public
0018  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0019  *
0020  */
0021 
0022 #include "PreviewImageProvider.h"
0023 
0024 #include <kiconloader.h>
0025 #include <kio/previewjob.h>
0026 
0027 #include <QCoreApplication>
0028 #include <QDir>
0029 #include <QTimer>
0030 #include <QIcon>
0031 #include <QMimeDatabase>
0032 #include <QMutex>
0033 #include <QThreadPool>
0034 #include <QDebug>
0035 
0036 class PreviewImageProvider::Private
0037 {
0038 public:
0039     Private() {};
0040 };
0041 
0042 PreviewImageProvider::PreviewImageProvider()
0043     : QQuickAsyncImageProvider()
0044     , d(new Private)
0045 {
0046     qRegisterMetaType<KFileItem>("KFileItem");
0047 }
0048 
0049 PreviewImageProvider::~PreviewImageProvider()
0050 {
0051     delete d;
0052 }
0053 
0054 class PreviewResponse : public QQuickImageResponse
0055 {
0056     public:
0057         PreviewResponse(const QString &id, const QSize &requestedSize)
0058         {
0059             m_runnable = new PreviewRunnable(id, requestedSize);
0060             m_runnable->setAutoDelete(false);
0061             connect(m_runnable, &PreviewRunnable::done, this, &PreviewResponse::handleDone, Qt::QueuedConnection);
0062             connect(this, &QQuickImageResponse::finished, m_runnable, &QObject::deleteLater,  Qt::QueuedConnection);
0063             QThreadPool::globalInstance()->start(m_runnable);
0064         }
0065 
0066         void handleDone(QImage image) {
0067             m_image = image;
0068             Q_EMIT finished();
0069         }
0070 
0071         QQuickTextureFactory *textureFactory() const override
0072         {
0073             return QQuickTextureFactory::textureFactoryForImage(m_image);
0074         }
0075 
0076         void cancel() override
0077         {
0078             m_runnable->abort();
0079         }
0080 
0081         PreviewRunnable* m_runnable{nullptr};
0082         QImage m_image;
0083 };
0084 
0085 QQuickImageResponse * PreviewImageProvider::requestImageResponse(const QString& id, const QSize& requestedSize)
0086 {
0087     // We sometimes get malformed IDs (that is, extra slashes at the start), so fix those up
0088     QString adjustedId{id};
0089     while (adjustedId.startsWith("//")) {
0090         adjustedId = adjustedId.mid(1);
0091     }
0092     PreviewResponse* response = new PreviewResponse(adjustedId, requestedSize);
0093     return response;
0094 }
0095 
0096 class PreviewRunnable::Private {
0097 public:
0098     Private() {}
0099     QString id;
0100     QSize requestedSize;
0101 
0102     bool abort{false};
0103     QMutex abortMutex;
0104     bool isAborted() {
0105         QMutexLocker locker(&abortMutex);
0106         return abort;
0107     }
0108 
0109     QImage preview;
0110     QPointer<KIO::PreviewJob> job{nullptr};
0111     QString mimetype;
0112 };
0113 
0114 PreviewRunnable::PreviewRunnable(const QString& id, const QSize& requestedSize)
0115     : d(new Private)
0116 {
0117     d->id = id;
0118     d->requestedSize = requestedSize;
0119 }
0120 
0121 PreviewRunnable::~PreviewRunnable()
0122 {
0123     abort();
0124     delete d;
0125 }
0126 
0127 void PreviewRunnable::run()
0128 {
0129 
0130     QSize ourSize(KIconLoader::SizeEnormous, KIconLoader::SizeEnormous);
0131     if(d->requestedSize.width() > 0 && d->requestedSize.height() > 0)
0132     {
0133         ourSize = d->requestedSize;
0134     }
0135 
0136     if(QFile(d->id).exists())
0137     {
0138         QMimeDatabase db;
0139         QList<QMimeType> mimetypes = db.mimeTypesForFileName(d->id);
0140         if(mimetypes.count() > 0)
0141         {
0142             d->mimetype = mimetypes.first().name();
0143         }
0144 
0145         if(!d->isAborted()) {
0146             static QStringList allPlugins{KIO::PreviewJob::availablePlugins()};
0147             d->job = new KIO::PreviewJob(KFileItemList() << KFileItem(QUrl::fromLocalFile(d->id), d->mimetype, 0), ourSize, &allPlugins);
0148             d->job->setIgnoreMaximumSize(true);
0149             d->job->setScaleType(KIO::PreviewJob::ScaledAndCached);
0150             connect(d->job, &KIO::PreviewJob::gotPreview, this, &PreviewRunnable::updatePreview);
0151             connect(d->job, &KIO::PreviewJob::failed, this, &PreviewRunnable::fallbackPreview);
0152             connect(d->job, &KIO::PreviewJob::finished, this, &PreviewRunnable::finishedPreview);
0153             d->job->start();
0154 
0155             QTimer* breaker = new QTimer();
0156             breaker->moveToThread(thread());
0157             breaker->setParent(this);
0158             breaker->setSingleShot(true);
0159             breaker->setInterval(3000);
0160             connect(breaker, &QTimer::timeout, this, [this](){
0161                 if (!d->isAborted()) {
0162                     abort();
0163                 }
0164             });
0165             QTimer::singleShot(0, breaker, [breaker](){ breaker->start(); });
0166         } else {
0167             finishedPreview(nullptr);
0168         }
0169     }
0170     else
0171     {
0172         finishedPreview(nullptr);
0173     }
0174 }
0175 
0176 void PreviewRunnable::abort()
0177 {
0178     if (d->job) {
0179         QMutexLocker locker(&d->abortMutex);
0180         d->abort = true;
0181         locker.unlock();
0182         d->job->kill();
0183     }
0184 }
0185 
0186 void PreviewRunnable::fallbackPreview(const KFileItem& item)
0187 {
0188     KIO::PreviewJob* previewJob = qobject_cast<KIO::PreviewJob*>(sender());
0189     if(previewJob)
0190     {
0191         QMimeDatabase db;
0192         QIcon mimeIcon = QIcon::fromTheme(db.mimeTypeForName(item.mimetype()).iconName());
0193         QSize actualSize = mimeIcon.actualSize(d->requestedSize);
0194         d->preview = mimeIcon.pixmap(actualSize).toImage();
0195     }
0196 }
0197 
0198 void PreviewRunnable::updatePreview(const KFileItem&, const QPixmap& p)
0199 {
0200     KIO::PreviewJob* previewJob = qobject_cast<KIO::PreviewJob*>(sender());
0201     if(previewJob)
0202     {
0203         d->preview = p.toImage();
0204     }
0205 }
0206 
0207 void PreviewRunnable::finishedPreview(KJob* /*job*/)
0208 {
0209     if(d->isAborted()) {
0210         if (d->preview.isNull()) {
0211             QMimeDatabase db;
0212             QIcon mimeIcon = QIcon::fromTheme(db.mimeTypeForName(d->mimetype).iconName());
0213             QSize actualSize = mimeIcon.actualSize(d->requestedSize);
0214             d->preview = mimeIcon.pixmap(actualSize).toImage();
0215         }
0216     } else {
0217         if(d->requestedSize.width() > 0 && d->requestedSize.height() > 0) {
0218             d->preview = d->preview.scaled(d->requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
0219         }
0220     }
0221     Q_EMIT done(d->preview);
0222 }