diff --git a/CHANGELOG.md b/CHANGELOG.md index a67a2cc1..472ea6d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Version counting is based on semantic versioning (Major.Feature.Patch) * New setting in Comic Vine scraper to force exact volume matches. * Better default search query in the Comic Vine scraper. * Improved navigation in Comic Vine scraper, including keeping the current query around to make edits and refined searches easier. +* Add support for adding custom covers for folders using the context menu. ### YACReaderLibraryServer * Log libraries validation when the app starts. diff --git a/YACReaderLibrary/YACReaderLibrary.pro b/YACReaderLibrary/YACReaderLibrary.pro index d4ebf6d8..2330a782 100644 --- a/YACReaderLibrary/YACReaderLibrary.pro +++ b/YACReaderLibrary/YACReaderLibrary.pro @@ -74,6 +74,7 @@ greaterThan(QT_MAJOR_VERSION, 5): QT += openglwidgets core5compat # Input HEADERS += comic_flow.h \ ../common/concurrent_queue.h \ + ../common/cover_utils.h \ create_library_dialog.h \ db/comic_query_result_processor.h \ db/folder_query_result_processor.h \ @@ -163,6 +164,7 @@ HEADERS += comic_flow.h \ SOURCES += comic_flow.cpp \ ../common/concurrent_queue.cpp \ + ../common/cover_utils.cpp \ create_library_dialog.cpp \ db/comic_query_result_processor.cpp \ db/folder_query_result_processor.cpp \ diff --git a/YACReaderLibrary/db/folder_model.cpp b/YACReaderLibrary/db/folder_model.cpp index 8884af18..ae24d0f9 100644 --- a/YACReaderLibrary/db/folder_model.cpp +++ b/YACReaderLibrary/db/folder_model.cpp @@ -370,8 +370,12 @@ QVariant FolderModel::data(const QModelIndex &index, int role) const if (role == FolderModel::IdRole) return item->id; - if (role == FolderModel::CoverPathRole) - return getCoverUrlPathForComicHash(item->data(FirstChildHash).toString()); + if (role == FolderModel::CoverPathRole) { + if (item->data(FolderModel::CustomImage).toString().isEmpty()) + return getCoverUrlPathForComicHash(item->data(FirstChildHash).toString()); + else + return getCoverUrlPathForFolderId(item->id); + } if (role == FolderModel::NumChildrenRole) return item->data(NumChildren); @@ -675,6 +679,50 @@ void FolderModel::updateTreeType(YACReader::FileType type) QSqlDatabase::removeDatabase(connectionName); } +void FolderModel::setCustomFolderCover(const QModelIndex &index, const QString &path) +{ + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); + db.transaction(); + + auto item = static_cast(index.internalPointer()); + item->setData(FolderModel::CustomImage, path); + + Folder f = DBHelper::loadFolder(item->id, db); + f.customImage = path; + DBHelper::update(f, db); + + db.commit(); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + + emit dataChanged(index, index); +} + +void FolderModel::resetFolderCover(const QModelIndex &index) +{ + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); + db.transaction(); + + auto item = static_cast(index.internalPointer()); + item->setData(FolderModel::CustomImage, ""); + + Folder f = DBHelper::loadFolder(item->id, db); + f.customImage = ""; + DBHelper::update(f, db); + + db.commit(); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + + emit dataChanged(index, index); +} + QStringList FolderModel::getSubfoldersNames(const QModelIndex &mi) { QStringList result; @@ -858,6 +906,12 @@ QUrl FolderModel::getCoverUrlPathForComicHash(const QString &hash) const return QUrl::fromLocalFile(coverPath); } +QUrl FolderModel::getCoverUrlPathForFolderId(qulonglong folderId) const +{ + auto coverPath = LibraryPaths::customFolderCoverPathFromDataPath(_databasePath, QString::number(folderId)); + return QUrl::fromLocalFile(coverPath); +} + void FolderModel::setShowRecent(bool showRecent) { if (this->showRecent == showRecent) diff --git a/YACReaderLibrary/db/folder_model.h b/YACReaderLibrary/db/folder_model.h index a0f9801b..2de737f1 100644 --- a/YACReaderLibrary/db/folder_model.h +++ b/YACReaderLibrary/db/folder_model.h @@ -70,6 +70,8 @@ public: void updateFolderFinishedStatus(const QModelIndexList &list, bool status); void updateFolderType(const QModelIndexList &list, YACReader::FileType type); void updateTreeType(YACReader::FileType type); + void setCustomFolderCover(const QModelIndex &index, const QString &path); + void resetFolderCover(const QModelIndex &index); QStringList getSubfoldersNames(const QModelIndex &mi); FolderModel *getSubfoldersModel(const QModelIndex &mi); // it creates a model that contains just the direct subfolders @@ -81,6 +83,7 @@ public: QModelIndex addFolderAtParent(const QString &folderName, const QModelIndex &parent); Q_INVOKABLE QUrl getCoverUrlPathForComicHash(const QString &hash) const; + Q_INVOKABLE QUrl getCoverUrlPathForFolderId(qulonglong folderId) const; void setShowRecent(bool showRecent); void setRecentRange(int days); diff --git a/YACReaderLibrary/db_helper.cpp b/YACReaderLibrary/db_helper.cpp index 54f2fe8a..e0e2f5c4 100644 --- a/YACReaderLibrary/db_helper.cpp +++ b/YACReaderLibrary/db_helper.cpp @@ -824,13 +824,34 @@ void DBHelper::updateAdded(ComicInfo *comicInfo, QSqlDatabase &db) void DBHelper::update(const Folder &folder, QSqlDatabase &db) { QSqlQuery updateFolderInfo(db); + updateFolderInfo.prepare("UPDATE folder SET " + "parentId = :parentId, " + "name = :name, " + "path = :path, " "finished = :finished, " - "completed = :completed " - "WHERE id = :id "); + "completed = :completed, " + "numChildren = :numChildren, " + "firstChildHash = :firstChildHash, " + "customImage = :customImage, " + "type = :type, " + "added = :added, " + "updated = :updated " + "WHERE id = :id"); + + updateFolderInfo.bindValue(":parentId", folder.parentId); + updateFolderInfo.bindValue(":name", folder.name); + updateFolderInfo.bindValue(":path", folder.path); updateFolderInfo.bindValue(":finished", folder.finished ? 1 : 0); updateFolderInfo.bindValue(":completed", folder.completed ? 1 : 0); + updateFolderInfo.bindValue(":numChildren", folder.numChildren); + updateFolderInfo.bindValue(":firstChildHash", folder.firstChildHash); + updateFolderInfo.bindValue(":customImage", folder.customImage); + updateFolderInfo.bindValue(":type", static_cast(folder.type)); + updateFolderInfo.bindValue(":added", folder.added); + updateFolderInfo.bindValue(":updated", folder.updated); updateFolderInfo.bindValue(":id", folder.id); + updateFolderInfo.exec(); } diff --git a/YACReaderLibrary/db_helper.h b/YACReaderLibrary/db_helper.h index d298bc42..66d3981d 100644 --- a/YACReaderLibrary/db_helper.h +++ b/YACReaderLibrary/db_helper.h @@ -67,7 +67,7 @@ public: static void update(ComicInfo *comicInfo, QSqlDatabase &db); static void updateRead(ComicInfo *comicInfo, QSqlDatabase &db); static void updateAdded(ComicInfo *comicInfo, QSqlDatabase &db); - static void update(const Folder &folder, QSqlDatabase &db); // only for finished/completed fields + static void update(const Folder &folder, QSqlDatabase &db); static void propagateFolderUpdatesToParent(const Folder &folder, QSqlDatabase &db); static Folder updateChildrenInfo(qulonglong folderId, QSqlDatabase &db); static void updateChildrenInfo(QSqlDatabase &db); diff --git a/YACReaderLibrary/initial_comic_info_extractor.cpp b/YACReaderLibrary/initial_comic_info_extractor.cpp index eb543397..15528ede 100644 --- a/YACReaderLibrary/initial_comic_info_extractor.cpp +++ b/YACReaderLibrary/initial_comic_info_extractor.cpp @@ -5,6 +5,7 @@ #include "comic.h" #include "compressed_archive.h" #include "qnaturalsorting.h" +#include "cover_utils.h" using namespace YACReader; @@ -154,17 +155,5 @@ QByteArray InitialComicInfoExtractor::getXMLInfoRawData() void InitialComicInfoExtractor::saveCover(const QString &path, const QImage &cover) { - QImage scaled; - if (cover.width() > cover.height()) { - scaled = cover.scaledToWidth(640, Qt::SmoothTransformation); - } else { - auto aspectRatio = static_cast(cover.width()) / static_cast(cover.height()); - auto maxAllowedAspectRatio = 0.5; - if (aspectRatio < maxAllowedAspectRatio) { // cover is too tall, e.g. webtoon - scaled = cover.scaledToHeight(960, Qt::SmoothTransformation); - } else { - scaled = cover.scaledToWidth(480, Qt::SmoothTransformation); - } - } - scaled.save(_target, 0, 75); + YACReader::saveCover(path, cover); } diff --git a/YACReaderLibrary/library_window.cpp b/YACReaderLibrary/library_window.cpp index c360faca..48ef18ef 100644 --- a/YACReaderLibrary/library_window.cpp +++ b/YACReaderLibrary/library_window.cpp @@ -83,6 +83,8 @@ #include "recent_visibility_coordinator.h" +#include "cover_utils.h" + #include "QsLog.h" #include "yacreader_http_server.h" @@ -536,6 +538,10 @@ void LibraryWindow::createMenus() foldersView->addAction(actions.setFolderAsWesternMangaAction); foldersView->addAction(actions.setFolderAsWebComicAction); foldersView->addAction(actions.setFolderAsYonkomaAction); + YACReader::addSperator(foldersView); + + foldersView->addAction(actions.setFolderCoverAction); + foldersView->addAction(actions.deleteCustomFolderCoverAction); selectedLibrary->addAction(actions.updateLibraryAction); selectedLibrary->addAction(actions.renameLibraryAction); @@ -669,11 +675,14 @@ void LibraryWindow::createMenus() folderMenu->addAction(actions.setFolderAsReadAction); folderMenu->addAction(actions.setFolderAsUnreadAction); folderMenu->addSeparator(); - foldersView->addAction(actions.setFolderAsNormalAction); - foldersView->addAction(actions.setFolderAsMangaAction); - foldersView->addAction(actions.setFolderAsWesternMangaAction); - foldersView->addAction(actions.setFolderAsWebComicAction); - foldersView->addAction(actions.setFolderAsYonkomaAction); + folderMenu->addAction(actions.setFolderAsNormalAction); + folderMenu->addAction(actions.setFolderAsMangaAction); + folderMenu->addAction(actions.setFolderAsWesternMangaAction); + folderMenu->addAction(actions.setFolderAsWebComicAction); + folderMenu->addAction(actions.setFolderAsYonkomaAction); + folderMenu->addSeparator(); + folderMenu->addAction(actions.setFolderCoverAction); + folderMenu->addAction(actions.deleteCustomFolderCoverAction); // comic QMenu *comicMenu = new QMenu(tr("Comic")); @@ -823,11 +832,15 @@ void LibraryWindow::loadLibrary(const QString &name) showRootWidget(); QString rootPath = libraries.getPath(name); QString path = LibraryPaths::libraryDataPath(rootPath); + QString customFolderCoversPath = LibraryPaths::libraryCustomFoldersCoverPath(rootPath); QString databasePath = LibraryPaths::libraryDatabasePath(rootPath); QDir d; // TODO change this by static methods (utils class?? with delTree for example) QString dbVersion; if (d.exists(path) && d.exists(databasePath) && (dbVersion = DataBaseManagement::checkValidDB(databasePath)) != "") // si existe en disco la biblioteca seleccionada, y es vĂ¡lida.. { + // this folde was added in 9.16, it needs to exist before the user starts importing custom covers for folders + d.mkdir(customFolderCoversPath); + int comparation = DataBaseManagement::compareVersions(dbVersion, DB_VERSION); if (comparation < 0) { @@ -1441,6 +1454,12 @@ void LibraryWindow::showGridFoldersContextMenu(QPoint point, Folder folder) auto setFolderAs4KomaAction = new QAction(); setFolderAs4KomaAction->setText(tr("4koma (top to botom)")); + auto setFolderCoverAction = new QAction(); + setFolderCoverAction->setText(tr("Set custom cover")); + + auto deleteCustomFolderCoverAction = new QAction(); + deleteCustomFolderCoverAction->setText(tr("Delete custom cover")); + menu.addAction(openContainingFolderAction); menu.addAction(updateFolderAction); menu.addSeparator(); @@ -1536,6 +1555,20 @@ void LibraryWindow::showGridFoldersContextMenu(QPoint point, Folder folder) foldersModel->updateFolderType(QModelIndexList() << foldersModel->getIndexFromFolder(folder), FileType::Yonkoma); subfolderModel->updateFolderType(QModelIndexList() << foldersModel->getIndexFromFolder(folder), FileType::Yonkoma); }); + connect(setFolderCoverAction, &QAction::triggered, this, [=]() { + setCustomFolderCover(folder); + }); + + connect(deleteCustomFolderCoverAction, &QAction::triggered, this, [=]() { + resetFolderCover(folder); + }); + + menu.addSeparator(); + + menu.addAction(setFolderCoverAction); + if (!folder.customImage.isEmpty()) { + menu.addAction(deleteCustomFolderCoverAction); + } menu.exec(contentViewsManager->folderContentView->mapToGlobal(point)); } @@ -2272,6 +2305,55 @@ void LibraryWindow::setFolderType(FileType type) foldersModel->updateFolderType(QModelIndexList() << foldersModelProxy->mapToSource(foldersView->currentIndex()), type); } +void LibraryWindow::setFolderCover() +{ + auto folder = foldersModel->getFolder(foldersModelProxy->mapToSource(foldersView->currentIndex())); + setCustomFolderCover(folder); +} + +void LibraryWindow::setCustomFolderCover(Folder folder) +{ + QString supportedImageFormatsString; + for (const QByteArray &format : QImageReader::supportedImageFormats()) { + supportedImageFormatsString += QString("*.%1 ").arg(QString(format)); + } + + QString customCoverPath = QFileDialog::getOpenFileName(this, tr("Select custom cover"), QDir::homePath(), tr("Images (%1)").arg(supportedImageFormatsString)); + if (!customCoverPath.isEmpty()) { + QImage cover(customCoverPath); + if (cover.isNull()) { + QMessageBox::warning(this, tr("Invalid image"), tr("The selected file is not a valid image.")); + return; + } + + auto folderCoverPath = LibraryPaths::customFolderCoverPath(libraries.getPath(selectedLibrary->currentText()), QString::number(folder.id)); + if (!YACReader::saveCover(folderCoverPath, cover)) { + QMessageBox::warning(this, tr("Error saving cover"), tr("There was an error saving the cover image.")); + } + + QModelIndex folderIndex = foldersModel->getIndexFromFolder(folder); + auto coversPath = LibraryPaths::libraryCoversFolderPath(libraries.getPath(selectedLibrary->currentText())); + auto relativePath = folderCoverPath.remove(coversPath); + foldersModel->setCustomFolderCover(folderIndex, relativePath); + } +} + +void LibraryWindow::deleteCustomFolderCover() +{ + auto folder = foldersModel->getFolder(foldersModelProxy->mapToSource(foldersView->currentIndex())); + resetFolderCover(folder); +} + +void LibraryWindow::resetFolderCover(Folder folder) +{ + auto folderCoverPath = LibraryPaths::customFolderCoverPath(libraries.getPath(selectedLibrary->currentText()), QString::number(folder.id)); + if (QFile::exists(folderCoverPath)) { + QFile::remove(folderCoverPath); + } + QModelIndex folderIndex = foldersModel->getIndexFromFolder(folder); + foldersModel->resetFolderCover(folderIndex); +} + void LibraryWindow::exportLibrary(QString destPath) { QString currentLibrary = selectedLibrary->currentText(); @@ -2513,9 +2595,7 @@ void LibraryWindow::showFoldersContextMenu(const QPoint &point) { QModelIndex sourceMI = foldersModelProxy->mapToSource(foldersView->indexAt(point)); - bool isCompleted = sourceMI.data(FolderModel::CompletedRole).toBool(); - bool isRead = sourceMI.data(FolderModel::FinishedRole).toBool(); - auto type = sourceMI.data(FolderModel::TypeRole).value(); + auto folder = foldersModel->getFolder(sourceMI); actions.setFolderAsNormalAction->setCheckable(true); actions.setFolderAsMangaAction->setCheckable(true); @@ -2529,7 +2609,7 @@ void LibraryWindow::showFoldersContextMenu(const QPoint &point) actions.setFolderAsWebComicAction->setChecked(false); actions.setFolderAsYonkomaAction->setChecked(false); - switch (type) { + switch (folder.type) { case FileType::Comic: actions.setFolderAsNormalAction->setChecked(true); break; @@ -2554,12 +2634,12 @@ void LibraryWindow::showFoldersContextMenu(const QPoint &point) menu.addSeparator(); //------------------------------- menu.addAction(actions.rescanXMLFromCurrentFolderAction); menu.addSeparator(); //------------------------------- - if (isCompleted) + if (folder.completed) menu.addAction(actions.setFolderAsNotCompletedAction); else menu.addAction(actions.setFolderAsCompletedAction); menu.addSeparator(); //------------------------------- - if (isRead) + if (folder.finished) menu.addAction(actions.setFolderAsUnreadAction); else menu.addAction(actions.setFolderAsReadAction); @@ -2571,6 +2651,11 @@ void LibraryWindow::showFoldersContextMenu(const QPoint &point) typeMenu->addAction(actions.setFolderAsWesternMangaAction); typeMenu->addAction(actions.setFolderAsWebComicAction); typeMenu->addAction(actions.setFolderAsYonkomaAction); + menu.addSeparator(); //------------------------------- + menu.addAction(actions.setFolderCoverAction); + if (!folder.customImage.isEmpty()) { + menu.addAction(actions.deleteCustomFolderCoverAction); + } menu.exec(foldersView->mapToGlobal(point)); } diff --git a/YACReaderLibrary/library_window.h b/YACReaderLibrary/library_window.h index 72c71e1a..b78a149c 100644 --- a/YACReaderLibrary/library_window.h +++ b/YACReaderLibrary/library_window.h @@ -246,6 +246,10 @@ public slots: void setFolderAsRead(); void setFolderAsUnread(); void setFolderType(FileType type); + void setFolderCover(); + void setCustomFolderCover(Folder folder); + void deleteCustomFolderCover(); + void resetFolderCover(Folder folder); void openContainingFolderComic(); void deleteCurrentLibrary(); void removeLibrary(); diff --git a/YACReaderLibrary/library_window_actions.cpp b/YACReaderLibrary/library_window_actions.cpp index 1f596677..554e5eea 100644 --- a/YACReaderLibrary/library_window_actions.cpp +++ b/YACReaderLibrary/library_window_actions.cpp @@ -281,6 +281,16 @@ void LibraryWindowActions::createActions(LibraryWindow *window, QSettings *setti setFolderAsUnreadAction->setData(SET_FOLDER_AS_UNREAD_ACTION_YL); setFolderAsUnreadAction->setShortcut(ShortcutsManager::getShortcutsManager().getShortcut(SET_FOLDER_AS_UNREAD_ACTION_YL)); + setFolderCoverAction = new QAction(window); + setFolderCoverAction->setText(tr("Set custom cover")); + setFolderCoverAction->setData(SET_FOLDER_COVER_ACTION_YL); + setFolderCoverAction->setShortcut(ShortcutsManager::getShortcutsManager().getShortcut(SET_FOLDER_COVER_ACTION_YL)); + + deleteCustomFolderCoverAction = new QAction(window); + deleteCustomFolderCoverAction->setText(tr("Delete custom cover")); + deleteCustomFolderCoverAction->setData(DELETE_CUSTOM_FOLDER_COVER_ACTION_YL); + deleteCustomFolderCoverAction->setShortcut(ShortcutsManager::getShortcutsManager().getShortcut(DELETE_CUSTOM_FOLDER_COVER_ACTION_YL)); + setFolderAsMangaAction = new QAction(window); setFolderAsMangaAction->setText(tr("manga")); setFolderAsMangaAction->setData(SET_FOLDER_AS_MANGA_ACTION_YL); @@ -445,6 +455,8 @@ void LibraryWindowActions::createActions(LibraryWindow *window, QSettings *setti window->addAction(setFolderAsWesternMangaAction); window->addAction(setFolderAsWebComicAction); window->addAction(setFolderAsYonkomaAction); + window->addAction(setFolderCoverAction); + window->addAction(deleteCustomFolderCoverAction); window->addAction(deleteMetadataAction); window->addAction(rescanXMLFromCurrentFolderAction); window->addAction(openContainingFolderComicAction); @@ -512,6 +524,8 @@ void LibraryWindowActions::createConnections( QObject::connect(setFolderAsReadAction, &QAction::triggered, window, &LibraryWindow::setFolderAsRead); QObject::connect(setFolderAsUnreadAction, &QAction::triggered, window, &LibraryWindow::setFolderAsUnread); QObject::connect(openContainingFolderAction, &QAction::triggered, window, &LibraryWindow::openContainingFolder); + QObject::connect(setFolderCoverAction, &QAction::triggered, window, &LibraryWindow::setFolderCover); + QObject::connect(deleteCustomFolderCoverAction, &QAction::triggered, window, &LibraryWindow::deleteCustomFolderCover); QObject::connect(setFolderAsMangaAction, &QAction::triggered, window, [=]() { window->setFolderType(FileType::Manga); @@ -630,7 +644,9 @@ void LibraryWindowActions::setUpShortcutsManagement(EditShortcutsDialog *editSho << setFolderAsMangaAction << setFolderAsNormalAction << updateCurrentFolderAction - << rescanXMLFromCurrentFolderAction); + << rescanXMLFromCurrentFolderAction + << setFolderCoverAction + << deleteCustomFolderCoverAction); allActions << tmpList; editShortcutsDialog->addActionsGroup("Lists", QIcon(":/images/shortcuts_group_folders.svg"), // TODO change icon diff --git a/YACReaderLibrary/library_window_actions.h b/YACReaderLibrary/library_window_actions.h index e2efb18b..30494236 100644 --- a/YACReaderLibrary/library_window_actions.h +++ b/YACReaderLibrary/library_window_actions.h @@ -72,6 +72,9 @@ public: QAction *setFolderAsWesternMangaAction; QAction *setFolderAsWebComicAction; QAction *setFolderAsYonkomaAction; + //-- + QAction *setFolderCoverAction; + QAction *deleteCustomFolderCoverAction; QAction *openContainingFolderComicAction; QAction *setAsReadAction; diff --git a/YACReaderLibrary/server/controllers/v2/covercontroller_v2.cpp b/YACReaderLibrary/server/controllers/v2/covercontroller_v2.cpp index 3cde157c..e0bb5a35 100644 --- a/YACReaderLibrary/server/controllers/v2/covercontroller_v2.cpp +++ b/YACReaderLibrary/server/controllers/v2/covercontroller_v2.cpp @@ -18,7 +18,8 @@ void CoverControllerV2::service(HttpRequest &request, HttpResponse &response) QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8(); QStringList pathElements = path.split('/'); QString libraryName = DBHelper::getLibraryName(pathElements.at(3).toInt()); - QString fileName = pathElements.at(5); + QStringList remainingPathElements = pathElements.mid(5); + QString fileName = remainingPathElements.join('/'); QImage img(YACReader::LibraryPaths::coverPathWithFileName(libraries.getPath(libraryName), fileName)); if (!img.isNull()) { diff --git a/YACReaderLibrary/server/requestmapper.cpp b/YACReaderLibrary/server/requestmapper.cpp index b54d734b..5f767bb0 100644 --- a/YACReaderLibrary/server/requestmapper.cpp +++ b/YACReaderLibrary/server/requestmapper.cpp @@ -246,7 +246,7 @@ void RequestMapper::serviceV2(HttpRequest &request, HttpResponse &response) QRegExp comicFullInfo("/v2/library/.+/comic/[0-9]+/fullinfo/?"); // get comic info QRegExp comicUpdate("/v2/library/.+/comic/[0-9]+/update/?"); // get comic info QRegExp comicClose("/v2/library/.+/comic/[0-9]+/close/?"); // the server will close the comic and free memory - QRegExp cover("/v2/library/.+/cover/[0-9a-f]+.jpg"); // get comic cover (navigation) + QRegExp cover("/v2/library/.+/cover/.+"); // get comic cover (navigation) QRegExp comicPage("/v2/library/.+/comic/[0-9]+/page/[0-9]+/?"); // get comic page QRegExp comicPageRemote("/v2/library/.+/comic/[0-9]+/page/[0-9]+/remote?"); // get comic page (remote reading) QRegExp serverVersion("/v2/version/?"); diff --git a/common/cover_utils.cpp b/common/cover_utils.cpp new file mode 100644 index 00000000..63f35ff3 --- /dev/null +++ b/common/cover_utils.cpp @@ -0,0 +1,18 @@ +#include "cover_utils.h" + +bool YACReader::saveCover(const QString &path, const QImage &cover) +{ + QImage scaled; + if (cover.width() > cover.height()) { + scaled = cover.scaledToWidth(640, Qt::SmoothTransformation); + } else { + auto aspectRatio = static_cast(cover.width()) / static_cast(cover.height()); + auto maxAllowedAspectRatio = 0.5; + if (aspectRatio < maxAllowedAspectRatio) { // cover is too tall, e.g. webtoon + scaled = cover.scaledToHeight(960, Qt::SmoothTransformation); + } else { + scaled = cover.scaledToWidth(480, Qt::SmoothTransformation); + } + } + return scaled.save(path, 0, 75); +} diff --git a/common/cover_utils.h b/common/cover_utils.h new file mode 100644 index 00000000..5e692bef --- /dev/null +++ b/common/cover_utils.h @@ -0,0 +1,9 @@ +#ifndef COVER_UTILS_H +#define COVER_UTILS_H + +#include + +namespace YACReader { +bool saveCover(const QString &path, const QImage &image); +} +#endif // COVER_UTILS_H diff --git a/common/yacreader_global.h b/common/yacreader_global.h index 9fb68539..3508bfcb 100644 --- a/common/yacreader_global.h +++ b/common/yacreader_global.h @@ -109,6 +109,7 @@ void iterate(const QModelIndex &index, const QAbstractItemModel *model, const std::function &iteration); +// TODO: remove all the dataPath variants and always use the root folder of a library `libraryPath` to get all the paths. struct LibraryPaths { LibraryPaths() = delete; // Prevent instantiation @@ -137,14 +138,34 @@ struct LibraryPaths { return QDir(libraryCoversFolderPath(libraryPath)).filePath(coverFileName(hash)); } + static QString libraryCustomFoldersCoverPath(const QString &libraryPath) // libraryPath + /.yacreaderlibrary/covers/folders + { + return QDir(libraryCoversFolderPath(libraryPath)).filePath("folders"); + } + + static QString libraryCustomFoldersCoverPathFromLibraryDataPath(const QString &libraryDataPath) + { + return QDir(libraryCoversPathFromLibraryDataPath(libraryDataPath)).filePath("folders"); + } + + static QString customFolderCoverPath(const QString &libraryPath, const QString &folderId) + { + return QDir(libraryCustomFoldersCoverPath(libraryPath)).filePath(coverFileName(folderId)); + } + + static QString customFolderCoverPathFromDataPath(const QString &libraryDataPath, const QString &folderId) + { + return QDir(libraryCustomFoldersCoverPathFromLibraryDataPath(libraryDataPath)).filePath(coverFileName(folderId)); + } + static QString coverPathFromLibraryDataPath(const QString &libraryDataPath, const QString &hash) // libraryDataPath + /covers/hash + .jpg { return QDir(libraryCoversPathFromLibraryDataPath(libraryDataPath)).filePath(coverFileName(hash)); } - static QString coverFileName(const QString &hash) // hash + .jpg + static QString coverFileName(const QString &id) // id + .jpg (it can be a comic hash or a folder id) { - return hash + ".jpg"; + return id + ".jpg"; } static QString coverPathWithFileName(const QString &libraryPath, const QString &fileName) // libraryPath + /.yacreaderlibrary/covers/hash + fileName diff --git a/shortcuts_management/shortcuts_manager.h b/shortcuts_management/shortcuts_manager.h index f511567e..5cf01bdf 100644 --- a/shortcuts_management/shortcuts_manager.h +++ b/shortcuts_management/shortcuts_manager.h @@ -74,6 +74,8 @@ public: #define SET_FOLDER_AS_NORMAL_ACTION_YL "SET_FOLDER_AS_NORMAL_ACTION_YL" #define SET_FOLDER_AS_WEB_COMIC_ACTION_YL "SET_FOLDER_AS_WEB_COMIC_ACTION_YL" #define SET_FOLDER_AS_YONKOMA_ACTION_YL "SET_FOLDER_AS_YONKOMA_ACTION_YL" +#define SET_FOLDER_COVER_ACTION_YL "SET_FOLDER_COVER_ACTION_YL" +#define DELETE_CUSTOM_FOLDER_COVER_ACTION_YL "DELETE_CUSTOM_FOLDER_COVER_ACTION_YL" #define OPEN_CONTAINING_FOLDER_COMIC_ACTION_YL "OPEN_CONTAINING_FOLDER_COMIC_ACTION_YL" #define RESET_COMIC_RATING_ACTION_YL "RESET_COMIC_RATING_ACTION_YL" #define SELECT_ALL_COMICS_ACTION_YL "SELECT_ALL_COMICS_ACTION_YL"