File indexing completed on 2025-01-26 04:14:58

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 "ArchiveBookModel.h"
0023 #include "ArchiveImageProvider.h"
0024 
0025 #include <AcbfAuthor.h>
0026 #include <AcbfBody.h>
0027 #include <AcbfBookinfo.h>
0028 #include <AcbfDocument.h>
0029 #include <AcbfDocumentinfo.h>
0030 #include <AcbfMetadata.h>
0031 #include <AcbfPage.h>
0032 #include <AcbfPublishinfo.h>
0033 #include <AcbfStyleSheet.h>
0034 
0035 #include <QCoreApplication>
0036 #include <QDir>
0037 #include <QFontDatabase>
0038 #include <QImageReader>
0039 #include <QMimeDatabase>
0040 #include <QQmlEngine>
0041 #include <QTemporaryFile>
0042 #include <QXmlStreamReader>
0043 
0044 #include <KFileMetaData/UserMetaData>
0045 #include <KLocalizedString>
0046 #include <karchive.h>
0047 #include <kzip.h>
0048 #include "KRar.h" // "" because it's a custom thing for now
0049 
0050 #include <qtquick_debug.h>
0051 #include <AcbfData.h>
0052 
0053 class ArchiveBookModel::Private
0054 {
0055 public:
0056     Private(ArchiveBookModel* qq)
0057         : q(qq)
0058         , engine(nullptr)
0059         , archive(nullptr)
0060         , readWrite(false)
0061         , imageProvider(nullptr)
0062         , isDirty(false)
0063         , isLoading(false)
0064     {}
0065     ~Private() {
0066         for (int fontId : fontIdByFilename.values()) {
0067             fontDatabase.removeApplicationFont(fontId);
0068         }
0069         delete archive;
0070     }
0071     ArchiveBookModel* q;
0072     QQmlEngine* engine;
0073     KArchive* archive;
0074     QStringList fileEntries;
0075     QStringList fileEntriesToDelete;
0076     QHash<QString, const KArchiveFile*> archiveFiles;
0077     bool readWrite;
0078     ArchiveImageProvider* imageProvider;
0079     bool isDirty;
0080     bool isLoading;
0081     QMimeDatabase mimeDatabase;
0082     QFontDatabase fontDatabase;
0083     QHash<QString, int> fontIdByFilename;
0084     QString acbfEntryName;
0085 
0086     void closeBook() {
0087         q->beginResetModel();
0088         if(archive)
0089         {
0090             q->clearPages();
0091             archiveFiles.clear();
0092             archive->close();
0093             delete archive;
0094             archive = nullptr;
0095         }
0096         if(imageProvider && engine) {
0097             engine->removeImageProvider(imageProvider->prefix());
0098         }
0099         imageProvider = nullptr;
0100         fileEntries.clear();
0101         Q_EMIT q->fileEntriesChanged();
0102         fileEntriesToDelete.clear();
0103         Q_EMIT q->fileEntriesToDeleteChanged();
0104         q->endResetModel();
0105         acbfEntryName.clear();
0106     }
0107 
0108     static int counter()
0109     {
0110         static int count = 0;
0111         return count++;
0112     }
0113 
0114     void setDirty()
0115     {
0116         isDirty = true;
0117         emit q->hasUnsavedChangesChanged();
0118     }
0119 
0120     AdvancedComicBookFormat::Document* createNewAcbfDocumentFromLegacyInformation()
0121     {
0122         AdvancedComicBookFormat::Document* acbfDocument = new AdvancedComicBookFormat::Document(q);
0123 
0124         acbfDocument->metaData()->bookInfo()->setTitle(q->title());
0125 
0126         AdvancedComicBookFormat::Author* author = new AdvancedComicBookFormat::Author(acbfDocument->metaData());
0127         author->setNickName(q->author());
0128         acbfDocument->metaData()->bookInfo()->addAuthor(author);
0129 
0130         acbfDocument->metaData()->publishInfo()->setPublisher(q->publisher());
0131 
0132         int prefixLength = QString("image://%1/").arg(imageProvider->prefix()).length();
0133         if(q->pageCount() > 0)
0134         {
0135             // First, let's see if we have something called "*cover*"... because that would be handy and useful
0136             int cover = -1;
0137             for(int i = q->pageCount(); i > -1; --i)
0138             {
0139                 QString url = q->data(q->index(i, 0, QModelIndex()), BookModel::UrlRole).toString().mid(prefixLength);
0140                 // Yup, this is a bit sort of naughty and stuff... but, assume index 0 is the cover if nothing else has shown up...
0141                 // FIXME this will also fail when there's more than one cover... say, back and front covers...
0142                 if(url.split('/').last().contains("cover", Qt::CaseInsensitive) || i == 0) {
0143                     acbfDocument->metaData()->bookInfo()->coverpage()->setImageHref(url);
0144                     acbfDocument->metaData()->bookInfo()->coverpage()->setTitle(q->data(q->index(0, 0, QModelIndex()), BookModel::TitleRole).toString());
0145                     cover = i;
0146                     break;
0147                 }
0148             }
0149 
0150             for(int i = 0; i < q->pageCount(); ++i)
0151             {
0152                 if(i == cover)
0153                 {
0154                     continue;
0155                 }
0156                 AdvancedComicBookFormat::Page* page = new AdvancedComicBookFormat::Page(acbfDocument);
0157                 page->setImageHref(q->data(q->index(i, 0, QModelIndex()), BookModel::UrlRole).toString().mid(prefixLength));
0158                 page->setTitle(q->data(q->index(i, 0, QModelIndex()), BookModel::TitleRole).toString());
0159                 acbfDocument->body()->addPage(page);
0160             }
0161         }
0162 
0163         q->setAcbfData(acbfDocument);
0164         setDirty();
0165         return acbfDocument;
0166     }
0167 };
0168 
0169 ArchiveBookModel::ArchiveBookModel(QObject* parent)
0170     : BookModel(parent)
0171     , d(new Private(this))
0172 {
0173 }
0174 
0175 ArchiveBookModel::~ArchiveBookModel()
0176 {
0177     d->closeBook();
0178     delete d;
0179 }
0180 
0181 QStringList recursiveEntries(const KArchiveDirectory* dir)
0182 {
0183     QStringList entries = dir->entries();
0184     QStringList allEntries = entries;
0185     for(const QString& entryName : qAsConst(entries))
0186     {
0187         const KArchiveEntry* entry = dir->entry(entryName);
0188         if(entry->isDirectory())
0189         {
0190             const KArchiveDirectory* subDir = static_cast<const KArchiveDirectory*>(entry);
0191             QStringList subEntries = recursiveEntries(subDir);
0192             for(const QString& subEntry : qAsConst(subEntries))
0193             {
0194                 entries.append(entryName + "/" + subEntry);
0195             }
0196         }
0197     }
0198     return entries;
0199 }
0200 
0201 void ArchiveBookModel::setFilename(QString newFilename)
0202 {
0203     setProcessing(true);
0204     d->isLoading = true;
0205     d->closeBook();
0206     beginResetModel();
0207 
0208     QMimeType mime = d->mimeDatabase.mimeTypeForFile(newFilename);
0209     if(mime.inherits("application/zip"))
0210     {
0211         d->archive = new KZip(newFilename);
0212     }
0213     else if (mime.inherits("application/x-rar"))
0214     {
0215         d->archive = new KRar(newFilename);
0216     }
0217 
0218     bool success = false;
0219     if(d->archive)
0220     {
0221         QString prefix = QString("archivebookpage%1").arg(QString::number(Private::counter()));
0222         if(d->archive->open(QIODevice::ReadOnly))
0223         {
0224             QMutexLocker locker(&archiveMutex);
0225             d->imageProvider = new ArchiveImageProvider();
0226             d->imageProvider->setArchiveBookModel(this);
0227             d->imageProvider->setPrefix(prefix);
0228             if(d->engine) {
0229                 d->engine->addImageProvider(prefix, d->imageProvider);
0230             }
0231 
0232             d->fileEntries = recursiveEntries(d->archive->directory());
0233             d->fileEntries.sort();
0234             Q_EMIT fileEntriesChanged();
0235 
0236             // First check and see if we've got an ACBF document in there...
0237             QString comicInfoEntry;
0238             QStringList xmlFiles;
0239             QLatin1String acbfSuffix(".acbf");
0240             QLatin1String ComicInfoXML("comicinfo.xml");
0241             QLatin1String xmlSuffix(".xml");
0242             QStringList images;
0243             for(const QString& entry : qAsConst(d->fileEntries))
0244             {
0245                 if(entry.toLower().endsWith(acbfSuffix))
0246                 {
0247                     d->acbfEntryName = entry;
0248                     break;
0249                 }
0250                 if(entry.toLower().endsWith(xmlSuffix)) {
0251                     if(entry.toLower().endsWith(ComicInfoXML)) {
0252                         comicInfoEntry = entry;
0253                     } else {
0254                         xmlFiles.append(entry);
0255                     }
0256                 }
0257                 if (entry.toLower().endsWith(".jpg") || entry.toLower().endsWith(".jpeg")
0258                         || entry.toLower().endsWith(".gif") || entry.toLower().endsWith(".png") || entry.toLower().endsWith(".webp")) {
0259                     images.append(entry);
0260                 }
0261             }
0262             images.sort();
0263             if(!d->acbfEntryName.isEmpty())
0264             {
0265                 AdvancedComicBookFormat::Document* acbfDocument = new AdvancedComicBookFormat::Document(this);
0266                 const KArchiveFile* archFile = d->archive->directory()->file(d->acbfEntryName);
0267                 if(acbfDocument->fromXml(QString(archFile->data())))
0268                 {
0269                     setAcbfData(acbfDocument);
0270                     addPage(QString("image://%1/%2").arg(prefix).arg(acbfDocument->metaData()->bookInfo()->coverpage()->imageHref()), acbfDocument->metaData()->bookInfo()->coverpage()->title());
0271                     for(AdvancedComicBookFormat::Page* page : acbfDocument->body()->pages())
0272                     {
0273                         addPage(QString("image://%1/%2").arg(prefix).arg(page->imageHref()), page->title());
0274                     }
0275                 }
0276                 else
0277                 {
0278                     // just in case this is, for whatever reason, being reused...
0279                     setAcbfData(nullptr);
0280                 }
0281             }
0282             else if (!comicInfoEntry.isEmpty() || !xmlFiles.isEmpty()) {
0283                 AdvancedComicBookFormat::Document* acbfDocument = new AdvancedComicBookFormat::Document(this);
0284                 const KArchiveFile* archFile = d->archive->directory()->file(comicInfoEntry);
0285                 bool loadData = false;
0286 
0287                 if (!comicInfoEntry.isEmpty()) {
0288                     loadData = loadComicInfoXML(archFile->data(), acbfDocument, images, newFilename);
0289                 } else {
0290                     loadData = loadCoMet(xmlFiles, acbfDocument, images, newFilename);
0291                 }
0292 
0293                 if (loadData) {
0294                     setAcbfData(acbfDocument);
0295                     QString undesired = QString("%1").arg("/").append("Thumbs.db");
0296                     addPage(QString("image://%1/%2").arg(prefix).arg(acbfDocument->metaData()->bookInfo()->coverpage()->imageHref()), acbfDocument->metaData()->bookInfo()->coverpage()->title());
0297                     for(AdvancedComicBookFormat::Page* page : acbfDocument->body()->pages())
0298                     {
0299                         addPage(QString("image://%1/%2").arg(prefix).arg(page->imageHref()), page->title());
0300                     }
0301                 }
0302             }
0303             if(!acbfData())
0304             {
0305                 // fall back to just handling the files directly if there's no ACBF document...
0306                 QString undesired = QString("%1").arg("/").append("Thumbs.db");
0307                 for(const QString& entry : qAsConst(d->fileEntries))
0308                 {
0309                     const KArchiveEntry* archEntry = d->archive->directory()->entry(entry);
0310                     if(archEntry->isFile() && !entry.endsWith(undesired))
0311                     {
0312                         addPage(QString("image://%1/%2").arg(prefix).arg(entry), entry.split("/").last());
0313                     }
0314                 }
0315             }
0316             d->archive->close();
0317             success = true;
0318         }
0319         else {
0320             qCDebug(QTQUICK_LOG) << "Failed to open archive";
0321         }
0322     }
0323 
0324 //     QDir dir(newFilename);
0325 //     if(dir.exists())
0326 //     {
0327 //         QFileInfoList entries = dir.entryInfoList(QDir::Files, QDir::Name);
0328 //         for(const QFileInfo& entry : entries)
0329 //         {
0330 //             addPage(QString("file://").append(entry.canonicalFilePath()), entry.fileName());
0331 //         }
0332 //     }
0333     BookModel::setFilename(newFilename);
0334 
0335     KFileMetaData::UserMetaData data(newFilename);
0336     if(data.hasAttribute("peruse.currentPage"))
0337         BookModel::setCurrentPage(data.attribute("peruse.currentPage").toInt(), false);
0338 
0339     if(!acbfData() && d->readWrite && d->imageProvider)
0340     {
0341         d->createNewAcbfDocumentFromLegacyInformation();
0342     }
0343 
0344     d->isLoading = false;
0345     emit loadingCompleted(success);
0346     setProcessing(false);
0347     endResetModel();
0348 }
0349 
0350 QString ArchiveBookModel::author() const
0351 {
0352     AdvancedComicBookFormat::Document* acbfDocument = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData());
0353     if(acbfDocument)
0354     {
0355         if(acbfDocument->metaData()->bookInfo()->author().count() > 0)
0356         {
0357             return acbfDocument->metaData()->bookInfo()->author().at(0)->displayName();
0358         }
0359     }
0360     return BookModel::author();
0361 }
0362 
0363 void ArchiveBookModel::setAuthor(QString newAuthor)
0364 {
0365     if(!d->isLoading)
0366     {
0367         AdvancedComicBookFormat::Document* acbfDocument = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData());
0368         if(!acbfDocument)
0369         {
0370             acbfDocument = d->createNewAcbfDocumentFromLegacyInformation();
0371         }
0372         if(acbfDocument->metaData()->bookInfo()->author().count() == 0)
0373         {
0374             AdvancedComicBookFormat::Author* author = new AdvancedComicBookFormat::Author(acbfDocument->metaData());
0375             author->setNickName(newAuthor);
0376             acbfDocument->metaData()->bookInfo()->addAuthor(author);
0377         }
0378         else
0379         {
0380             acbfDocument->metaData()->bookInfo()->author().at(0)->setNickName(newAuthor);
0381         }
0382     }
0383     BookModel::setAuthor(newAuthor);
0384 }
0385 
0386 QString ArchiveBookModel::publisher() const
0387 {
0388     AdvancedComicBookFormat::Document* acbfDocument = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData());
0389     if(acbfDocument)
0390     {
0391         if(!acbfDocument->metaData()->publishInfo()->publisher().isEmpty())
0392         {
0393             return acbfDocument->metaData()->publishInfo()->publisher();
0394         }
0395     }
0396     return BookModel::publisher();
0397 }
0398 
0399 void ArchiveBookModel::setPublisher(QString newPublisher)
0400 {
0401     if(!d->isLoading)
0402     {
0403         AdvancedComicBookFormat::Document* acbfDocument = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData());
0404         if(!acbfDocument)
0405         {
0406             acbfDocument = d->createNewAcbfDocumentFromLegacyInformation();
0407         }
0408         acbfDocument->metaData()->publishInfo()->setPublisher(newPublisher);
0409     }
0410     BookModel::setAuthor(newPublisher);
0411 }
0412 
0413 QString ArchiveBookModel::title() const
0414 {
0415     AdvancedComicBookFormat::Document* acbfDocument = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData());
0416     if(acbfDocument)
0417     {
0418         if(acbfDocument->metaData()->bookInfo()->title().length() > 0)
0419         {
0420             return acbfDocument->metaData()->bookInfo()->title();
0421         }
0422     }
0423     return BookModel::title();
0424 }
0425 
0426 void ArchiveBookModel::setTitle(QString newTitle)
0427 {
0428     if(!d->isLoading)
0429     {
0430         AdvancedComicBookFormat::Document* acbfDocument = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData());
0431         if(!acbfDocument)
0432         {
0433             acbfDocument = d->createNewAcbfDocumentFromLegacyInformation();
0434         }
0435         acbfDocument->metaData()->bookInfo()->setTitle(newTitle);
0436     }
0437     BookModel::setTitle(newTitle);
0438 }
0439 
0440 QObject * ArchiveBookModel::qmlEngine() const
0441 {
0442     return d->engine;
0443 }
0444 
0445 void ArchiveBookModel::setQmlEngine(QObject* newEngine)
0446 {
0447     d->engine = qobject_cast<QQmlEngine*>(newEngine);
0448     emit qmlEngineChanged();
0449 }
0450 
0451 bool ArchiveBookModel::readWrite() const
0452 {
0453     return d->readWrite;
0454 }
0455 
0456 void ArchiveBookModel::setReadWrite(bool newReadWrite)
0457 {
0458     d->readWrite = newReadWrite;
0459     emit readWriteChanged();
0460 }
0461 
0462 bool ArchiveBookModel::hasUnsavedChanges() const
0463 {
0464     return d->isDirty;
0465 }
0466 
0467 void ArchiveBookModel::setDirty(bool isDirty)
0468 {
0469     d->isDirty = isDirty;
0470     emit hasUnsavedChangesChanged();
0471 }
0472 
0473 QStringList ArchiveBookModel::fileEntries() const
0474 {
0475     return d->fileEntries;
0476 }
0477 
0478 int ArchiveBookModel::fileEntryReferenced(const QString& fileEntry) const
0479 {
0480     int isReferenced{0};
0481     AdvancedComicBookFormat::Document* document = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData());
0482     if (document->metaData()->bookInfo()->coverpage()->imageHref() == fileEntry) {
0483         isReferenced = 1;
0484     }
0485     if (!isReferenced) {
0486         for(const AdvancedComicBookFormat::Page* page : document->body()->pages()) {
0487             if (page->imageHref() == fileEntry) {
0488                 isReferenced = 1;
0489                 break;
0490             }
0491         }
0492     }
0493     if (!isReferenced) {
0494         for(const QObject* obj : document->styleSheet()->styles()) {
0495             const AdvancedComicBookFormat::Style* style = qobject_cast<const AdvancedComicBookFormat::Style*>(obj);
0496             const QString styleString{style->toString()};
0497             if (styleString.contains(fileEntry)) {
0498                 isReferenced = 1;
0499                 break;
0500             } else if (styleString.contains(fileEntry.split("/").last())) {
0501                 isReferenced = 2;
0502                 break;
0503             }
0504         }
0505     }
0506     return isReferenced;
0507 }
0508 
0509 bool ArchiveBookModel::fileEntryIsDirectory(const QString& fileEntry) const
0510 {
0511     bool isDirectory{false};
0512     const KArchiveEntry* entry = d->archive->directory()->entry(fileEntry);
0513     if (entry && entry->isDirectory()) {
0514         isDirectory = true;
0515     }
0516     return isDirectory;
0517 }
0518 
0519 QStringList ArchiveBookModel::fileEntriesToDelete() const
0520 {
0521     return d->fileEntriesToDelete;
0522 }
0523 
0524 void ArchiveBookModel::markArchiveFileForDeletion(const QString& archiveFile, bool markForDeletion)
0525 {
0526     if(markForDeletion) {
0527         if (!d->fileEntriesToDelete.contains(archiveFile)) {
0528             d->fileEntriesToDelete << archiveFile;
0529             Q_EMIT fileEntriesToDeleteChanged();
0530         }
0531     } else {
0532         if (d->fileEntriesToDelete.contains(archiveFile)) {
0533             d->fileEntriesToDelete.removeAll(archiveFile);
0534             Q_EMIT fileEntriesToDeleteChanged();
0535         }
0536     }
0537 }
0538 
0539 bool ArchiveBookModel::saveBook()
0540 {
0541     bool success = true;
0542     if(d->isDirty)
0543     {
0544         QMutexLocker locker(&archiveMutex);
0545         // TODO get new filenames out of acbf
0546 
0547         setProcessing(true);
0548         qApp->processEvents();
0549 
0550         QTemporaryFile tmpFile(this);
0551         tmpFile.open();
0552         QString archiveFileName = tmpFile.fileName().append(".cbz");
0553         QFileInfo fileInfo(tmpFile);
0554         tmpFile.close();
0555         setProcessingDescription(i18n("Creating archive in %1", archiveFileName));
0556         KZip* archive = new KZip(archiveFileName);
0557         archive->open(QIODevice::ReadWrite);
0558 
0559         QString acbfFileName{d->acbfEntryName};
0560         if (acbfFileName.isEmpty()) {
0561             acbfFileName = QStringLiteral("metadata.acbf");
0562         } else {
0563             // If we actually /have/ an acbf filename already, let's not copy the old one across...
0564             d->fileEntriesToDelete << acbfFileName;
0565         }
0566         // We're a zip file... size isn't used
0567         setProcessingDescription(i18n("Writing in ACBF data"));
0568         archive->prepareWriting(acbfFileName, fileInfo.owner(), fileInfo.group(), 0);
0569         AdvancedComicBookFormat::Document* acbfDocument = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData());
0570         if(!acbfDocument)
0571         {
0572             acbfDocument = d->createNewAcbfDocumentFromLegacyInformation();
0573         }
0574         QByteArray acbfStringUtf8 = acbfDocument->toXml().toUtf8();
0575         archive->writeData(acbfStringUtf8, acbfStringUtf8.size());
0576         archive->finishWriting(acbfStringUtf8.size());
0577 
0578         setProcessingDescription(i18n("Copying across all files not marked for deletion"));
0579         const QStringList allFiles = fileEntries();
0580         const KArchiveFile* archFile{nullptr};
0581         for( const QString& file : allFiles) {
0582             qApp->processEvents();
0583             if (d->fileEntriesToDelete.contains(file)) {
0584                 qCDebug(QTQUICK_LOG) << "Not copying file marked for deletion:" << file;
0585             } else {
0586                 setProcessingDescription(i18n("Copying over %1", file));
0587                 archFile = archiveFile(file);
0588                 if(archFile && archFile->isFile())
0589                 {
0590                     archive->prepareWriting(file, archFile->user(), archFile->group(), 0);
0591                     archive->writeData(archFile->data(), archFile->size());
0592                     archive->finishWriting(archFile->size());
0593                 }
0594             }
0595         }
0596         d->fileEntriesToDelete.clear();
0597         Q_EMIT fileEntriesToDeleteChanged();
0598 
0599         archive->close();
0600         qCDebug(QTQUICK_LOG) << "Archive created and closed...";
0601 
0602         // swap out the two files, tell model we're about to swap things out...
0603         beginResetModel();
0604 
0605         QString actualFile = d->archive->fileName();
0606         d->closeBook();
0607 
0608         // This seems roundabout... but it retains ctime and xattrs, which would be gone
0609         // if we just did a delete+rename
0610         QFile destinationFile(actualFile);
0611         if(destinationFile.open(QIODevice::WriteOnly))
0612         {
0613             QFile originFile(archiveFileName);
0614             if(originFile.open(QIODevice::ReadOnly)) {
0615                 setProcessingDescription(i18n("Copying all content from %1 to %2", archiveFileName, actualFile));
0616                 while(!originFile.atEnd())
0617                 {
0618                     destinationFile.write(originFile.read(65536));
0619                     qApp->processEvents();
0620                 }
0621                 destinationFile.close();
0622                 originFile.close();
0623                 if(originFile.remove())
0624                 {
0625                     setProcessingDescription(i18n("Successfully replaced old archive with the new archive - now loading the new archive..."));
0626                     // now load the new thing...
0627                     locker.unlock();
0628                     setFilename(actualFile);
0629                     locker.relock();
0630                 }
0631                 else
0632                 {
0633                     qCWarning(QTQUICK_LOG) << "Failed to delete" << originFile.fileName();
0634                 }
0635             }
0636             else
0637             {
0638                 qCWarning(QTQUICK_LOG) << "Failed to open" << originFile.fileName() << "for reading";
0639             }
0640         }
0641         else
0642         {
0643             qCWarning(QTQUICK_LOG) << "Failed to open" << destinationFile.fileName() << "for writing";
0644         }
0645     }
0646     endResetModel();
0647     setProcessing(false);
0648     setDirty(false);
0649     return success;
0650 }
0651 
0652 void ArchiveBookModel::addPage(QString url, QString title)
0653 {
0654     // don't do this unless we're done loading... don't want to dirty things up until then!
0655     if(!d->isLoading)
0656     {
0657         AdvancedComicBookFormat::Document* acbfDocument = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData());
0658         if(!acbfDocument)
0659         {
0660             acbfDocument = d->createNewAcbfDocumentFromLegacyInformation();
0661         }
0662         QUrl imageUrl(url);
0663         if(pageCount() == 0)
0664         {
0665             acbfDocument->metaData()->bookInfo()->coverpage()->setTitle(title);
0666             acbfDocument->metaData()->bookInfo()->coverpage()->setImageHref(QString("%1/%2").arg(imageUrl.path().mid(1)).arg(imageUrl.fileName()));
0667         }
0668         else
0669         {
0670             AdvancedComicBookFormat::Page* page = new AdvancedComicBookFormat::Page(acbfDocument);
0671             page->setTitle(title);
0672             page->setImageHref(QString("%1/%2").arg(imageUrl.path().mid(1)).arg(imageUrl.fileName()));
0673             acbfDocument->body()->addPage(page);
0674         }
0675     }
0676     BookModel::addPage(url, title);
0677 }
0678 
0679 void ArchiveBookModel::removePage(int pageNumber)
0680 {
0681     if(!d->isLoading)
0682     {
0683         AdvancedComicBookFormat::Document* acbfDocument = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData());
0684         if(!acbfDocument)
0685         {
0686             acbfDocument = d->createNewAcbfDocumentFromLegacyInformation();
0687         }
0688         else
0689         {
0690             if(pageNumber == 0)
0691             {
0692                 // Page no 0 is the cover page, when removed we'd usually end up with no cover page
0693                 // Normally we'd want to discourage this, but we do need to support the functionality
0694                 AdvancedComicBookFormat::Page* cover = acbfDocument->metaData()->bookInfo()->coverpage();
0695                 if (cover) {
0696                     cover->deleteLater();
0697                 }
0698                 AdvancedComicBookFormat::Page* page = acbfDocument->body()->page(0);
0699                 acbfDocument->metaData()->bookInfo()->setCoverpage(page);
0700                 if (page) {
0701                     acbfDocument->body()->removePage(page);
0702                 }
0703             }
0704             else {
0705                 AdvancedComicBookFormat::Page* page = acbfDocument->body()->page(pageNumber-1);
0706                 if (page) {
0707                     acbfDocument->body()->removePage(page);
0708                     page->deleteLater();
0709                 }
0710             }
0711         }
0712     }
0713     BookModel::removePage(pageNumber);
0714 }
0715 
0716 // FIXME any metadata change sets dirty (as we need to replace the whole file in archive when saving)
0717 
0718 void ArchiveBookModel::addPageFromFile(QString fileUrl, int insertAfter)
0719 {
0720     if(d->archive && d->readWrite && !d->isDirty)
0721     {
0722         int insertionIndex = insertAfter;
0723         if(insertAfter < 0 || pageCount() - 1 < insertAfter) {
0724             insertionIndex = pageCount();
0725         }
0726 
0727         // This is a permanent thing, renaming in zip files is VERY expensive (literally not possible without
0728         // rewriting the entire archive...)
0729         QString archiveFileName = QString("page-%1.%2").arg(QString::number(insertionIndex), QFileInfo(fileUrl).completeSuffix());
0730         d->archive->close();
0731         d->archive->open(QIODevice::ReadWrite);
0732         d->archive->addLocalFile(fileUrl, archiveFileName);
0733         d->archive->close();
0734         d->archive->open(QIODevice::ReadOnly);
0735         addPage(QString("image://%1/%2").arg(d->imageProvider->prefix()).arg(archiveFileName), archiveFileName.split("/").last());
0736         d->fileEntries << archiveFileName;
0737         d->fileEntries.sort();
0738         Q_EMIT fileEntriesChanged();
0739         saveBook();
0740     }
0741 }
0742 
0743 void ArchiveBookModel::swapPages(int swapThisIndex, int withThisIndex)
0744 {
0745     d->setDirty();
0746 
0747     AdvancedComicBookFormat::Document* acbfDocument = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData());
0748 
0749     // Cover pages are special, and in acbf they are very special... (otherwise they're page zero)
0750     if(swapThisIndex == 0)
0751     {
0752         AdvancedComicBookFormat::Page* oldCoverPage = acbfDocument->metaData()->bookInfo()->coverpage();
0753         AdvancedComicBookFormat::Page* otherPage = acbfDocument->body()->page(withThisIndex - 1);
0754         acbfDocument->body()->removePage(otherPage);
0755         acbfDocument->metaData()->bookInfo()->setCoverpage(otherPage);
0756         acbfDocument->body()->addPage(oldCoverPage, withThisIndex - 1);
0757     }
0758     else if(withThisIndex == 0)
0759     {
0760         AdvancedComicBookFormat::Page* oldCoverPage = acbfDocument->metaData()->bookInfo()->coverpage();
0761         AdvancedComicBookFormat::Page* otherPage = acbfDocument->body()->page(swapThisIndex - 1);
0762         acbfDocument->body()->removePage(otherPage);
0763         acbfDocument->metaData()->bookInfo()->setCoverpage(otherPage);
0764         acbfDocument->body()->addPage(oldCoverPage, swapThisIndex - 1);
0765     }
0766     else
0767     {
0768         AdvancedComicBookFormat::Page* firstPage = acbfDocument->body()->page(swapThisIndex - 1);
0769         AdvancedComicBookFormat::Page* otherPage = acbfDocument->body()->page(withThisIndex - 1);
0770         acbfDocument->body()->swapPages(firstPage, otherPage);
0771     }
0772 
0773     // FIXME only treat things which have sequential numbers in as pages, split out chapters automatically?
0774 
0775     BookModel::swapPages(swapThisIndex, withThisIndex);
0776 }
0777 
0778 QString ArchiveBookModel::createBook(QString folder, QString title, QString coverUrl)
0779 {
0780     bool success = true;
0781 
0782     QString fileTitle = title.replace( QRegExp("\\W"),QString("")).simplified();
0783     QString filename = QString("%1/%2.cbz").arg(folder).arg(fileTitle);
0784     int i = 1;
0785     while(QFile(filename).exists())
0786     {
0787         filename = QString("%1/%2 (%3).cbz").arg(folder).arg(fileTitle).arg(QString::number(i++));
0788     }
0789 
0790     ArchiveBookModel* model = new ArchiveBookModel(nullptr);
0791     model->setQmlEngine(qmlEngine());
0792     model->setReadWrite(true);
0793     QString prefix = QString("archivebookpage%1").arg(QString::number(Private::counter()));
0794     model->d->imageProvider = new ArchiveImageProvider();
0795     model->d->imageProvider->setArchiveBookModel(model);
0796     model->d->imageProvider->setPrefix(prefix);
0797     model->d->archive = new KZip(filename);
0798     model->BookModel::setFilename(filename);
0799     model->setTitle(title);
0800     AdvancedComicBookFormat::Document* acbfDocument = qobject_cast<AdvancedComicBookFormat::Document*>(model->acbfData());
0801     QString coverArchiveName = QString("cover.%1").arg(QFileInfo(coverUrl).completeSuffix());
0802     acbfDocument->metaData()->bookInfo()->coverpage()->setImageHref(coverArchiveName);
0803     success = model->saveBook();
0804 
0805     model->d->archive->close();
0806     model->d->archive->open(QIODevice::ReadWrite);
0807     model->d->archive->addLocalFile(coverUrl, coverArchiveName);
0808     model->d->fileEntries << coverArchiveName;
0809     model->d->fileEntries.sort();
0810     Q_EMIT model->fileEntriesChanged();
0811     model->d->archive->close();
0812 
0813     model->deleteLater();
0814 
0815     if(!success)
0816         return QLatin1String("");
0817     return filename;
0818 }
0819 
0820 const KArchiveFile * ArchiveBookModel::archiveFile(const QString& filePath) const
0821 {
0822     if(d->archive->isOpen() == false){
0823         d->archive->open(QIODevice::ReadOnly);
0824     }
0825     if(d->archive)
0826     {
0827         if(!d->archiveFiles.contains(filePath)) {
0828             d->archiveFiles[filePath] = d->archive->directory()->file(filePath);
0829         }
0830         return d->archiveFiles[filePath];
0831     }
0832     return nullptr;
0833 }
0834 
0835 bool ArchiveBookModel::loadComicInfoXML(QString xmlDocument, QObject *acbfData, QStringList entries, QString filename)
0836 {
0837     KFileMetaData::UserMetaData filedata(filename);
0838     AdvancedComicBookFormat::Document* acbfDocument = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData);
0839     QXmlStreamReader xmlReader(xmlDocument);
0840     if(xmlReader.readNextStartElement())
0841     {
0842         if(xmlReader.name() == QStringLiteral("ComicInfo"))
0843         {
0844             // We'll need to collect several items to generate a series. Thankfully, comicinfo only has two types of series.
0845             QString series;
0846             int number = -1;
0847             int volume = 0;
0848 
0849             QString seriesAlt;
0850             int numberAlt = -1;
0851             int volumeAlt = 0;
0852 
0853             // Also publishing date.
0854             int year = 0;
0855             int month = 0;
0856             int day = 0;
0857 
0858             QStringList publisher;
0859             QStringList keywords;
0860             QStringList empty;
0861 
0862             while(xmlReader.readNextStartElement())
0863             {
0864                 if(xmlReader.name() == QStringLiteral("Title"))
0865                 {
0866                     acbfDocument->metaData()->bookInfo()->setTitle(xmlReader.readElementText(),"");
0867                 }
0868 
0869                 //Summary/annotation.
0870                 else if(xmlReader.name() == QStringLiteral("Summary"))
0871                 {
0872                     acbfDocument->metaData()->bookInfo()->setAnnotation(xmlReader.readElementText().split("\n\n"), "");
0873                 }
0874 
0875                 /*
0876                  * This ought to go into the kfile metadata.
0877                  */
0878                 else if(xmlReader.name() == QStringLiteral("Notes"))
0879                 {
0880                     if (filedata.userComment().isEmpty()) {
0881                         filedata.setUserComment(xmlReader.readElementText());
0882                     } else {
0883                         xmlReader.skipCurrentElement();
0884                     }
0885                 }
0886                 else if(xmlReader.name() == QStringLiteral("Tags"))
0887                 {
0888                     QStringList tags = filedata.tags();
0889                     QStringList newTags = xmlReader.readElementText().split(",");
0890                     for (int i=0; i < newTags.size(); i++) {
0891                         if (!tags.contains(newTags.at(i))) {
0892                             tags.append(newTags.at(i));
0893                         }
0894                     }
0895                     filedata.setTags(tags);
0896                 }
0897                 else if(xmlReader.name() == QStringLiteral("PageCount"))
0898                 {
0899                     filedata.setAttribute("Peruse.totalPages", xmlReader.readElementText());
0900                 }
0901                 else if(xmlReader.name() == QStringLiteral("ScanInformation"))
0902                 {
0903                     QString userComment = filedata.userComment();
0904                     userComment.append("\n"+xmlReader.readElementText());
0905                     filedata.setUserComment(userComment);
0906                 }
0907 
0908                 //Series
0909 
0910                 else if(xmlReader.name() == QStringLiteral("Series"))
0911                 {
0912                     series = xmlReader.readElementText();
0913                 }
0914                 else if(xmlReader.name() == QStringLiteral("Number"))
0915                 {
0916                     number = xmlReader.readElementText().toInt();
0917                 }
0918                 else if(xmlReader.name() == QStringLiteral("Volume"))
0919                 {
0920                     volume = xmlReader.readElementText().toInt();
0921                 }
0922 
0923                 // Series alt
0924 
0925                 else if(xmlReader.name() == QStringLiteral("AlternateSeries"))
0926                 {
0927                     seriesAlt = xmlReader.readElementText();
0928                 }
0929                 else if(xmlReader.name() == QStringLiteral("AlternateNumber"))
0930                 {
0931                     numberAlt = xmlReader.readElementText().toInt();
0932                 }
0933                 else if(xmlReader.name() == QStringLiteral("AlternateVolume"))
0934                 {
0935                     volumeAlt = xmlReader.readElementText().toInt();
0936                 }
0937 
0938                 // Publishing date.
0939 
0940                 else if(xmlReader.name() == QStringLiteral("Year"))
0941                 {
0942                     year = xmlReader.readElementText().toInt();
0943                 }
0944                 else if(xmlReader.name() == QStringLiteral("Month"))
0945                 {
0946                     month = xmlReader.readElementText().toInt();
0947                 }
0948                 else if(xmlReader.name() == QStringLiteral("Day"))
0949                 {
0950                     day = xmlReader.readElementText().toInt();
0951                 }
0952 
0953                 //Publisher
0954 
0955                 else if(xmlReader.name() == QStringLiteral("Publisher"))
0956                 {
0957                     publisher.append(xmlReader.readElementText());
0958                 }
0959                 else if(xmlReader.name() == QStringLiteral("Imprint"))
0960                 {
0961                     publisher.append(xmlReader.readElementText());
0962                 }
0963 
0964                 //Genre
0965                 else if(xmlReader.name() == QStringLiteral("Genre"))
0966                 {
0967                     QString key = xmlReader.readElementText();
0968                     QString genreKey = key.toLower().replace(" ", "_");
0969                     if (acbfDocument->metaData()->bookInfo()->availableGenres().contains(genreKey)) {
0970                         acbfDocument->metaData()->bookInfo()->setGenre(genreKey);
0971                     } else {
0972                         //There must always be a genre in a proper acbf file...
0973                         acbfDocument->metaData()->bookInfo()->setGenre("other");
0974                         keywords.append(key);
0975                     }
0976                 }
0977 
0978                 //Language
0979 
0980                 else if(xmlReader.name() == QStringLiteral("LanguageISO"))
0981                 {
0982                     acbfDocument->metaData()->bookInfo()->addLanguage(xmlReader.readElementText());
0983                 }
0984 
0985                 //Sources/Weblink
0986                 else if(xmlReader.name() == QStringLiteral("Web"))
0987                 {
0988                     acbfDocument->metaData()->documentInfo()->setSource(QStringList(xmlReader.readElementText()));
0989                 }
0990 
0991                 //One short, trade, etc.
0992                 else if(xmlReader.name() == QStringLiteral("Format"))
0993                 {
0994                     keywords.append(xmlReader.readElementText());
0995                 }
0996 
0997                 //Is this a manga?
0998                 else if(xmlReader.name() == QStringLiteral("Manga"))
0999                 {
1000                     if (xmlReader.readElementText() == "Yes") {
1001                         acbfDocument->metaData()->bookInfo()->setGenre("manga");
1002                         acbfDocument->metaData()->bookInfo()->setRightToLeft(true);
1003                     }
1004                 }
1005                 //Content rating...
1006                 else if(xmlReader.name() == QStringLiteral("AgeRating"))
1007                 {
1008                     acbfDocument->metaData()->bookInfo()->addContentRating(xmlReader.readElementText());
1009                 }
1010 
1011                 //Authors...
1012                 else if(xmlReader.name() == QStringLiteral("Writer"))
1013                 {
1014                     QStringList people = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1015                     for (int i=0; i < people.size(); i++) {
1016                         acbfDocument->metaData()->bookInfo()->addAuthor("Writer", "", "", "", "", people.at(i).trimmed(), empty, empty);
1017                     }
1018                 }
1019                 else if(xmlReader.name() == QStringLiteral("Plotter"))
1020                 {
1021                     QStringList people = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1022                     for (int i=0; i < people.size(); i++) {
1023                         acbfDocument->metaData()->bookInfo()->addAuthor("Writer", "", "", "", "", people.at(i).trimmed(), empty, empty);
1024                     }
1025                 }
1026                 else if(xmlReader.name() == QStringLiteral("Scripter"))
1027                 {
1028                     QStringList people = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1029                     for (int i=0; i < people.size(); i++) {
1030                         acbfDocument->metaData()->bookInfo()->addAuthor("Writer", "", "", "", "", people.at(i).trimmed(), empty, empty);
1031                     }
1032                 }
1033                 else if(xmlReader.name() == QStringLiteral("Penciller"))
1034                 {
1035                     QStringList people = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1036                     for (int i=0; i < people.size(); i++) {
1037                         acbfDocument->metaData()->bookInfo()->addAuthor("Penciller", "", "", "", "", people.at(i).trimmed(), empty, empty);
1038                     }
1039                 }
1040                 else if(xmlReader.name() == QStringLiteral("Inker"))
1041                 {
1042                     QStringList people = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1043                     for (int i=0; i < people.size(); i++) {
1044                         acbfDocument->metaData()->bookInfo()->addAuthor("Inker", "", "", "", "", people.at(i).trimmed(), empty, empty);
1045                     }
1046                 }
1047                 else if(xmlReader.name() == QStringLiteral("Colorist"))
1048                 {
1049                     QStringList people = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1050                     for (int i=0; i < people.size(); i++) {
1051                         acbfDocument->metaData()->bookInfo()->addAuthor("Colorist", "", "", "", "", people.at(i).trimmed(), empty, empty);
1052                     }
1053                 }
1054                 else if(xmlReader.name() == QStringLiteral("CoverArtist"))
1055                 {
1056                     QStringList people = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1057                     for (int i=0; i < people.size(); i++) {
1058                         acbfDocument->metaData()->bookInfo()->addAuthor("CoverArtist", "", "", "", "", people.at(i).trimmed(), empty, empty);
1059                     }
1060                 }
1061                 else if(xmlReader.name() == QStringLiteral("Letterer"))
1062                 {
1063                     QStringList people = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1064                     for (int i=0; i < people.size(); i++) {
1065                         acbfDocument->metaData()->bookInfo()->addAuthor("Letterer", "", "", "", "", people.at(i).trimmed(), empty, empty);
1066                     }
1067                 }
1068                 else if(xmlReader.name() == QStringLiteral("Editor"))
1069                 {
1070                     QStringList people = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1071                     for (int i=0; i < people.size(); i++) {
1072                         acbfDocument->metaData()->bookInfo()->addAuthor("Editor", "", "", "", "", people.at(i).trimmed(), empty, empty);
1073                     }
1074                 }
1075                 else if(xmlReader.name() == QStringLiteral("Other"))
1076                 {
1077                     QStringList people = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1078                     for (int i=0; i < people.size(); i++) {
1079                         acbfDocument->metaData()->bookInfo()->addAuthor("Other", "", "", "", "", people.at(i).trimmed(), empty, empty);
1080                     }
1081                 }
1082                 //Characters...
1083                 else if(xmlReader.name() == QStringLiteral("Characters"))
1084                 {
1085                     QStringList people = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1086                     for (int i=0; i < people.size(); i++) {
1087                         acbfDocument->metaData()->bookInfo()->addCharacter(people.at(i).trimmed());
1088                     }
1089                 }
1090                 //Throw the rest into the keywords.
1091                 else if(xmlReader.name() == QStringLiteral("Teams"))
1092                 {
1093                     QStringList teams = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1094                     for (int i=0; i < teams.size(); i++) {
1095                         keywords.append(teams.at(i).trimmed());
1096                     }
1097                 }
1098                 else if(xmlReader.name() == QStringLiteral("Locations"))
1099                 {
1100                     QStringList locations = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1101                     for (int i=0; i < locations.size(); i++) {
1102                         keywords.append(locations.at(i).trimmed());
1103                     }
1104                 }
1105                 else if(xmlReader.name() == QStringLiteral("StoryArc"))
1106                 {
1107                     QStringList arc = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1108                     for (int i=0; i < arc.size(); i++) {
1109                         keywords.append(arc.at(i).trimmed());
1110                     }
1111                 }
1112                 else if(xmlReader.name() == QStringLiteral("SeriesGroup"))
1113                 {
1114                     QStringList group = xmlReader.readElementText().split(",", Qt::SkipEmptyParts);
1115                     for (int i=0; i < group.size(); i++) {
1116                         keywords.append(group.at(i).trimmed());
1117                     }
1118                 }
1119 
1120                 //Pages...
1121                 else if(xmlReader.name() == QStringLiteral("Pages")) {
1122                     while(xmlReader.readNextStartElement()) {
1123                         if(xmlReader.name() == QStringLiteral("Page"))
1124                         {
1125                             int index = xmlReader.attributes().value(QStringLiteral("Image")).toInt();
1126                             QString type = xmlReader.attributes().value(QStringLiteral("Type")).toString();
1127                             QString bookmark = xmlReader.attributes().value(QStringLiteral("Bookmark")).toString();
1128                             AdvancedComicBookFormat::Page* page = new AdvancedComicBookFormat::Page(acbfDocument);
1129                             page->setImageHref(entries.at(index));
1130                             if (type ==  QStringLiteral("FrontCover")) {
1131                                 acbfDocument->metaData()->bookInfo()->setCoverpage(page);
1132                             } else {
1133                                 if (bookmark.isEmpty()) {
1134                                     page->setTitle(type.append(QString::number(index)));
1135                                 } else {
1136                                     page->setTitle(bookmark);
1137                                 }
1138                                 acbfDocument->body()->addPage(page, index-1);
1139                             }
1140                             xmlReader.readNext();
1141                         }
1142                     }
1143                 }
1144 
1145                 else
1146                 {
1147                     qCWarning(QTQUICK_LOG) << Q_FUNC_INFO << "currently unsupported subsection:" << xmlReader.name();
1148                     xmlReader.skipCurrentElement();
1149                 }
1150             }
1151 
1152             if (!series.isEmpty() && number>-1) {
1153                 acbfDocument->metaData()->bookInfo()->addSequence(number, series, volume);
1154             }
1155             if (!seriesAlt.isEmpty() && numberAlt>-1) {
1156                 acbfDocument->metaData()->bookInfo()->addSequence(numberAlt, seriesAlt, volumeAlt);
1157             }
1158 
1159             if (year > 0 || month > 0 || day > 0) {
1160                 //acbfDocument->metaData()->publishInfo()->setPublishDateFromInts(year, month, day);
1161             }
1162 
1163             if (publisher.size()>0) {
1164                 acbfDocument->metaData()->publishInfo()->setPublisher(publisher.join(", "));
1165             }
1166 
1167             if (keywords.size()>0) {
1168                 acbfDocument->metaData()->bookInfo()->setKeywords(keywords, "");
1169             }
1170 
1171             if (acbfDocument->metaData()->bookInfo()->languages().size()>0) {
1172                 QString lang = acbfDocument->metaData()->bookInfo()->languageEntryList().at(0);
1173                 acbfDocument->metaData()->bookInfo()->setTitle(acbfDocument->metaData()->bookInfo()->title(""), lang);
1174                 acbfDocument->metaData()->bookInfo()->setAnnotation(acbfDocument->metaData()->bookInfo()->annotation(""), lang);
1175                 acbfDocument->metaData()->bookInfo()->setKeywords(acbfDocument->metaData()->bookInfo()->keywords(""), lang);
1176             }
1177         }
1178     }
1179 
1180     if (xmlReader.hasError()) {
1181         qCWarning(QTQUICK_LOG) << Q_FUNC_INFO << "Failed to read Comic Info XML document at token" << xmlReader.name() << "(" << xmlReader.lineNumber() << ":" << xmlReader.columnNumber() << ") The reported error was:" << xmlReader.errorString();
1182     }
1183     qCDebug(QTQUICK_LOG) << Q_FUNC_INFO << "Completed ACBF document creation from ComicInfo.xml for" << acbfDocument->metaData()->bookInfo()->title();
1184     acbfData = acbfDocument;
1185     return !xmlReader.hasError();
1186 }
1187 
1188 bool ArchiveBookModel::loadCoMet(QStringList xmlDocuments, QObject *acbfData, QStringList entries, QString filename)
1189 {
1190     KFileMetaData::UserMetaData filedata(filename);
1191     AdvancedComicBookFormat::Document* acbfDocument = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData);
1192     for(const QString& xmlDocument : qAsConst(xmlDocuments)) {
1193         QMutexLocker locker(&archiveMutex);
1194         const KArchiveFile* archFile = d->archive->directory()->file(xmlDocument);
1195         QXmlStreamReader xmlReader(archFile->data());
1196         if(xmlReader.readNextStartElement())
1197         {
1198             if(xmlReader.name() == QStringLiteral("comet"))
1199             {
1200                 // We'll need to collect several items to generate a series. Thankfully, comicinfo only has two types of series.
1201                 QString series;
1202                 int number = -1;
1203                 int volume = 0;
1204 
1205                 QStringList keywords;
1206                 QStringList empty;
1207 
1208                 while(xmlReader.readNextStartElement())
1209                 {
1210                     if(xmlReader.name() == QStringLiteral("title"))
1211                     {
1212                         acbfDocument->metaData()->bookInfo()->setTitle(xmlReader.readElementText(),"");
1213                     }
1214 
1215                     //Summary/annotation.
1216                     else if(xmlReader.name() == QStringLiteral("description"))
1217                     {
1218                         acbfDocument->metaData()->bookInfo()->setAnnotation(xmlReader.readElementText().split("\n\n"), "");
1219                     }
1220 
1221                     /*
1222                      * This ought to go into the kfile metadata.
1223                      */
1224                     //pages
1225                     else if(xmlReader.name() == QStringLiteral("pages"))
1226                     {
1227                         filedata.setAttribute("Peruse.totalPages", xmlReader.readElementText());
1228                     }
1229                     //curentpage -- only read this when there's no such entry.
1230                     else if(xmlReader.name() == QStringLiteral("lastMark"))
1231                     {
1232                         if (!filedata.hasAttribute("Peruse.currentPage")) {
1233                             filedata.setAttribute("Peruse.currentPage", xmlReader.readElementText());
1234                         } else {
1235                             xmlReader.skipCurrentElement();
1236                         }
1237                     }
1238 
1239                     //Series
1240 
1241                     else if(xmlReader.name() == QStringLiteral("series"))
1242                     {
1243                         series = xmlReader.readElementText();
1244                     }
1245                     else if(xmlReader.name() == QStringLiteral("issue"))
1246                     {
1247                         number = xmlReader.readElementText().toInt();
1248                     }
1249                     else if(xmlReader.name() == QStringLiteral("volume"))
1250                     {
1251                         volume = xmlReader.readElementText().toInt();
1252                     }
1253 
1254                     // Publishing date.
1255 
1256                     else if(xmlReader.name() == QStringLiteral("date"))
1257                     {
1258                         acbfDocument->metaData()->publishInfo()->setPublishDate(QDate::fromString(xmlReader.readElementText(), Qt::ISODate));
1259                     }
1260 
1261                     //Publisher
1262 
1263                     else if(xmlReader.name() == QStringLiteral("publisher"))
1264                     {
1265                         acbfDocument->metaData()->publishInfo()->setPublisher(xmlReader.readElementText());
1266                     }
1267                     else if(xmlReader.name() == QStringLiteral("rights"))
1268                     {
1269                         acbfDocument->metaData()->publishInfo()->setLicense(xmlReader.readElementText());
1270                     }
1271                     else if(xmlReader.name() == QStringLiteral("identifier"))
1272                     {
1273                         acbfDocument->metaData()->publishInfo()->setIsbn(xmlReader.readElementText());
1274                     }
1275 
1276                     //Genre
1277                     else if(xmlReader.name() == QStringLiteral("genre"))
1278                     {
1279                         QString key = xmlReader.readElementText();
1280                         QString genreKey = key.toLower().replace(" ", "_");
1281                         if (acbfDocument->metaData()->bookInfo()->availableGenres().contains(genreKey)) {
1282                             acbfDocument->metaData()->bookInfo()->setGenre(genreKey);
1283                         } else {
1284                             keywords.append(key);
1285                         }
1286                     }
1287 
1288                     //Language
1289 
1290                     else if(xmlReader.name() == QStringLiteral("language"))
1291                     {
1292                         acbfDocument->metaData()->bookInfo()->addLanguage(xmlReader.readElementText());
1293                     }
1294 
1295                     //Sources/Weblink
1296                     else if(xmlReader.name() == QStringLiteral("isVersionOf"))
1297                     {
1298                         acbfDocument->metaData()->documentInfo()->setSource(QStringList(xmlReader.readElementText()));
1299                     }
1300 
1301                     //One short, trade, etc.
1302                     else if(xmlReader.name() == QStringLiteral("format"))
1303                     {
1304                         keywords.append(xmlReader.readElementText());
1305                     }
1306 
1307                     //Is this a manga?
1308                     else if(xmlReader.name() == QStringLiteral("readingDirection"))
1309                     {
1310                         if (xmlReader.readElementText() == "rtl") {
1311                             acbfDocument->metaData()->bookInfo()->setRightToLeft(true);
1312                         }
1313                     }
1314                     //Content rating...
1315                     else if(xmlReader.name() == QStringLiteral("rating"))
1316                     {
1317                         acbfDocument->metaData()->bookInfo()->addContentRating(xmlReader.readElementText());
1318                     }
1319 
1320                     //Authors...
1321                     else if(xmlReader.name() == QStringLiteral("writer"))
1322                     {
1323                         QString person = xmlReader.readElementText();
1324                         acbfDocument->metaData()->bookInfo()->addAuthor("Writer", "", "", "", "", person, empty, empty);
1325                     }
1326                     else if(xmlReader.name() == QStringLiteral("creator"))
1327                     {
1328                         QString person = xmlReader.readElementText();
1329                         acbfDocument->metaData()->bookInfo()->addAuthor("Writer", "", "", "", "", person, empty, empty);
1330                     }
1331                     else if(xmlReader.name() == QStringLiteral("penciller"))
1332                     {
1333                         QString person = xmlReader.readElementText();
1334                         acbfDocument->metaData()->bookInfo()->addAuthor("Penciller", "", "", "", "", person, empty, empty);
1335                     }
1336                     else if(xmlReader.name() == QStringLiteral("editor"))
1337                     {
1338                         QString person = xmlReader.readElementText();
1339                         acbfDocument->metaData()->bookInfo()->addAuthor("Editor", "", "", "", "", person, empty, empty);
1340                     }
1341                     else if(xmlReader.name() == QStringLiteral("coverDesigner"))
1342                     {
1343                         QString person = xmlReader.readElementText();
1344                         acbfDocument->metaData()->bookInfo()->addAuthor("CoverArtist", "", "", "", "", person, empty, empty);
1345                     }
1346                     else if(xmlReader.name() == QStringLiteral("letterer"))
1347                     {
1348                         QString person = xmlReader.readElementText();
1349                         acbfDocument->metaData()->bookInfo()->addAuthor("Letterer", "", "", "", "", person, empty, empty);
1350                     }
1351                     else if(xmlReader.name() == QStringLiteral("inker"))
1352                     {
1353                         QString person = xmlReader.readElementText();
1354                         acbfDocument->metaData()->bookInfo()->addAuthor("Inker", "", "", "", "", person, empty, empty);
1355                     }
1356                     else if(xmlReader.name() == QStringLiteral("colorist"))
1357                     {
1358                         QString person = xmlReader.readElementText();
1359                         acbfDocument->metaData()->bookInfo()->addAuthor("Colorist", "", "", "", "", person, empty, empty);
1360                     }
1361 
1362                     //Characters
1363                     else if(xmlReader.name() == QStringLiteral("character"))
1364                     {
1365                         QString person = xmlReader.readElementText();
1366                         acbfDocument->metaData()->bookInfo()->addCharacter(person);
1367                     }
1368 
1369 
1370                     //Get the cover image, set it, remove it from entries, then remove all other entries.
1371                     else if(xmlReader.name() == QStringLiteral("coverImage"))
1372                     {
1373                         QString url = xmlReader.readElementText();
1374                         AdvancedComicBookFormat::Page* cover = new AdvancedComicBookFormat::Page(acbfDocument);
1375                         cover->setImageHref(url);
1376                         acbfDocument->metaData()->bookInfo()->setCoverpage(cover);
1377                         entries.removeAll(url);
1378                         for(const QString& entry : qAsConst(entries)) {
1379                             AdvancedComicBookFormat::Page* page = new AdvancedComicBookFormat::Page(acbfDocument);
1380                             page->setImageHref(entry);
1381                             acbfDocument->body()->addPage(page);
1382                         }
1383                         xmlReader.readNext();
1384                     }
1385 
1386                     else
1387                     {
1388                         qCWarning(QTQUICK_LOG) << Q_FUNC_INFO << "currently unsupported subsection:" << xmlReader.name();
1389                         xmlReader.skipCurrentElement();
1390                     }
1391                 }
1392 
1393                 if (!series.isEmpty() && number>-1) {
1394                     acbfDocument->metaData()->bookInfo()->addSequence(number, series, volume);
1395                 }
1396 
1397                 if (acbfDocument->metaData()->bookInfo()->genres().size()==0) {
1398                     //There must always be a genre in a proper acbf file...
1399                     acbfDocument->metaData()->bookInfo()->setGenre("other");
1400                 }
1401 
1402                 if (keywords.size()>0) {
1403                     acbfDocument->metaData()->bookInfo()->setKeywords(keywords, "");
1404                 }
1405 
1406                 if (acbfDocument->metaData()->bookInfo()->languages().size()>0) {
1407                     QString lang = acbfDocument->metaData()->bookInfo()->languageEntryList().at(0);
1408                     acbfDocument->metaData()->bookInfo()->setTitle(acbfDocument->metaData()->bookInfo()->title(""), lang);
1409                     acbfDocument->metaData()->bookInfo()->setAnnotation(acbfDocument->metaData()->bookInfo()->annotation(""), lang);
1410                     acbfDocument->metaData()->bookInfo()->setKeywords(acbfDocument->metaData()->bookInfo()->keywords(""), lang);
1411                 }
1412             }
1413             if (xmlReader.hasError()) {
1414                 qCWarning(QTQUICK_LOG) << Q_FUNC_INFO << "Failed to read CoMet document at token" << xmlReader.name()
1415                            << "(" << xmlReader.lineNumber() << ":"
1416                            << xmlReader.columnNumber() << ") The reported error was:" << xmlReader.errorString();
1417             }
1418             qCDebug(QTQUICK_LOG) << Q_FUNC_INFO << "Completed ACBF document creation from CoMet for" << acbfDocument->metaData()->bookInfo()->title();
1419             acbfData = acbfDocument;
1420             return !xmlReader.hasError();
1421         } else {
1422             xmlReader.skipCurrentElement();
1423         }
1424     }
1425     return false;
1426 }
1427 
1428 QString ArchiveBookModel::previewForId(const QString& id) const
1429 {
1430     static const QString directorySplit{"/"};
1431     static const QString period{"."};
1432     static const QString acbfSuffix{"acbf"};
1433     if (d->archive) {
1434         if (id.splitRef(directorySplit).last().contains(period)) {
1435             const QString suffix = id.splitRef(period).last().toString().toLower();
1436             if (d->imageProvider && QImageReader::supportedImageFormats().contains(suffix.toLatin1())) {
1437                 return QString("image://%1/%2").arg(d->imageProvider->prefix()).arg(id);
1438             } else if (suffix == acbfSuffix) {
1439                 return QString{"image://icon/data-information"};
1440             } else {
1441                 QList<QMimeType> mimetypes = d->mimeDatabase.mimeTypesForFileName(id);
1442                 if (mimetypes.count() > 0) {
1443                     return QString("image://icon/").append(mimetypes.first().iconName());
1444                 }
1445             }
1446         } else {
1447             return QString{"image://icon/folder"};
1448         }
1449     }
1450     return QString();
1451 }
1452 
1453 QString ArchiveBookModel::fontFamilyName(const QString& fontFileName)
1454 {
1455     QString familyName;
1456     if (!fontFileName.isEmpty()) {
1457         if (d->fontIdByFilename.contains(fontFileName)) {
1458             familyName = QFontDatabase::applicationFontFamilies(d->fontIdByFilename.value(fontFileName)).first();
1459         } else {
1460             AdvancedComicBookFormat::Document* acbf = qobject_cast<AdvancedComicBookFormat::Document*>(acbfData());
1461             if (acbf) {
1462                 AdvancedComicBookFormat::Binary* binary = qobject_cast<AdvancedComicBookFormat::Binary*>(acbf->objectByID(fontFileName));
1463                 if (binary) {
1464                     int id = QFontDatabase::addApplicationFontFromData(binary->data());
1465                     if (id > -1) {
1466                         d->fontIdByFilename[fontFileName] = id;
1467                         familyName = QFontDatabase::applicationFontFamilies(d->fontIdByFilename.value(fontFileName)).first();
1468                     }
1469                 }
1470             }
1471             if (familyName.isEmpty()) {
1472                 QString foundEntry;
1473                 // If there's more files by the same name, just assume it's the first one in a DFS, because it won't be sensibly deducible anyway
1474                 for (const QString& entry : d->fileEntries) {
1475                     if (entry.endsWith(fontFileName)) {
1476                         foundEntry = entry;
1477                         break;
1478                     }
1479                 }
1480                 auto file = archiveFile(foundEntry);
1481                 if (file) {
1482                     int id = QFontDatabase::addApplicationFontFromData(file->data());
1483                     if (id > -1) {
1484                         d->fontIdByFilename[fontFileName] = id;
1485                         familyName = QFontDatabase::applicationFontFamilies(d->fontIdByFilename.value(fontFileName)).first();
1486                     }
1487                 }
1488             }
1489         }
1490     }
1491     return familyName;
1492 }
1493 
1494 QString ArchiveBookModel::firstAvailableFont(const QStringList& fontList)
1495 {
1496     QString availableFont;
1497     for (const QString& fontName : fontList) {
1498         QString actualName = fontName;
1499         if (fontName.toLower().endsWith("ttf") || fontName.toLower().endsWith("ttc")) {
1500             actualName = fontFamilyName(fontName);
1501         }
1502         if (actualName.isEmpty()) {
1503             if (d->fontDatabase.hasFamily(fontName)) {
1504                 actualName = fontName;
1505             }
1506         }
1507         if (!actualName.isEmpty()) {
1508             availableFont = actualName;
1509             break;
1510         }
1511     }
1512     return availableFont;
1513 }