diff --git a/YACReaderLibrary/YACReaderLibrary.pro b/YACReaderLibrary/YACReaderLibrary.pro index ee810eca..0ff05782 100644 --- a/YACReaderLibrary/YACReaderLibrary.pro +++ b/YACReaderLibrary/YACReaderLibrary.pro @@ -78,6 +78,7 @@ HEADERS += comic_flow.h \ db/comic_query_result_processor.h \ db/folder_query_result_processor.h \ db/query_lexer.h \ + initial_comic_info_extractor.h \ library_comic_opener.h \ library_creator.h \ library_window.h \ @@ -116,6 +117,8 @@ HEADERS += comic_flow.h \ no_libraries_widget.h \ import_widget.h \ trayicon_controller.h \ + xml_info_library_scanner.h \ + xml_info_parser.h \ yacreader_local_server.h \ yacreader_main_toolbar.h \ comics_remover.h \ @@ -157,6 +160,7 @@ SOURCES += comic_flow.cpp \ db/comic_query_result_processor.cpp \ db/folder_query_result_processor.cpp \ db/query_lexer.cpp \ + initial_comic_info_extractor.cpp \ library_comic_opener.cpp \ library_creator.cpp \ library_window.cpp \ @@ -191,6 +195,8 @@ SOURCES += comic_flow.cpp \ no_libraries_widget.cpp \ import_widget.cpp \ trayicon_controller.cpp \ + xml_info_library_scanner.cpp \ + xml_info_parser.cpp \ yacreader_local_server.cpp \ yacreader_main_toolbar.cpp \ comics_remover.cpp \ diff --git a/YACReaderLibrary/db/data_base_management.cpp b/YACReaderLibrary/db/data_base_management.cpp index 93c01af2..c77db70c 100644 --- a/YACReaderLibrary/db/data_base_management.cpp +++ b/YACReaderLibrary/db/data_base_management.cpp @@ -1,12 +1,14 @@ #include "data_base_management.h" #include -#include "library_creator.h" +#include "initial_comic_info_extractor.h" #include "check_new_version.h" #include "db_helper.h" #include "QsLog.h" +using namespace YACReader; + static QString fields = "title ," "coverPage," @@ -156,7 +158,7 @@ bool DataBaseManagement::createTables(QSqlDatabase &database) "publisher TEXT," "format TEXT," "color BOOLEAN," - "ageRating BOOLEAN," + "ageRating BOOLEAN," //this is actually a string (TEXT), funny thing is that the current implementation works "synopsis TEXT," "characters TEXT," @@ -543,8 +545,8 @@ bool DataBaseManagement::importComicsInfo(QString source, QString dest) QString basePath = QString(dest).remove("/.yacreaderlibrary/library.ydb"); QString path = basePath + getComic.record().value("path").toString(); int coverPage = getComic.record().value("coverPage").toInt(); - ThumbnailCreator tc(path, basePath + "/.yacreaderlibrary/covers/" + hash + ".jpg", coverPage); - tc.create(); + InitialComicInfoExtractor ie(path, basePath + "/.yacreaderlibrary/covers/" + hash + ".jpg", coverPage); + ie.extract(); } } sourceDBconnection = sourceDB.connectionName(); diff --git a/YACReaderLibrary/db_helper.cpp b/YACReaderLibrary/db_helper.cpp index 6fa4edf9..13c632cc 100644 --- a/YACReaderLibrary/db_helper.cpp +++ b/YACReaderLibrary/db_helper.cpp @@ -1154,7 +1154,7 @@ qulonglong DBHelper::insert(Folder *folder, QSqlDatabase &db) return query.lastInsertId().toULongLong(); } -qulonglong DBHelper::insert(ComicDB *comic, QSqlDatabase &db) +qulonglong DBHelper::insert(ComicDB *comic, QSqlDatabase &db, bool insertAllInfo) { if (!comic->info.existOnDb) { QSqlQuery comicInfoInsert(db); @@ -1167,6 +1167,10 @@ qulonglong DBHelper::insert(ComicDB *comic, QSqlDatabase &db) comicInfoInsert.exec(); comic->info.id = comicInfoInsert.lastInsertId().toULongLong(); comic->_hasCover = false; + + if (insertAllInfo) { + DBHelper::update(&(comic->info), db); //TODO use insert to insert all the info values, the common binding need to be extracted and shared between update and insert + } } else comic->_hasCover = true; diff --git a/YACReaderLibrary/db_helper.h b/YACReaderLibrary/db_helper.h index abf69625..db81f020 100644 --- a/YACReaderLibrary/db_helper.h +++ b/YACReaderLibrary/db_helper.h @@ -53,7 +53,7 @@ public: static void deleteComicsFromReadingList(const QList &comicsList, qulonglong readingListId, QSqlDatabase &db); //inserts static qulonglong insert(Folder *folder, QSqlDatabase &db); - static qulonglong insert(ComicDB *comic, QSqlDatabase &db); + static qulonglong insert(ComicDB *comic, QSqlDatabase &db, bool insertAllInfo); static qulonglong insertLabel(const QString &name, YACReader::LabelColors color, QSqlDatabase &db); static qulonglong insertReadingList(const QString &name, QSqlDatabase &db); static qulonglong insertReadingSubList(const QString &name, qulonglong parentId, int ordering, QSqlDatabase &db); diff --git a/YACReaderLibrary/import_widget.cpp b/YACReaderLibrary/import_widget.cpp index d33f6ae3..cf4fd03b 100644 --- a/YACReaderLibrary/import_widget.cpp +++ b/YACReaderLibrary/import_widget.cpp @@ -364,6 +364,18 @@ void ImportWidget::setUpgradeLook() hideButton->setVisible(false); } +void ImportWidget::setXMLScanLook() +{ + iconLabel->setPixmap(QPixmap(":/images/updatingIcon.png")); + text->setText("" + tr("Scanning the library") + ""); + textDescription->setText("" + tr("

Current library is being scanned for legacy XML metadata information.

This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.

") + "
"); + + stopButton->setVisible(true); + coversLabel->setVisible(false); + coversViewContainer->setVisible(false); + hideButton->setVisible(false); +} + void ImportWidget::clearScene() { } diff --git a/YACReaderLibrary/import_widget.h b/YACReaderLibrary/import_widget.h index 78b7daf9..21d4c755 100644 --- a/YACReaderLibrary/import_widget.h +++ b/YACReaderLibrary/import_widget.h @@ -20,6 +20,7 @@ public slots: void setImportLook(); void setUpdateLook(); void setUpgradeLook(); + void setXMLScanLook(); void showCovers(bool hide); private: diff --git a/YACReaderLibrary/initial_comic_info_extractor.cpp b/YACReaderLibrary/initial_comic_info_extractor.cpp new file mode 100644 index 00000000..d275f8a6 --- /dev/null +++ b/YACReaderLibrary/initial_comic_info_extractor.cpp @@ -0,0 +1,174 @@ +#include "initial_comic_info_extractor.h" + +#include +#include "pdf_comic.h" +#include "comic.h" +#include "compressed_archive.h" +#include "qnaturalsorting.h" + +using namespace YACReader; + +bool InitialComicInfoExtractor::crash = false; + +InitialComicInfoExtractor::InitialComicInfoExtractor(QString fileSource, QString target, int coverPage) + : _fileSource(fileSource), _target(target), _numPages(0), _coverPage(coverPage), _xmlInfoData() +{ +} + +void InitialComicInfoExtractor::extract() +{ + QFileInfo fi(_fileSource); + if (!fi.exists()) //TODO: error file not found. + { + _cover.load(":/images/notCover.png"); + QLOG_WARN() << "Extracting cover: file not found " << _fileSource; + return; + } +#ifndef NO_PDF + if (fi.suffix().compare("pdf", Qt::CaseInsensitive) == 0) { +#if defined Q_OS_MAC && defined USE_PDFKIT + MacOSXPDFComic *pdfComic = new MacOSXPDFComic(); + if (!pdfComic->openComic(_fileSource)) { + delete pdfComic; + //QImage p; + //p.load(":/images/notCover.png"); + //p.save(_target); + return; + } +#elif defined USE_PDFIUM + auto pdfComic = new PdfiumComic(); + if (!pdfComic->openComic(_fileSource)) { + delete pdfComic; + return; + } +#else + Poppler::Document *pdfComic = Poppler::Document::load(_fileSource); +#endif + + if (!pdfComic) { + QLOG_WARN() << "Extracting cover: unable to open PDF file " << _fileSource; + //delete pdfComic; //TODO check if the delete is needed + pdfComic = 0; + //QImage p; + //p.load(":/images/notCover.png"); + //p.save(_target); + return; + } +#if !defined USE_PDFKIT && !defined USE_PDFIUM + //poppler only, not mac + if (pdfComic->isLocked()) { + QLOG_WARN() << "Extracting cover: unable to open PDF file " << _fileSource; + delete pdfComic; + return; + } +#endif + _numPages = pdfComic->numPages(); + if (_numPages >= _coverPage) { +#if defined Q_OS_MAC || defined USE_PDFIUM + QImage p = pdfComic->getPage(_coverPage - 1); //TODO check if the page is valid +#else + QImage p = pdfComic->page(_coverPage - 1)->renderToImage(72, 72); +#endif // + _cover = p; + _coverSize = QPair(p.width(), p.height()); + if (_target != "") { + QImage scaled; + if (p.width() > p.height()) //landscape?? + { + scaled = p.scaledToWidth(640, Qt::SmoothTransformation); + } else { + scaled = p.scaledToWidth(480, Qt::SmoothTransformation); + } + scaled.save(_target, 0, 75); + } else if (_target != "") { + QLOG_WARN() << "Extracting cover: requested cover index greater than numPages " << _fileSource; + //QImage p; + //p.load(":/images/notCover.png"); + //p.save(_target); + } + delete pdfComic; + } + return; + } +#endif //NO_PDF + + if (crash) { + return; + } + + CompressedArchive archive(_fileSource); + if (!archive.toolsLoaded()) { + QLOG_WARN() << "Extracting cover: 7z lib not loaded"; + crash = true; + return; + } + if (!archive.isValid()) { + QLOG_WARN() << "Extracting cover: file format not supported " << _fileSource; + } + + QList order = archive.getFileNames(); + + //Try to find embeded XML info (ComicRack or ComicTagger) + + auto infoIndex = 0; + for (auto &fileName : order) { + if (fileName.endsWith(".xml", Qt::CaseInsensitive)) { + _xmlInfoData = archive.getRawDataAtIndex(infoIndex); + break; + } + + infoIndex++; + } + + //-------------------------- + + if (_target == "None") { + return; + } + + //se filtran para obtener sólo los formatos soportados + QList fileNames = FileComic::filter(order); + _numPages = fileNames.size(); + if (_numPages == 0) { + QLOG_WARN() << "Extracting cover: empty comic " << _fileSource; + _cover.load(":/images/notCover.png"); + if (_target != "") { + _cover.save(_target); + } + } else { + if (_coverPage > _numPages) { + _coverPage = 1; + } + std::sort(fileNames.begin(), fileNames.end(), naturalSortLessThanCI); + int index = order.indexOf(fileNames.at(_coverPage - 1)); + + if (_target == "") { + if (!_cover.loadFromData(archive.getRawDataAtIndex(index))) { + QLOG_WARN() << "Extracting cover: unable to load image from extracted cover " << _fileSource; + _cover.load(":/images/notCover.png"); + } + } else { + QImage p; + if (p.loadFromData(archive.getRawDataAtIndex(index))) { + _coverSize = QPair(p.width(), p.height()); + QImage scaled; + if (p.width() > p.height()) //landscape?? + { + scaled = p.scaledToWidth(640, Qt::SmoothTransformation); + } else { + scaled = p.scaledToWidth(480, Qt::SmoothTransformation); + } + scaled.save(_target, 0, 75); + } else { + QLOG_WARN() << "Extracting cover: unable to load image from extracted cover " << _fileSource; + //p.load(":/images/notCover.png"); + //p.save(_target); + } + } + } +} + +QByteArray InitialComicInfoExtractor::getXMLInfoRawData() +{ + return _xmlInfoData; +} diff --git a/YACReaderLibrary/initial_comic_info_extractor.h b/YACReaderLibrary/initial_comic_info_extractor.h new file mode 100644 index 00000000..34879da3 --- /dev/null +++ b/YACReaderLibrary/initial_comic_info_extractor.h @@ -0,0 +1,36 @@ +#ifndef INITIALCOMICINFOEXTRACTOR_H +#define INITIALCOMICINFOEXTRACTOR_H + +#include + +namespace YACReader { +class InitialComicInfoExtractor : public QObject +{ + Q_OBJECT + +public: + InitialComicInfoExtractor(QString fileSource, QString target = "", int coverPage = 1); + +private: + QString _fileSource; + QString _target; + QString _currentName; + int _numPages; + QPair _coverSize; + QImage _cover; + int _coverPage; + static bool crash; + QByteArray _xmlInfoData; + +public slots: + void extract(); + int getNumPages() { return _numPages; } + QPixmap getCover() { return QPixmap::fromImage(_cover); } + QPair getOriginalCoverSize() { return _coverSize; } + QByteArray getXMLInfoRawData(); +signals: + void openingError(QProcess::ProcessError error); +}; +} + +#endif // INITIALCOMICINFOEXTRACTOR_H diff --git a/YACReaderLibrary/library_creator.cpp b/YACReaderLibrary/library_creator.cpp index fc426513..a46ecc1c 100644 --- a/YACReaderLibrary/library_creator.cpp +++ b/YACReaderLibrary/library_creator.cpp @@ -12,7 +12,8 @@ #include "qnaturalsorting.h" #include "db_helper.h" -#include "compressed_archive.h" +#include "initial_comic_info_extractor.h" +#include "xml_info_parser.h" #include "comic.h" #include "pdf_comic.h" #include "yacreader_global.h" @@ -20,7 +21,9 @@ #include "QsLog.h" #include + using namespace std; +using namespace YACReader; //-------------------------------------------------------------------------------- LibraryCreator::LibraryCreator() @@ -173,6 +176,7 @@ void LibraryCreator::run() return; } QSqlQuery pragma("PRAGMA foreign_keys = ON", _database); + pragma.exec(); _database.transaction(); if (partialUpdate) { @@ -295,11 +299,13 @@ void LibraryCreator::insertComic(const QString &relativePath, const QFileInfo &f int numPages = 0; QPair originalCoverSize = { 0, 0 }; bool exists = checkCover(hash); + + YACReader::InitialComicInfoExtractor ie(QDir::cleanPath(fileInfo.absoluteFilePath()), _target + "/covers/" + hash + ".jpg", comic.info.coverPage.toInt()); + if (!(comic.hasCover() && exists)) { - ThumbnailCreator tc(QDir::cleanPath(fileInfo.absoluteFilePath()), _target + "/covers/" + hash + ".jpg", comic.info.coverPage.toInt()); - tc.create(); - numPages = tc.getNumPages(); - originalCoverSize = tc.getOriginalCoverSize(); + ie.extract(); + numPages = ie.getNumPages(); + originalCoverSize = ie.getOriginalCoverSize(); if (numPages > 0) { emit(comicAdded(relativePath, _target + "/covers/" + hash + ".jpg")); } @@ -308,6 +314,9 @@ void LibraryCreator::insertComic(const QString &relativePath, const QFileInfo &f if (numPages > 0 || exists) { //en este punto sabemos que todos los folders que hay en _currentPath, deberían estar añadidos a la base de datos insertFolders(); + + bool parsed = YACReader::parseXMLIntoInfo(ie.getXMLInfoRawData(), comic.info); + comic.info.numPages = numPages; if (originalCoverSize.second > 0) { comic.info.originalCoverSize = QString("%1x%2").arg(originalCoverSize.first).arg(originalCoverSize.second); @@ -316,7 +325,8 @@ void LibraryCreator::insertComic(const QString &relativePath, const QFileInfo &f comic.parentId = _currentPathFolders.last().id; comic.info.manga = _currentPathFolders.last().isManga(); - DBHelper::insert(&comic, _database); + + DBHelper::insert(&comic, _database, parsed); } } @@ -539,143 +549,3 @@ void LibraryCreator::update(QDir dirS) } } } - -bool ThumbnailCreator::crash = false; - -ThumbnailCreator::ThumbnailCreator(QString fileSource, QString target, int coverPage) - : _fileSource(fileSource), _target(target), _numPages(0), _coverPage(coverPage) -{ -} - -void ThumbnailCreator::create() -{ - QFileInfo fi(_fileSource); - if (!fi.exists()) //TODO: error file not found. - { - _cover.load(":/images/notCover.png"); - QLOG_WARN() << "Extracting cover: file not found " << _fileSource; - return; - } -#ifndef NO_PDF - if (fi.suffix().compare("pdf", Qt::CaseInsensitive) == 0) { -#if defined Q_OS_MAC && defined USE_PDFKIT - MacOSXPDFComic *pdfComic = new MacOSXPDFComic(); - if (!pdfComic->openComic(_fileSource)) { - delete pdfComic; - //QImage p; - //p.load(":/images/notCover.png"); - //p.save(_target); - return; - } -#elif defined USE_PDFIUM - auto pdfComic = new PdfiumComic(); - if (!pdfComic->openComic(_fileSource)) { - delete pdfComic; - return; - } -#else - Poppler::Document *pdfComic = Poppler::Document::load(_fileSource); -#endif - - if (!pdfComic) { - QLOG_WARN() << "Extracting cover: unable to open PDF file " << _fileSource; - //delete pdfComic; //TODO check if the delete is needed - pdfComic = 0; - //QImage p; - //p.load(":/images/notCover.png"); - //p.save(_target); - return; - } -#if !defined USE_PDFKIT && !defined USE_PDFIUM - //poppler only, not mac - if (pdfComic->isLocked()) { - QLOG_WARN() << "Extracting cover: unable to open PDF file " << _fileSource; - delete pdfComic; - return; - } -#endif - _numPages = pdfComic->numPages(); - if (_numPages >= _coverPage) { -#if defined Q_OS_MAC || defined USE_PDFIUM - QImage p = pdfComic->getPage(_coverPage - 1); //TODO check if the page is valid -#else - QImage p = pdfComic->page(_coverPage - 1)->renderToImage(72, 72); -#endif // - _cover = p; - _coverSize = QPair(p.width(), p.height()); - if (_target != "") { - QImage scaled; - if (p.width() > p.height()) //landscape?? - { - scaled = p.scaledToWidth(640, Qt::SmoothTransformation); - } else { - scaled = p.scaledToWidth(480, Qt::SmoothTransformation); - } - scaled.save(_target, 0, 75); - } else if (_target != "") { - QLOG_WARN() << "Extracting cover: requested cover index greater than numPages " << _fileSource; - //QImage p; - //p.load(":/images/notCover.png"); - //p.save(_target); - } - delete pdfComic; - } - return; - } -#endif //NO_PDF - - if (crash) { - return; - } - - CompressedArchive archive(_fileSource); - if (!archive.toolsLoaded()) { - QLOG_WARN() << "Extracting cover: 7z lib not loaded"; - crash = true; - return; - } - if (!archive.isValid()) { - QLOG_WARN() << "Extracting cover: file format not supported " << _fileSource; - } - //se filtran para obtener sólo los formatos soportados - QList order = archive.getFileNames(); - QList fileNames = FileComic::filter(order); - _numPages = fileNames.size(); - if (_numPages == 0) { - QLOG_WARN() << "Extracting cover: empty comic " << _fileSource; - _cover.load(":/images/notCover.png"); - if (_target != "") { - _cover.save(_target); - } - } else { - if (_coverPage > _numPages) { - _coverPage = 1; - } - std::sort(fileNames.begin(), fileNames.end(), naturalSortLessThanCI); - int index = order.indexOf(fileNames.at(_coverPage - 1)); - - if (_target == "") { - if (!_cover.loadFromData(archive.getRawDataAtIndex(index))) { - QLOG_WARN() << "Extracting cover: unable to load image from extracted cover " << _fileSource; - _cover.load(":/images/notCover.png"); - } - } else { - QImage p; - if (p.loadFromData(archive.getRawDataAtIndex(index))) { - _coverSize = QPair(p.width(), p.height()); - QImage scaled; - if (p.width() > p.height()) //landscape?? - { - scaled = p.scaledToWidth(640, Qt::SmoothTransformation); - } else { - scaled = p.scaledToWidth(480, Qt::SmoothTransformation); - } - scaled.save(_target, 0, 75); - } else { - QLOG_WARN() << "Extracting cover: unable to load image from extracted cover " << _fileSource; - //p.load(":/images/notCover.png"); - //p.save(_target); - } - } - } -} diff --git a/YACReaderLibrary/library_creator.h b/YACReaderLibrary/library_creator.h index ccb27f24..d2d7a435 100644 --- a/YACReaderLibrary/library_creator.h +++ b/YACReaderLibrary/library_creator.h @@ -67,30 +67,4 @@ signals: void updatedCurrentFolder(QModelIndex); }; -class ThumbnailCreator : public QObject -{ - Q_OBJECT - -public: - ThumbnailCreator(QString fileSource, QString target = "", int coverPage = 1); - -private: - QString _fileSource; - QString _target; - QString _currentName; - int _numPages; - QPair _coverSize; - QImage _cover; - int _coverPage; - static bool crash; - -public slots: - void create(); - int getNumPages() { return _numPages; } - QPixmap getCover() { return QPixmap::fromImage(_cover); } - QPair getOriginalCoverSize() { return _coverSize; } -signals: - void openingError(QProcess::ProcessError error); -}; - #endif diff --git a/YACReaderLibrary/library_window.cpp b/YACReaderLibrary/library_window.cpp index 9d9e8e3b..a9e651ef 100644 --- a/YACReaderLibrary/library_window.cpp +++ b/YACReaderLibrary/library_window.cpp @@ -33,6 +33,7 @@ #include "comic_db.h" #include "library_creator.h" #include "package_manager.h" +#include "xml_info_library_scanner.h" #include "comic_flow_widget.h" #include "create_library_dialog.h" #include "rename_library_dialog.h" @@ -167,6 +168,7 @@ void LibraryWindow::setupUI() libraryCreator = new LibraryCreator(); packageManager = new PackageManager(); + xmlInfoLibraryScanner = new XMLInfoLibraryScanner(); historyController = new YACReaderHistoryController(this); @@ -425,7 +427,8 @@ void LibraryWindow::setUpShortcutsManagement() << importLibraryAction << updateLibraryAction << renameLibraryAction - << removeLibraryAction); + << removeLibraryAction + << rescanLibraryForXMLInfoAction); allActions << tmpList; @@ -532,6 +535,11 @@ void LibraryWindow::createActions() removeLibraryAction->setShortcut(ShortcutsManager::getShortcutsManager().getShortcut(REMOVE_LIBRARY_ACTION_YL)); removeLibraryAction->setIcon(QIcon(":/images/menus_icons/removeLibraryIcon.png")); + rescanLibraryForXMLInfoAction = new QAction(tr("Rescan library for XML info"), this); + rescanLibraryForXMLInfoAction->setToolTip(tr("Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files.")); + rescanLibraryForXMLInfoAction->setData(RESCAN_LIBRARY_XML_INFO_ACTION_YL); + rescanLibraryForXMLInfoAction->setShortcut(ShortcutsManager::getShortcutsManager().getShortcut(RESCAN_LIBRARY_XML_INFO_ACTION_YL)); + openComicAction = new QAction(tr("Open current comic"), this); openComicAction->setToolTip(tr("Open current comic on YACReader")); openComicAction->setData(OPEN_COMIC_ACTION_YL); @@ -868,6 +876,7 @@ void LibraryWindow::disableLibrariesActions(bool disabled) exportComicsInfoAction->setDisabled(disabled); importComicsInfoAction->setDisabled(disabled); exportLibraryAction->setDisabled(disabled); + rescanLibraryForXMLInfoAction->setDisabled(disabled); //importLibraryAction->setDisabled(disabled); } @@ -877,6 +886,7 @@ void LibraryWindow::disableNoUpdatedLibrariesActions(bool disabled) exportComicsInfoAction->setDisabled(disabled); importComicsInfoAction->setDisabled(disabled); exportLibraryAction->setDisabled(disabled); + rescanLibraryForXMLInfoAction->setDisabled(disabled); } void LibraryWindow::disableFoldersActions(bool disabled) @@ -1001,6 +1011,9 @@ void LibraryWindow::createMenus() selectedLibrary->addAction(removeLibraryAction); YACReader::addSperator(selectedLibrary); + selectedLibrary->addAction(rescanLibraryForXMLInfoAction); + YACReader::addSperator(selectedLibrary); + selectedLibrary->addAction(exportComicsInfoAction); selectedLibrary->addAction(importComicsInfoAction); YACReader::addSperator(selectedLibrary); @@ -1022,6 +1035,9 @@ void LibraryWindow::createMenus() libraryMenu->addAction(removeLibraryAction); libraryMenu->addSeparator(); + libraryMenu->addAction(rescanLibraryForXMLInfoAction); + libraryMenu->addSeparator(); + libraryMenu->addAction(exportComicsInfoAction); libraryMenu->addAction(importComicsInfoAction); @@ -1083,8 +1099,13 @@ void LibraryWindow::createConnections() connect(libraryCreator, &LibraryCreator::failedCreatingDB, this, &LibraryWindow::manageCreatingError); connect(libraryCreator, SIGNAL(failedUpdatingDB(QString)), this, SLOT(manageUpdatingError(QString))); //TODO: implement failedUpdatingDB + connect(xmlInfoLibraryScanner, &QThread::finished, this, &LibraryWindow::showRootWidget); + connect(xmlInfoLibraryScanner, &QThread::finished, this, &LibraryWindow::reloadCurrentFolderComicsContent); + connect(xmlInfoLibraryScanner, &XMLInfoLibraryScanner::comicScanned, importWidget, &ImportWidget::newComic); + //new import widget connect(importWidget, &ImportWidget::stop, this, &LibraryWindow::stopLibraryCreator); + connect(importWidget, &ImportWidget::stop, this, &LibraryWindow::stopXMLScanning); //packageManager connections connect(exportLibraryDialog, &ExportLibraryDialog::exportPath, this, &LibraryWindow::exportLibrary); @@ -1147,6 +1168,7 @@ void LibraryWindow::createConnections() connect(renameLibraryAction, &QAction::triggered, this, &LibraryWindow::renameLibrary); //connect(deleteLibraryAction,SIGNAL(triggered()),this,SLOT(deleteLibrary())); connect(removeLibraryAction, &QAction::triggered, this, &LibraryWindow::removeLibrary); + connect(rescanLibraryForXMLInfoAction, &QAction::triggered, this, &LibraryWindow::rescanLibraryForXMLInfo); connect(openComicAction, &QAction::triggered, this, QOverload<>::of(&LibraryWindow::openComic)); connect(helpAboutAction, &QAction::triggered, had, &QWidget::show); connect(addFolderAction, &QAction::triggered, this, &LibraryWindow::addFolderToCurrentIndex); @@ -1298,12 +1320,13 @@ void LibraryWindow::loadLibrary(const QString &name) d.setCurrent(libraries.getPath(name)); d.setFilter(QDir::AllDirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks | QDir::NoDotAndDotDot); - if (d.count() <= 1) //librería de sólo lectura + if (d.count() <= 1) //read only library { - //QMessageBox::critical(NULL,QString::number(d.count()),QString::number(d.count())); disableLibrariesActions(false); updateLibraryAction->setDisabled(true); openContainingFolderAction->setDisabled(true); + rescanLibraryForXMLInfoAction->setDisabled(true); + disableComicsActions(true); #ifndef Q_OS_MAC toggleFullScreenAction->setEnabled(true); @@ -1514,6 +1537,13 @@ QProgressDialog *LibraryWindow::newProgressDialog(const QString &label, int maxV return progressDialog; } +void LibraryWindow::reloadCurrentFolderComicsContent() +{ + navigationController->loadFolderInfo(getCurrentFolderIndex()); + + enableNeededActions(); +} + void LibraryWindow::reloadAfterCopyMove(const QModelIndex &mi) { if (getCurrentFolderIndex() == mi) { @@ -2076,6 +2106,18 @@ void LibraryWindow::rename(QString newName) //TODO replace //selectedLibrary->setCurrentIndex(selectedLibrary->findText(newName)); } +void LibraryWindow::rescanLibraryForXMLInfo() +{ + importWidget->setXMLScanLook(); + showImportingWidget(); + + QString currentLibrary = selectedLibrary->currentText(); + QString path = libraries.getPath(currentLibrary); + _lastAdded = currentLibrary; + + xmlInfoLibraryScanner->scanLibrary(path, path + "/.yacreaderlibrary"); +} + void LibraryWindow::cancelCreating() { stopLibraryCreator(); @@ -2087,6 +2129,12 @@ void LibraryWindow::stopLibraryCreator() libraryCreator->wait(); } +void LibraryWindow::stopXMLScanning() +{ + xmlInfoLibraryScanner->stop(); + xmlInfoLibraryScanner->wait(); +} + void LibraryWindow::setRootIndex() { if (!libraries.isEmpty()) { diff --git a/YACReaderLibrary/library_window.h b/YACReaderLibrary/library_window.h index 2db2c7c4..e06f53b0 100644 --- a/YACReaderLibrary/library_window.h +++ b/YACReaderLibrary/library_window.h @@ -82,6 +82,7 @@ class EmptyReadingListWidget; namespace YACReader { class TrayIconController; +class XMLInfoLibraryScanner; } #include "comic_db.h" @@ -103,6 +104,7 @@ public: ImportComicsInfoDialog *importComicsInfoDialog; AddLibraryDialog *addLibraryDialog; LibraryCreator *libraryCreator; + XMLInfoLibraryScanner *xmlInfoLibraryScanner; HelpAboutDialog *had; RenameLibraryDialog *renameLibraryDialog; PropertiesDialog *propertiesDialog; @@ -163,6 +165,8 @@ public: QAction *exportLibraryAction; QAction *importLibraryAction; + QAction *rescanLibraryForXMLInfoAction; + QAction *updateLibraryAction; QAction *removeLibraryAction; QAction *helpAboutAction; @@ -338,9 +342,11 @@ public slots: void deleteCurrentLibrary(); void removeLibrary(); void renameLibrary(); + void rescanLibraryForXMLInfo(); void rename(QString newName); void cancelCreating(); void stopLibraryCreator(); + void stopXMLScanning(); void setRootIndex(); void toggleFullScreen(); void toNormal(); @@ -391,6 +397,7 @@ public slots: void updateCurrentFolder(); void updateFolder(const QModelIndex &miFolder); QProgressDialog *newProgressDialog(const QString &label, int maxValue); + void reloadCurrentFolderComicsContent(); void reloadAfterCopyMove(const QModelIndex &mi); QModelIndex getCurrentFolderIndex(); void enableNeededActions(); diff --git a/YACReaderLibrary/properties_dialog.cpp b/YACReaderLibrary/properties_dialog.cpp index 33db4506..11cb6ba3 100644 --- a/YACReaderLibrary/properties_dialog.cpp +++ b/YACReaderLibrary/properties_dialog.cpp @@ -1,7 +1,7 @@ #include "properties_dialog.h" #include "data_base_management.h" -#include "library_creator.h" +#include "initial_comic_info_extractor.h" #include "yacreader_field_edit.h" #include "yacreader_field_plain_text_edit.h" #include "db_helper.h" @@ -22,6 +22,8 @@ #include #include +using namespace YACReader; + PropertiesDialog::PropertiesDialog(QWidget *parent) : QDialog(parent) { @@ -734,12 +736,12 @@ void PropertiesDialog::save() if (comics.count() == 1) { if (coverChanged) // && coverPageEdit->text().toInt() != *comics[0].info.coverPage) { - ThumbnailCreator tc(basePath + comics[0].path, basePath + "/.yacreaderlibrary/covers/" + comics[0].info.hash + ".jpg", comics[0].info.coverPage.toInt()); - tc.create(); + InitialComicInfoExtractor ie(basePath + comics[0].path, basePath + "/.yacreaderlibrary/covers/" + comics[0].info.hash + ".jpg", comics[0].info.coverPage.toInt()); + ie.extract(); - if (tc.getOriginalCoverSize().second > 0) { - comics[0].info.originalCoverSize = QString("%1x%2").arg(tc.getOriginalCoverSize().first).arg(tc.getOriginalCoverSize().second); - comics[0].info.coverSizeRatio = static_cast(tc.getOriginalCoverSize().first) / tc.getOriginalCoverSize().second; + if (ie.getOriginalCoverSize().second > 0) { + comics[0].info.originalCoverSize = QString("%1x%2").arg(ie.getOriginalCoverSize().first).arg(ie.getOriginalCoverSize().second); + comics[0].info.coverSizeRatio = static_cast(ie.getOriginalCoverSize().first) / ie.getOriginalCoverSize().second; } } } @@ -831,9 +833,9 @@ void PropertiesDialog::loadNextCover() if (current < comics.at(0).info.numPages.toInt()) { updateCoverPageNumberLabel(current + 1); - ThumbnailCreator tc(basePath + comics[0].path, "", current + 1); - tc.create(); - setCover(tc.getCover()); + InitialComicInfoExtractor ie(basePath + comics[0].path, "", current + 1); + ie.extract(); + setCover(ie.getCover()); repaint(); if ((current + 1) == comics.at(0).info.numPages.toInt()) { @@ -854,9 +856,9 @@ void PropertiesDialog::loadPreviousCover() int current = coverPageNumberLabel->text().toInt(); if (current != 1) { updateCoverPageNumberLabel(current - 1); - ThumbnailCreator tc(basePath + comics[0].path, "", current - 1); - tc.create(); - setCover(tc.getCover()); + InitialComicInfoExtractor ie(basePath + comics[0].path, "", current - 1); + ie.extract(); + setCover(ie.getCover()); repaint(); if ((current - 1) == 1) { diff --git a/YACReaderLibrary/xml_info_library_scanner.cpp b/YACReaderLibrary/xml_info_library_scanner.cpp new file mode 100644 index 00000000..a5c13d63 --- /dev/null +++ b/YACReaderLibrary/xml_info_library_scanner.cpp @@ -0,0 +1,98 @@ +#include "xml_info_library_scanner.h" + +#include "comic_db.h" +#include "data_base_management.h" +#include "db_helper.h" +#include "initial_comic_info_extractor.h" +#include "xml_info_parser.h" +#include "yacreader_global.h" + +#include "QsLog.h" + +using namespace YACReader; + +XMLInfoLibraryScanner::XMLInfoLibraryScanner() + : QThread() +{ +} + +void XMLInfoLibraryScanner::scanLibrary(const QString &source, const QString &target) +{ + this->source = source; + this->target = target; + + this->stopRunning = false; + + start(); +} + +void XMLInfoLibraryScanner::run() +{ +#ifndef use_unarr +//check for 7z lib +#if defined Q_OS_UNIX && !defined Q_OS_MAC + QLibrary *sevenzLib = new QLibrary(QString(LIBDIR) + "/p7zip/7z.so"); +#else + QLibrary *sevenzLib = new QLibrary(QCoreApplication::applicationDirPath() + "/utils/7z"); +#endif + + if (!sevenzLib->load()) { + QLOG_ERROR() << "Loading 7z.dll : " + sevenzLib->errorString() << endl; + QCoreApplication::exit(YACReader::SevenZNotFound); + exit(); + } + sevenzLib->deleteLater(); +#endif + + QString databaseConnection; + + { + auto database = DataBaseManagement::loadDatabase(this->target); + databaseConnection = database.connectionName(); + + database.transaction(); + + QSqlQuery comicsInfo("SELECT * FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id)", database); + comicsInfo.exec(); + + QSqlRecord record = comicsInfo.record(); + + int id = record.indexOf("id"); + //int parentIdIndex = record.indexOf("parentId"); + int fileNameIndex = record.indexOf("fileName"); + int pathIndex = record.indexOf("path"); + + while (comicsInfo.next()) { + if (this->stopRunning) { + break; + } + /* currentItem.id = selectQuery.value(id).toULongLong(); + currentItem.parentId = parentId; //selectQuery.value(parentId).toULongLong(); + currentItem.name = selectQuery.value(fileName).toString(); */ + auto fileName = comicsInfo.value(fileNameIndex).toString(); + auto path = comicsInfo.value(pathIndex).toString(); + + emit comicScanned(path, fileName); + + auto info = DBHelper::getComicInfoFromQuery(comicsInfo, "comicInfoId"); + + InitialComicInfoExtractor ie(QDir::cleanPath(this->source + path), "None"); + + ie.extract(); + + if (parseXMLIntoInfo(ie.getXMLInfoRawData(), info)) { + DBHelper::update(&info, database); + } + } + + database.commit(); + database.close(); + } + + QSqlDatabase::removeDatabase(databaseConnection); +} + +void XMLInfoLibraryScanner::stop() +{ + stopRunning = true; +} diff --git a/YACReaderLibrary/xml_info_library_scanner.h b/YACReaderLibrary/xml_info_library_scanner.h new file mode 100644 index 00000000..d8607b76 --- /dev/null +++ b/YACReaderLibrary/xml_info_library_scanner.h @@ -0,0 +1,32 @@ +#ifndef XMLINFOLIBRARYSCANNER_H +#define XMLINFOLIBRARYSCANNER_H + +#include + +namespace YACReader { + +class XMLInfoLibraryScanner : public QThread +{ + Q_OBJECT +public: + XMLInfoLibraryScanner(); + void scanLibrary(const QString &source, const QString &target); + +protected: + void run() override; + +public slots: + void stop(); + +signals: + void comicScanned(QString, QString); + +private: + QString source; + QString target; + bool stopRunning; +}; + +} + +#endif // XMLINFOLIBRARYSCANNER_H diff --git a/YACReaderLibrary/xml_info_parser.cpp b/YACReaderLibrary/xml_info_parser.cpp new file mode 100644 index 00000000..aa51ad73 --- /dev/null +++ b/YACReaderLibrary/xml_info_parser.cpp @@ -0,0 +1,184 @@ +#include "xml_info_parser.h" + +#include + +bool isValidText(const QString &string) +{ + return !string.isEmpty() && !string.isNull(); +} + +QString transLateMultiValuedString(QString &string, const QString &originSeparator = ", ", const QString &targetSeparator = "\n") +{ + return string.replace(originSeparator, targetSeparator); +} + +bool parseField(QXmlStreamReader &reader, const QString &xmlName, QVariant &dest, bool multivalued = false) +{ + auto name = reader.name(); + if (name == xmlName) { + auto string = reader.readElementText(); + if (isValidText(string)) { + if (multivalued) { + dest = transLateMultiValuedString(string); + } else { + dest = string; + } + } + + return true; + } + + return false; +} + +bool parseFieldNumber(QXmlStreamReader &reader, const QString &xmlName, QVariant &dest) +{ + if (reader.name() == xmlName) { + auto string = reader.readElementText(); + if (isValidText(string)) { + bool success; + auto number = string.toInt(&success); + if (success) { + dest = number; + } + } + + return true; + } + + return false; +} + +void consolidateDate(ComicInfo &info) +{ + if (!info.year.isValid() && !info.month.isValid() && !info.day.isValid()) { + return; + } + + auto year = info.year.isNull() ? 0 : info.year.toInt(); + auto month = info.month.isNull() ? 1 : info.month.toInt(); + auto day = info.date.isNull() ? 1 : info.date.toInt(); + + info.date = QString("%1/%2/%3").arg(day).arg(month).arg(year); +} + +bool tryValues(QXmlStreamReader &reader, ComicInfo &info) +{ + std::map stringValues = { + { "Title", info.title }, + { "Volume", info.volume }, + { "StoryArc", info.storyArc }, + { "Genre", info.genere }, + { "Publisher", info.publisher }, + { "Format", info.format }, + { "AgeRating", info.ageRating }, + { "Summary", info.synopsis }, + { "Notes", info.notes }, + }; + + std::map forcedNumbers = { + { "Number", info.number }, + { "Count", info.count }, + { "AlternateNumber", info.arcNumber }, + { "AlternateCount", info.arcCount }, + { "Day", info.day }, + { "Month", info.month }, + { "Year", info.year } + }; + + std::map multiValuedStrings = { + { "Writer", info.writer }, + { "Penciller", info.penciller }, + { "Inker", info.inker }, + { "Colorist", info.colorist }, + { "Letterer", info.letterer }, + { "CoverArtist", info.coverArtist }, + { "Characters", info.characters } + }; + + for (auto &pair : stringValues) { + if (parseField(reader, pair.first, pair.second)) { + return true; + } + } + + for (auto &pair : forcedNumbers) { + if (parseFieldNumber(reader, pair.first, pair.second)) { + return true; + } + } + + for (auto &pair : multiValuedStrings) { + if (parseField(reader, pair.first, pair.second, true)) { + return true; + } + } + + if (reader.name() == "BlackAndWhite") { + auto string = reader.readElementText(); + if (isValidText(string)) { + if (string == "Yes") { + info.color = false; + } else if (string == "No") { + info.color = true; + } + } + + return true; + } + + if (reader.name() == "Manga") { + auto string = reader.readElementText(); + if (isValidText(string)) { + if (string == "Yes" || string == "YesAndRightToLeft") { + info.manga = true; + } else if (string == "No") { + info.manga = false; + } + } + + return true; + } + + if (reader.name() == "Web") { + auto string = reader.readElementText(); + if (isValidText(string)) { + auto comicVineId = string.split("-").last().replace("/", ""); + + info.comicVineID = comicVineId; + } + + return true; + } + + return false; +} + +bool YACReader::parseXMLIntoInfo(const QByteArray &xmlRawData, ComicInfo &info) +{ + if (xmlRawData.isEmpty()) { + return false; + } + + QXmlStreamReader reader(xmlRawData); + + bool someDataWasParsed = false; + + while (reader.readNextStartElement()) { + if (tryValues(reader, info)) { + someDataWasParsed = true; + } else { + if (reader.name() != "ComicInfo") { + reader.skipCurrentElement(); + } + } + } + + consolidateDate(info); + + if (reader.error()) { + return false; + } + + return someDataWasParsed; +} diff --git a/YACReaderLibrary/xml_info_parser.h b/YACReaderLibrary/xml_info_parser.h new file mode 100644 index 00000000..295aba75 --- /dev/null +++ b/YACReaderLibrary/xml_info_parser.h @@ -0,0 +1,12 @@ +#ifndef XMLINFOPARSER_H +#define XMLINFOPARSER_H + +#include "comic_db.h" + +namespace YACReader { + +bool parseXMLIntoInfo(const QByteArray &xmlRawData, ComicInfo &info); + +} + +#endif // XMLINFOPARSER_H diff --git a/YACReaderLibraryServer/YACReaderLibraryServer.pro b/YACReaderLibraryServer/YACReaderLibraryServer.pro index 78342492..e84ab29a 100644 --- a/YACReaderLibraryServer/YACReaderLibraryServer.pro +++ b/YACReaderLibraryServer/YACReaderLibraryServer.pro @@ -44,6 +44,8 @@ HEADERS += ../YACReaderLibrary/library_creator.h \ ../YACReaderLibrary/db_helper.h \ ../YACReaderLibrary/db/data_base_management.h \ ../YACReaderLibrary/db/reading_list.h \ + ../YACReaderLibrary/initial_comic_info_extractor.h \ + ../YACReaderLibrary/xml_info_parser.h \ ../common/comic_db.h \ ../common/folder.h \ ../common/library_item.h \ @@ -67,6 +69,8 @@ SOURCES += ../YACReaderLibrary/library_creator.cpp \ ../YACReaderLibrary/db_helper.cpp \ ../YACReaderLibrary/db/data_base_management.cpp \ ../YACReaderLibrary/db/reading_list.cpp \ + ../YACReaderLibrary/initial_comic_info_extractor.cpp \ + ../YACReaderLibrary/xml_info_parser.cpp \ ../common/comic_db.cpp \ ../common/folder.cpp \ ../common/library_item.cpp \ diff --git a/common/comic_db.cpp b/common/comic_db.cpp index d508783d..633ed2f3 100644 --- a/common/comic_db.cpp +++ b/common/comic_db.cpp @@ -173,7 +173,9 @@ QString ComicDB::getTitleIncludingNumber() const //COMIC_INFO------------------------------------------------------------------- //----------------------------------------------------------------------------- ComicInfo::ComicInfo() - : existOnDb(false), + : read(false), + edited(false), + existOnDb(false), rating(0), hasBeenOpened(false), currentPage(1), diff --git a/common/comic_db.h b/common/comic_db.h index 3f8a33ce..f3ecd717 100644 --- a/common/comic_db.h +++ b/common/comic_db.h @@ -69,6 +69,11 @@ public: QVariant coverArtist; //string QVariant date; //string + + QVariant day; + QVariant month; + QVariant year; + QVariant publisher; //string QVariant format; //string QVariant color; //bool @@ -177,6 +182,9 @@ public: Q_PROPERTY(QVariant coverArtist MEMBER coverArtist CONSTANT) Q_PROPERTY(QVariant date MEMBER date CONSTANT) + Q_PROPERTY(QVariant day MEMBER day CONSTANT) + Q_PROPERTY(QVariant month MEMBER month CONSTANT) + Q_PROPERTY(QVariant year MEMBER year CONSTANT) Q_PROPERTY(QVariant publisher MEMBER publisher CONSTANT) Q_PROPERTY(QVariant format MEMBER format CONSTANT) Q_PROPERTY(QVariant color MEMBER color CONSTANT) diff --git a/shortcuts_management/shortcuts_manager.h b/shortcuts_management/shortcuts_manager.h index 166830ad..8452eb09 100644 --- a/shortcuts_management/shortcuts_manager.h +++ b/shortcuts_management/shortcuts_manager.h @@ -45,6 +45,7 @@ public: #define UPDATE_LIBRARY_ACTION_YL "UPDATE_LIBRARY_ACTION_YL" #define RENAME_LIBRARY_ACTION_YL "RENAME_LIBRARY_ACTION_YL" #define REMOVE_LIBRARY_ACTION_YL "REMOVE_LIBRARY_ACTION_YL" +#define RESCAN_LIBRARY_XML_INFO_ACTION_YL "RESCAN_LIBRARY_XML_INFO_ACTION_YL" #define OPEN_COMIC_ACTION_YL "OPEN_COMIC_ACTION_YL" #define SET_AS_READ_ACTION_YL "SET_AS_READ_ACTION_YL" #define SET_AS_NON_READ_ACTION_YL "SET_AS_NON_READ_ACTION_YL"