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 }