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 }