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 }