File indexing completed on 2025-01-26 04:14:59
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 "ComicCoverImageProvider.h" 0023 0024 #include <KRar.h> 0025 #include <KZip> 0026 #include <karchive.h> 0027 #include <karchivefile.h> 0028 #include <kiconloader.h> 0029 0030 #include <QIcon> 0031 #include <QMimeDatabase> 0032 #include <QMutex> 0033 #include <QThreadPool> 0034 0035 #include <qtquick_debug.h> 0036 0037 class ComicCoverImageProvider::Private { 0038 public: 0039 Private() { 0040 // We'll just stick with a 100MiB cache for now - something to expose later maybe? 0041 imageCache = new KImageCache("peruse-comiccover", 104857600); 0042 } 0043 ~Private() { 0044 delete imageCache; 0045 } 0046 KImageCache* imageCache; 0047 }; 0048 0049 ComicCoverImageProvider::ComicCoverImageProvider() 0050 : QQuickAsyncImageProvider() 0051 , d(new Private) 0052 { 0053 } 0054 0055 ComicCoverImageProvider::~ComicCoverImageProvider() 0056 { 0057 delete d; 0058 } 0059 0060 class ComicCoverResponse : public QQuickImageResponse 0061 { 0062 public: 0063 ComicCoverResponse(const QString &id, const QSize &requestedSize, KImageCache* imageCache) 0064 { 0065 m_runnable = new ComicCoverRunnable(id, requestedSize, imageCache); 0066 m_runnable->setAutoDelete(false); 0067 connect(m_runnable, &ComicCoverRunnable::done, this, &ComicCoverResponse::handleDone, Qt::QueuedConnection); 0068 connect(this, &QQuickImageResponse::finished, m_runnable, &QObject::deleteLater, Qt::QueuedConnection); 0069 QThreadPool::globalInstance()->start(m_runnable); 0070 } 0071 0072 void handleDone(QImage image) { 0073 m_image = image; 0074 Q_EMIT finished(); 0075 } 0076 0077 QQuickTextureFactory *textureFactory() const override 0078 { 0079 return QQuickTextureFactory::textureFactoryForImage(m_image); 0080 } 0081 0082 void cancel() override 0083 { 0084 m_runnable->abort(); 0085 } 0086 0087 ComicCoverRunnable* m_runnable{nullptr}; 0088 QImage m_image; 0089 }; 0090 0091 QQuickImageResponse * ComicCoverImageProvider::requestImageResponse(const QString& id, const QSize& requestedSize) 0092 { 0093 ComicCoverResponse* response = new ComicCoverResponse(id, requestedSize, d->imageCache); 0094 return response; 0095 } 0096 0097 0098 class ComicCoverRunnable::Private { 0099 public: 0100 Private() {} 0101 QString id; 0102 QSize requestedSize; 0103 KImageCache* imageCache; 0104 0105 bool abort{false}; 0106 QMutex abortMutex; 0107 bool isAborted() { 0108 QMutexLocker locker(&abortMutex); 0109 return abort; 0110 } 0111 0112 QStringList entries; 0113 void filterImages(QStringList& entries) 0114 { 0115 /// Sort case-insensitive, then remove non-image entries. 0116 QMap<QString, QString> entryMap; 0117 for(const QString& entry : qAsConst(entries)) { 0118 if (entry.endsWith(QLatin1String(".gif"), Qt::CaseInsensitive) || 0119 entry.endsWith(QLatin1String(".jpg"), Qt::CaseInsensitive) || 0120 entry.endsWith(QLatin1String(".jpeg"), Qt::CaseInsensitive) || 0121 entry.endsWith(QLatin1String(".png"), Qt::CaseInsensitive)) { 0122 entryMap.insert(entry.toLower(), entry); 0123 } 0124 } 0125 entries = entryMap.values(); 0126 } 0127 void getArchiveFileList(QStringList& entries, const QString& prefix, const KArchiveDirectory *dir) 0128 { 0129 /// Recursively list all files in the ZIP archive into 'entries'. 0130 for (const QString& entry : dir->entries()) { 0131 const KArchiveEntry *e = dir->entry(entry); 0132 if (e->isDirectory()) { 0133 getArchiveFileList(entries, prefix + entry + '/', 0134 static_cast<const KArchiveDirectory*>(e)); 0135 } else if (e->isFile()) { 0136 entries.append(prefix + entry); 0137 } 0138 } 0139 } 0140 }; 0141 0142 ComicCoverRunnable::ComicCoverRunnable(const QString& id, const QSize& requestedSize, KImageCache* imageCache) 0143 : d(new Private) 0144 { 0145 d->id = id; 0146 d->requestedSize = requestedSize; 0147 d->imageCache = imageCache; 0148 } 0149 0150 ComicCoverRunnable::~ComicCoverRunnable() 0151 { 0152 abort(); 0153 } 0154 0155 void ComicCoverRunnable::abort() 0156 { 0157 QMutexLocker locker(&d->abortMutex); 0158 d->abort = true; 0159 } 0160 0161 void ComicCoverRunnable::run() 0162 { 0163 QSize ourSize(KIconLoader::SizeEnormous, KIconLoader::SizeEnormous); 0164 if(d->requestedSize.width() > 0 && d->requestedSize.height() > 0) 0165 { 0166 ourSize = d->requestedSize; 0167 } 0168 0169 QImage img; 0170 if (!d->imageCache->findImage(d->id, &img)) { 0171 KArchive* archive = nullptr; 0172 QMimeDatabase db; 0173 db.mimeTypeForFile(d->id, QMimeDatabase::MatchContent); 0174 const QMimeType mime = db.mimeTypeForFile(d->id, QMimeDatabase::MatchContent); 0175 if(!d->isAborted() && (mime.inherits("application/x-cbr") || mime.inherits("application/x-rar"))) { 0176 archive = new KRar(d->id); 0177 } else if(!d->isAborted() && (mime.inherits("application/x-cbz") || mime.inherits("application/zip"))) { 0178 archive = new KZip(d->id); 0179 } 0180 // FIXME: This goes elsewhere - see below 0181 // If this code seems familiar, it is adapted from kio-extras/thumbnail/comiccreator.cpp 0182 // The reason being that this code should be removed once our karchive-rar functionality is merged into 0183 // karchive proper. 0184 if(!d->isAborted() && archive && archive->open(QIODevice::ReadOnly)) { 0185 // Get the archive's directory. 0186 const KArchiveDirectory* cArchiveDir = archive->directory(); 0187 if (!d->isAborted() && cArchiveDir) { 0188 QStringList entries; 0189 // Get and filter the entries from the archive. 0190 d->getArchiveFileList(entries, QString(), cArchiveDir); 0191 d->filterImages(entries); 0192 if (!d->isAborted() && !entries.isEmpty()) { 0193 // Extract the cover file. 0194 const KArchiveFile *coverFile = static_cast<const KArchiveFile*>(cArchiveDir->entry(entries[0])); 0195 if (!d->isAborted() && coverFile) { 0196 bool success = img.loadFromData(coverFile->data()); 0197 if(!d->isAborted() && !success) { 0198 QIcon oops = QIcon::fromTheme("unknown"); 0199 img = oops.pixmap(oops.availableSizes().last()).toImage(); 0200 qCDebug(QTQUICK_LOG) << "Failed to load image with id:" << d->id; 0201 } 0202 } 0203 } 0204 } 0205 } 0206 d->imageCache->insertImage(d->id, img); 0207 } 0208 Q_EMIT done(img.scaled(ourSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); 0209 }