File indexing completed on 2024-05-12 16:06:33

0001 /*
0002     SPDX-FileCopyrightText: 2007 Tobias Koenig <tokoe@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "document.h"
0008 
0009 #include <QBuffer>
0010 #include <QImage>
0011 #include <QImageReader>
0012 #include <QScopedPointer>
0013 
0014 #include <KLocalizedString>
0015 #include <KTar>
0016 #include <QMimeDatabase>
0017 #include <QMimeType>
0018 #include <kzip.h>
0019 #if WITH_K7ZIP
0020 #include <K7Zip>
0021 #endif
0022 
0023 #include <memory>
0024 
0025 #include <core/page.h>
0026 
0027 #include "debug_comicbook.h"
0028 #include "directory.h"
0029 #include "qnatsort.h"
0030 #include "unrar.h"
0031 
0032 using namespace ComicBook;
0033 
0034 static void imagesInArchive(const QString &prefix, const KArchiveDirectory *dir, QStringList *entries)
0035 {
0036     const QStringList entryList = dir->entries();
0037     for (const QString &file : entryList) {
0038         const KArchiveEntry *e = dir->entry(file);
0039         if (e->isDirectory()) {
0040             imagesInArchive(prefix + file + QLatin1Char('/'), static_cast<const KArchiveDirectory *>(e), entries);
0041         } else if (e->isFile()) {
0042             entries->append(prefix + file);
0043         }
0044     }
0045 }
0046 
0047 Document::Document()
0048     : mDirectory(nullptr)
0049     , mUnrar(nullptr)
0050     , mArchive(nullptr)
0051 {
0052 }
0053 
0054 Document::~Document()
0055 {
0056 }
0057 
0058 bool Document::open(const QString &fileName)
0059 {
0060     close();
0061 
0062     QMimeDatabase db;
0063     const QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent);
0064 
0065     /**
0066      * We have a zip archive
0067      */
0068     if (mime.inherits(QStringLiteral("application/x-cbz")) || mime.inherits(QStringLiteral("application/zip"))) {
0069         mArchive = new KZip(fileName);
0070 
0071         if (!processArchive()) {
0072             return false;
0073         }
0074         /**
0075          * We have a TAR archive
0076          */
0077     } else if (mime.inherits(QStringLiteral("application/x-cbt")) || mime.inherits(QStringLiteral("application/x-gzip")) || mime.inherits(QStringLiteral("application/x-tar")) || mime.inherits(QStringLiteral("application/x-bzip"))) {
0078         mArchive = new KTar(fileName);
0079 
0080         if (!processArchive()) {
0081             return false;
0082         }
0083 #ifdef WITH_K7ZIP
0084         /**
0085          * We have a 7z archive
0086          */
0087     } else if (mime.inherits(QStringLiteral("application/x-cb7")) || mime.inherits(QStringLiteral("application/x-7z-compressed"))) {
0088         mArchive = new K7Zip(fileName);
0089 
0090         if (!processArchive()) {
0091             return false;
0092         }
0093 #endif
0094     } else if (mime.inherits(QStringLiteral("application/x-cbr")) || mime.inherits(QStringLiteral("application/x-rar")) || mime.inherits(QStringLiteral("application/vnd.rar"))) {
0095         if (!Unrar::isAvailable()) {
0096             mLastErrorString = i18n("Cannot open document, neither unrar nor unarchiver were found.");
0097             return false;
0098         }
0099 
0100         if (!Unrar::isSuitableVersionAvailable()) {
0101             mLastErrorString = i18n("The version of unrar on your system is not suitable for opening comicbooks.");
0102             return false;
0103         }
0104 
0105         /**
0106          * We have a rar archive
0107          */
0108         mUnrar = new Unrar();
0109 
0110         if (!mUnrar->open(fileName)) {
0111             delete mUnrar;
0112             mUnrar = nullptr;
0113 
0114             return false;
0115         }
0116 
0117         mEntries = mUnrar->list();
0118     } else if (mime.inherits(QStringLiteral("inode/directory"))) {
0119         mDirectory = new Directory();
0120 
0121         if (!mDirectory->open(fileName)) {
0122             delete mDirectory;
0123             mDirectory = nullptr;
0124 
0125             return false;
0126         }
0127 
0128         mEntries = mDirectory->list();
0129     } else {
0130         mLastErrorString = i18n("Unknown ComicBook format.");
0131         return false;
0132     }
0133 
0134     return true;
0135 }
0136 
0137 void Document::close()
0138 {
0139     mLastErrorString.clear();
0140 
0141     if (!(mArchive || mUnrar || mDirectory)) {
0142         return;
0143     }
0144 
0145     delete mArchive;
0146     mArchive = nullptr;
0147     delete mDirectory;
0148     mDirectory = nullptr;
0149     delete mUnrar;
0150     mUnrar = nullptr;
0151     mPageMap.clear();
0152     mEntries.clear();
0153 }
0154 
0155 bool Document::processArchive()
0156 {
0157     if (!mArchive->open(QIODevice::ReadOnly)) {
0158         delete mArchive;
0159         mArchive = nullptr;
0160 
0161         return false;
0162     }
0163 
0164     const KArchiveDirectory *directory = mArchive->directory();
0165     if (!directory) {
0166         delete mArchive;
0167         mArchive = nullptr;
0168 
0169         return false;
0170     }
0171 
0172     mArchiveDir = directory;
0173 
0174     imagesInArchive(QString(), mArchiveDir, &mEntries);
0175 
0176     return true;
0177 }
0178 
0179 void Document::pages(QVector<Okular::Page *> *pagesVector)
0180 {
0181     std::sort(mEntries.begin(), mEntries.end(), caseSensitiveNaturalOrderLessThen);
0182     QScopedPointer<QIODevice> dev;
0183 
0184     int count = 0;
0185     pagesVector->clear();
0186     pagesVector->resize(mEntries.size());
0187     QImageReader reader;
0188     reader.setAutoTransform(true);
0189     for (const QString &file : std::as_const(mEntries)) {
0190         if (mArchive) {
0191             const KArchiveFile *entry = static_cast<const KArchiveFile *>(mArchiveDir->entry(file));
0192             if (entry) {
0193                 dev.reset(entry->createDevice());
0194             }
0195         } else if (mDirectory) {
0196             dev.reset(mDirectory->createDevice(file));
0197         } else {
0198             dev.reset(mUnrar->createDevice(file));
0199         }
0200 
0201         if (!dev.isNull()) {
0202             reader.setDevice(dev.data());
0203             if (reader.canRead()) {
0204                 QSize pageSize = reader.size();
0205                 if (reader.transformation() & QImageIOHandler::TransformationRotate90) {
0206                     pageSize.transpose();
0207                 }
0208                 if (!pageSize.isValid()) {
0209                     const QImage i = reader.read();
0210                     if (!i.isNull()) {
0211                         pageSize = i.size();
0212                     }
0213                 }
0214                 if (pageSize.isValid()) {
0215                     pagesVector->replace(count, new Okular::Page(count, pageSize.width(), pageSize.height(), Okular::Rotation0));
0216                     mPageMap.append(file);
0217                     count++;
0218                 } else {
0219                     qCDebug(OkularComicbookDebug) << "Ignoring" << file << "doesn't seem to be an image even if QImageReader::canRead returned true";
0220                 }
0221             }
0222         }
0223     }
0224     pagesVector->resize(count);
0225 }
0226 
0227 QStringList Document::pageTitles() const
0228 {
0229     return QStringList();
0230 }
0231 
0232 QImage Document::pageImage(int page) const
0233 {
0234     if (mArchive) {
0235         const KArchiveFile *entry = static_cast<const KArchiveFile *>(mArchiveDir->entry(mPageMap[page]));
0236         if (entry) {
0237             std::unique_ptr<QIODevice> dev(entry->createDevice());
0238             // This could simply be
0239             //     QImageReader reader(dev.get());
0240             // but due to https://codereview.qt-project.org/c/qt/qtbase/+/349174 and https://invent.kde.org/frameworks/karchive/-/merge_requests/14
0241             // it can not, so it will have to be like this at least until Qt6
0242             // Test with https://bugs.kde.org/attachment.cgi?id=74039 (it's a cbz with a png inside)
0243             QBuffer b;
0244             b.setData(dev->readAll());
0245             QImageReader reader(&b);
0246             reader.setAutoTransform(true);
0247             return reader.read();
0248         }
0249     } else if (mDirectory) {
0250         return QImage(mPageMap[page]);
0251     } else {
0252         return QImage::fromData(mUnrar->contentOf(mPageMap[page]));
0253     }
0254 
0255     return QImage();
0256 }
0257 
0258 QString Document::lastErrorString() const
0259 {
0260     return mLastErrorString;
0261 }
0262 
0263 Q_LOGGING_CATEGORY(OkularComicbookDebug, "org.kde.okular.generators.comicbook", QtWarningMsg)