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)