2020-09-21 17:17:34 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2020-05-26 16:34:33 +00:00
|
|
|
//
|
2020-09-21 17:17:34 +00:00
|
|
|
// EmulationStation Desktop Edition
|
2020-06-21 12:25:28 +00:00
|
|
|
// GuiScraperSearch.cpp
|
2020-05-26 16:34:33 +00:00
|
|
|
//
|
2020-06-21 12:25:28 +00:00
|
|
|
// User interface for the scraper where the user is able to see an overview
|
|
|
|
// of the game being scraped and an option to override the game search string.
|
|
|
|
// Used by both single-game scraping from the GuiMetaDataEd menu as well as
|
|
|
|
// to resolve scraping conflicts when run from GuiScraperMenu.
|
|
|
|
// The function to properly save scraped metadata is located here too.
|
2020-05-26 16:34:33 +00:00
|
|
|
//
|
2020-07-20 08:19:15 +00:00
|
|
|
// This GUI is called from GuiGameScraper for single-game scraping and
|
2020-06-21 12:25:28 +00:00
|
|
|
// from GuiScraperMulti for multi-game scraping.
|
2020-05-26 16:34:33 +00:00
|
|
|
//
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
#include "guis/GuiScraperSearch.h"
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
#include "CollectionSystemsManager.h"
|
|
|
|
#include "FileData.h"
|
|
|
|
#include "Log.h"
|
|
|
|
#include "MameNames.h"
|
|
|
|
#include "PlatformId.h"
|
|
|
|
#include "SystemData.h"
|
|
|
|
#include "Window.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "components/ComponentList.h"
|
2018-10-13 01:08:15 +00:00
|
|
|
#include "components/DateTimeEditComponent.h"
|
2014-06-25 16:29:58 +00:00
|
|
|
#include "components/ImageComponent.h"
|
|
|
|
#include "components/RatingComponent.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "components/ScrollableContainer.h"
|
|
|
|
#include "components/TextComponent.h"
|
|
|
|
#include "guis/GuiMsgBox.h"
|
|
|
|
#include "guis/GuiTextEditPopup.h"
|
|
|
|
#include "resources/Font.h"
|
2018-01-27 17:04:28 +00:00
|
|
|
#include "utils/StringUtil.h"
|
2020-06-07 18:09:02 +00:00
|
|
|
#include "views/ViewController.h"
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2021-05-24 19:22:10 +00:00
|
|
|
#define FAILED_VERIFICATION_RETRIES 8
|
2021-05-24 16:51:16 +00:00
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
GuiScraperSearch::GuiScraperSearch(Window* window, SearchType type, unsigned int scrapeCount)
|
|
|
|
: GuiComponent(window)
|
|
|
|
, mGrid(window, Vector2i(4, 3))
|
|
|
|
, mBusyAnim(window)
|
|
|
|
, mSearchType(type)
|
|
|
|
, mScrapeCount(scrapeCount)
|
|
|
|
, mScrapeRatings(false)
|
|
|
|
, mRefinedSearch(false)
|
|
|
|
, mFoundGame(false)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
addChild(&mGrid);
|
|
|
|
|
|
|
|
mBlockAccept = false;
|
2021-07-11 20:26:53 +00:00
|
|
|
mAcceptedResult = false;
|
2021-05-24 16:51:16 +00:00
|
|
|
mRetrySearch = false;
|
|
|
|
mRetryCount = 0;
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
// Left spacer (empty component, needed for borders).
|
2021-07-07 18:03:42 +00:00
|
|
|
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 0), false, false,
|
|
|
|
Vector2i(1, 3), GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
// Selected result name.
|
|
|
|
mResultName = std::make_shared<TextComponent>(mWindow, "Result name",
|
2021-07-07 18:03:42 +00:00
|
|
|
Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
// Selected result thumbnail.
|
|
|
|
mResultThumbnail = std::make_shared<ImageComponent>(mWindow);
|
|
|
|
mGrid.setEntry(mResultThumbnail, Vector2i(1, 1), false, false, Vector2i(1, 1));
|
|
|
|
|
|
|
|
// Selected result description and container.
|
|
|
|
mDescContainer = std::make_shared<ScrollableContainer>(mWindow);
|
2021-01-17 09:17:41 +00:00
|
|
|
|
|
|
|
// Adjust the game description text scrolling parameters depending on the search type.
|
|
|
|
if (mSearchType == NEVER_AUTO_ACCEPT)
|
2021-01-21 21:53:58 +00:00
|
|
|
mDescContainer->setScrollParameters(3000, 3000, 85);
|
2021-01-17 09:17:41 +00:00
|
|
|
else
|
2021-01-21 21:53:58 +00:00
|
|
|
mDescContainer->setScrollParameters(6000, 3000, 85);
|
2021-01-17 09:17:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
mResultDesc = std::make_shared<TextComponent>(mWindow, "Result desc",
|
2021-07-07 18:03:42 +00:00
|
|
|
Font::get(FONT_SIZE_SMALL), 0x777777FF);
|
2020-06-21 12:25:28 +00:00
|
|
|
mDescContainer->addChild(mResultDesc.get());
|
|
|
|
mDescContainer->setAutoScroll(true);
|
|
|
|
|
|
|
|
// Metadata.
|
|
|
|
auto font = Font::get(FONT_SIZE_SMALL); // Placeholder, gets replaced in onSizeChanged().
|
|
|
|
const unsigned int mdColor = 0x777777FF;
|
|
|
|
const unsigned int mdLblColor = 0x666666FF;
|
|
|
|
mMD_Rating = std::make_shared<RatingComponent>(mWindow);
|
|
|
|
mMD_ReleaseDate = std::make_shared<DateTimeEditComponent>(mWindow);
|
|
|
|
mMD_ReleaseDate->setColor(mdColor);
|
2020-08-02 13:56:32 +00:00
|
|
|
mMD_ReleaseDate->setUppercase(true);
|
2021-07-07 18:03:42 +00:00
|
|
|
mMD_Developer =
|
|
|
|
std::make_shared<TextComponent>(mWindow, "", font, mdColor, ALIGN_LEFT, Vector3f::Zero(),
|
|
|
|
Vector2f::Zero(), 0x00000000, 0.02f);
|
|
|
|
mMD_Publisher =
|
|
|
|
std::make_shared<TextComponent>(mWindow, "", font, mdColor, ALIGN_LEFT, Vector3f::Zero(),
|
|
|
|
Vector2f::Zero(), 0x00000000, 0.02f);
|
|
|
|
mMD_Genre =
|
|
|
|
std::make_shared<TextComponent>(mWindow, "", font, mdColor, ALIGN_LEFT, Vector3f::Zero(),
|
|
|
|
Vector2f::Zero(), 0x00000000, 0.02f);
|
|
|
|
mMD_Players =
|
|
|
|
std::make_shared<TextComponent>(mWindow, "", font, mdColor, ALIGN_LEFT, Vector3f::Zero(),
|
|
|
|
Vector2f::Zero(), 0x00000000, 0.02f);
|
2020-07-30 14:29:38 +00:00
|
|
|
mMD_Filler = std::make_shared<TextComponent>(mWindow, "", font, mdColor);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2020-12-16 18:03:05 +00:00
|
|
|
if (Settings::getInstance()->getString("Scraper") != "thegamesdb")
|
2020-07-30 14:29:38 +00:00
|
|
|
mScrapeRatings = true;
|
|
|
|
|
|
|
|
if (mScrapeRatings)
|
2021-07-07 18:03:42 +00:00
|
|
|
mMD_Pairs.push_back(
|
|
|
|
MetaDataPair(std::make_shared<TextComponent>(mWindow, "RATING:", font, mdLblColor),
|
|
|
|
mMD_Rating, false));
|
|
|
|
|
|
|
|
mMD_Pairs.push_back(MetaDataPair(
|
|
|
|
std::make_shared<TextComponent>(mWindow, "RELEASED:", font, mdLblColor), mMD_ReleaseDate));
|
|
|
|
mMD_Pairs.push_back(MetaDataPair(
|
|
|
|
std::make_shared<TextComponent>(mWindow, "DEVELOPER:", font, mdLblColor), mMD_Developer));
|
|
|
|
mMD_Pairs.push_back(MetaDataPair(
|
|
|
|
std::make_shared<TextComponent>(mWindow, "PUBLISHER:", font, mdLblColor), mMD_Publisher));
|
|
|
|
mMD_Pairs.push_back(MetaDataPair(
|
|
|
|
std::make_shared<TextComponent>(mWindow, "GENRE:", font, mdLblColor), mMD_Genre));
|
|
|
|
mMD_Pairs.push_back(MetaDataPair(
|
|
|
|
std::make_shared<TextComponent>(mWindow, "PLAYERS:", font, mdLblColor), mMD_Players));
|
|
|
|
|
2021-01-30 11:32:46 +00:00
|
|
|
// If no rating is being scraped, add a filler to make sure that the fonts keep the same
|
|
|
|
// size so the GUI looks consistent.
|
2020-07-30 14:29:38 +00:00
|
|
|
if (!mScrapeRatings)
|
2021-07-07 18:03:42 +00:00
|
|
|
mMD_Pairs.push_back(MetaDataPair(
|
|
|
|
std::make_shared<TextComponent>(mWindow, "", font, mdLblColor), mMD_Filler));
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
mMD_Grid = std::make_shared<ComponentGrid>(
|
|
|
|
mWindow, Vector2i(2, static_cast<int>(mMD_Pairs.size() * 2 - 1)));
|
2020-06-21 12:25:28 +00:00
|
|
|
unsigned int i = 0;
|
|
|
|
for (auto it = mMD_Pairs.cbegin(); it != mMD_Pairs.cend(); it++) {
|
|
|
|
mMD_Grid->setEntry(it->first, Vector2i(0, i), false, true);
|
|
|
|
mMD_Grid->setEntry(it->second, Vector2i(1, i), false, it->resize);
|
|
|
|
i += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
mGrid.setEntry(mMD_Grid, Vector2i(2, 1), false, false);
|
|
|
|
|
|
|
|
// Result list.
|
|
|
|
mResultList = std::make_shared<ComponentList>(mWindow);
|
|
|
|
mResultList->setCursorChangedCallback([this](CursorState state) {
|
2021-07-07 18:03:42 +00:00
|
|
|
if (state == CURSOR_STOPPED)
|
|
|
|
updateInfoPane();
|
|
|
|
});
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
updateViewStyle();
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-10-18 09:01:56 +00:00
|
|
|
GuiScraperSearch::~GuiScraperSearch()
|
|
|
|
{
|
2020-10-18 09:41:36 +00:00
|
|
|
// The following manual resets are required to avoid a race condition when the
|
|
|
|
// STOP button is pressed in the multi-scraper. Without this code there will be
|
|
|
|
// a memory leak as the cURL easy handle is not cleaned up. For a normally completed
|
|
|
|
// scraping however, the destructor will already have been called in HttpReq.
|
|
|
|
if (mSearchHandle)
|
|
|
|
mSearchHandle.reset();
|
|
|
|
|
|
|
|
if (mMDRetrieveURLsHandle)
|
|
|
|
mMDRetrieveURLsHandle.reset();
|
|
|
|
|
|
|
|
if (mMDResolveHandle)
|
|
|
|
mMDResolveHandle.reset();
|
|
|
|
|
2020-11-14 19:46:08 +00:00
|
|
|
if (mThumbnailReqMap.size() > 0)
|
|
|
|
mThumbnailReqMap.clear();
|
2020-10-18 09:41:36 +00:00
|
|
|
|
2020-10-18 09:01:56 +00:00
|
|
|
HttpReq::cleanupCurlMulti();
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
// This is required to properly refresh the gamelist view if the user aborted the
|
|
|
|
// scraping when the miximage was getting generated.
|
|
|
|
if (Settings::getInstance()->getBool("MiximageGenerate") &&
|
2021-07-07 18:03:42 +00:00
|
|
|
mMiximageGeneratorThread.joinable()) {
|
2021-06-07 21:02:42 +00:00
|
|
|
mScrapeResult.savedNewMedia = true;
|
|
|
|
// We always let the miximage generator thread complete.
|
|
|
|
mMiximageGeneratorThread.join();
|
|
|
|
mMiximageGenerator.reset();
|
|
|
|
TextureResource::manualUnload(mLastSearch.game->getMiximagePath(), false);
|
|
|
|
ViewController::get()->onFileChanged(mLastSearch.game, true);
|
|
|
|
}
|
2020-10-18 09:01:56 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
void GuiScraperSearch::onSizeChanged()
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
mGrid.setSize(mSize);
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mSize.x() == 0 || mSize.y() == 0)
|
|
|
|
return;
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Column widths.
|
|
|
|
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT)
|
|
|
|
mGrid.setColWidthPerc(0, 0.02f); // Looks better when this is higher in auto mode.
|
|
|
|
else
|
|
|
|
mGrid.setColWidthPerc(0, 0.01f);
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
mGrid.setColWidthPerc(1, 0.25f);
|
2021-01-30 11:32:46 +00:00
|
|
|
|
|
|
|
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT)
|
|
|
|
mGrid.setColWidthPerc(2, 0.25f);
|
|
|
|
else
|
|
|
|
mGrid.setColWidthPerc(2, 0.28f);
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Row heights.
|
|
|
|
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) // Show name.
|
|
|
|
mGrid.setRowHeightPerc(0, (mResultName->getFont()->getHeight() * 1.6f) /
|
2021-07-07 18:03:42 +00:00
|
|
|
mGrid.getSize().y()); // Result name.
|
2020-06-21 12:25:28 +00:00
|
|
|
else
|
|
|
|
mGrid.setRowHeightPerc(0, 0.0825f); // Hide name but do padding.
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT)
|
|
|
|
mGrid.setRowHeightPerc(2, 0.2f);
|
|
|
|
else
|
|
|
|
mGrid.setRowHeightPerc(1, 0.505f);
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
const float boxartCellScale = 0.9f;
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Limit thumbnail size using setMaxHeight - we do this instead of letting mGrid
|
|
|
|
// call setSize because it maintains the aspect ratio.
|
|
|
|
// We also pad a little so it doesn't rub up against the metadata labels.
|
|
|
|
mResultThumbnail->setMaxSize(mGrid.getColWidth(1) * boxartCellScale, mGrid.getRowHeight(1));
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Metadata.
|
|
|
|
resizeMetadata();
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mSearchType != ALWAYS_ACCEPT_FIRST_RESULT)
|
2021-07-07 18:03:42 +00:00
|
|
|
mDescContainer->setSize(mGrid.getColWidth(1) * boxartCellScale + mGrid.getColWidth(2),
|
|
|
|
mResultDesc->getFont()->getHeight() * 3.0f);
|
2020-06-21 12:25:28 +00:00
|
|
|
else
|
2021-01-17 11:43:31 +00:00
|
|
|
mDescContainer->setSize(mGrid.getColWidth(3) * boxartCellScale,
|
2021-07-07 18:03:42 +00:00
|
|
|
mResultDesc->getFont()->getHeight() * 6.0f);
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Make description text wrap at edge of container.
|
|
|
|
mResultDesc->setSize(mDescContainer->getSize().x(), 0);
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2021-01-23 14:22:30 +00:00
|
|
|
// Set the width of mResultName to the cell width so that text abbreviation will work correctly.
|
|
|
|
Vector2f resultNameSize = mResultName->getSize();
|
|
|
|
mResultName->setSize(mGrid.getColWidth(3), resultNameSize.y());
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
mGrid.onSizeChanged();
|
|
|
|
mBusyAnim.setSize(mSize);
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
void GuiScraperSearch::resizeMetadata()
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
mMD_Grid->setSize(mGrid.getColWidth(2), mGrid.getRowHeight(1));
|
|
|
|
if (mMD_Grid->getSize().y() > mMD_Pairs.size()) {
|
2020-11-17 22:06:54 +00:00
|
|
|
const int fontHeight = static_cast<int>(mMD_Grid->getSize().y() / mMD_Pairs.size() * 0.8f);
|
2020-06-21 12:25:28 +00:00
|
|
|
auto fontLbl = Font::get(fontHeight, FONT_PATH_REGULAR);
|
|
|
|
auto fontComp = Font::get(fontHeight, FONT_PATH_LIGHT);
|
|
|
|
|
|
|
|
// Update label fonts.
|
|
|
|
float maxLblWidth = 0;
|
|
|
|
for (auto it = mMD_Pairs.cbegin(); it != mMD_Pairs.cend(); it++) {
|
|
|
|
it->first->setFont(fontLbl);
|
|
|
|
it->first->setSize(0, 0);
|
|
|
|
if (it->first->getSize().x() > maxLblWidth)
|
2021-07-07 18:03:42 +00:00
|
|
|
maxLblWidth =
|
|
|
|
it->first->getSize().x() + (16.0f * Renderer::getScreenWidthModifier());
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < mMD_Pairs.size(); i++)
|
2021-07-07 18:03:42 +00:00
|
|
|
mMD_Grid->setRowHeightPerc(
|
|
|
|
i * 2, (fontLbl->getLetterHeight() + (2.0f * Renderer::getScreenHeightModifier())) /
|
|
|
|
mMD_Grid->getSize().y());
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
// Update component fonts.
|
|
|
|
mMD_ReleaseDate->setFont(fontComp);
|
|
|
|
mMD_Developer->setFont(fontComp);
|
|
|
|
mMD_Publisher->setFont(fontComp);
|
|
|
|
mMD_Genre->setFont(fontComp);
|
|
|
|
mMD_Players->setFont(fontComp);
|
|
|
|
|
|
|
|
mMD_Grid->setColWidthPerc(0, maxLblWidth / mMD_Grid->getSize().x());
|
|
|
|
|
2020-07-30 14:29:38 +00:00
|
|
|
if (mScrapeRatings) {
|
2020-06-21 12:25:28 +00:00
|
|
|
// Rating is manually sized.
|
|
|
|
mMD_Rating->setSize(mMD_Grid->getColWidth(1), fontLbl->getHeight() * 0.65f);
|
|
|
|
mMD_Grid->onSizeChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make result font follow label font.
|
|
|
|
mResultDesc->setFont(Font::get(fontHeight, FONT_PATH_REGULAR));
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
void GuiScraperSearch::updateViewStyle()
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
// Unlink description, result list and result name.
|
|
|
|
mGrid.removeEntry(mResultName);
|
|
|
|
mGrid.removeEntry(mResultDesc);
|
|
|
|
mGrid.removeEntry(mResultList);
|
|
|
|
|
|
|
|
// Add them back depending on search type.
|
|
|
|
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) {
|
|
|
|
// Show name.
|
2021-01-23 14:22:30 +00:00
|
|
|
mGrid.setEntry(mResultName, Vector2i(1, 0), false, false, Vector2i(2, 1),
|
2021-07-07 18:03:42 +00:00
|
|
|
GridFlags::BORDER_TOP);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
// Need a border on the bottom left.
|
2021-07-07 18:03:42 +00:00
|
|
|
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 2), false, false,
|
|
|
|
Vector2i(3, 1), GridFlags::BORDER_BOTTOM);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
// Show description on the right.
|
|
|
|
mGrid.setEntry(mDescContainer, Vector2i(3, 0), false, false, Vector2i(1, 3),
|
2021-07-07 18:03:42 +00:00
|
|
|
GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
|
2020-06-21 12:25:28 +00:00
|
|
|
// Make description text wrap at edge of container.
|
2021-05-30 10:28:17 +00:00
|
|
|
mResultDesc->setSize(mDescContainer->getSize().x(), 0.0f);
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Fake row where name would be.
|
2021-07-07 18:03:42 +00:00
|
|
|
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 0), false, true,
|
|
|
|
Vector2i(2, 1), GridFlags::BORDER_TOP);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
// Show result list on the right.
|
|
|
|
mGrid.setEntry(mResultList, Vector2i(3, 0), true, true, Vector2i(1, 3),
|
2021-07-07 18:03:42 +00:00
|
|
|
GridFlags::BORDER_LEFT | GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
// Show description under image/info.
|
|
|
|
mGrid.setEntry(mDescContainer, Vector2i(1, 2), false, false, Vector2i(2, 1),
|
2021-07-07 18:03:42 +00:00
|
|
|
GridFlags::BORDER_BOTTOM);
|
2020-06-21 12:25:28 +00:00
|
|
|
// Make description text wrap at edge of container.
|
|
|
|
mResultDesc->setSize(mDescContainer->getSize().x(), 0);
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
void GuiScraperSearch::search(const ScraperSearchParams& params)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
mBlockAccept = true;
|
2021-07-11 20:26:53 +00:00
|
|
|
mAcceptedResult = false;
|
2021-06-08 19:07:35 +00:00
|
|
|
mMiximageResult = false;
|
2021-06-07 21:02:42 +00:00
|
|
|
mScrapeResult = {};
|
2017-05-25 17:56:06 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
mResultList->clear();
|
|
|
|
mScraperResults.clear();
|
|
|
|
mMDRetrieveURLsHandle.reset();
|
2020-11-14 19:46:08 +00:00
|
|
|
mThumbnailReqMap.clear();
|
2020-06-21 12:25:28 +00:00
|
|
|
mMDResolveHandle.reset();
|
|
|
|
updateInfoPane();
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
mLastSearch = params;
|
|
|
|
mSearchHandle = startScraperSearch(params);
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
void GuiScraperSearch::stop()
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-11-14 19:46:08 +00:00
|
|
|
mThumbnailReqMap.clear();
|
2020-06-21 12:25:28 +00:00
|
|
|
mSearchHandle.reset();
|
|
|
|
mMDResolveHandle.reset();
|
|
|
|
mMDRetrieveURLsHandle.reset();
|
2021-06-07 21:02:42 +00:00
|
|
|
mMiximageGenerator.reset();
|
2020-06-21 12:25:28 +00:00
|
|
|
mBlockAccept = false;
|
2021-07-11 20:26:53 +00:00
|
|
|
mAcceptedResult = false;
|
2021-06-08 19:07:35 +00:00
|
|
|
mMiximageResult = false;
|
2021-06-07 21:02:42 +00:00
|
|
|
mScrapeResult = {};
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
void GuiScraperSearch::onSearchDone(const std::vector<ScraperSearchResult>& results)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
mResultList->clear();
|
|
|
|
|
|
|
|
mScraperResults = results;
|
|
|
|
|
|
|
|
auto font = Font::get(FONT_SIZE_MEDIUM);
|
|
|
|
unsigned int color = 0x777777FF;
|
|
|
|
if (results.empty()) {
|
|
|
|
// Check if the scraper used is still valid.
|
|
|
|
if (!isValidConfiguredScraper()) {
|
2021-07-07 18:03:42 +00:00
|
|
|
mWindow->pushGui(new GuiMsgBox(
|
|
|
|
mWindow, getHelpStyle(),
|
|
|
|
Utils::String::toUpper("Configured scraper is no longer available.\n"
|
|
|
|
"Please change the scraping source in the settings."),
|
2020-06-21 12:25:28 +00:00
|
|
|
"FINISH", mSkipCallback));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mFoundGame = false;
|
|
|
|
ComponentListRow row;
|
2021-07-07 18:03:42 +00:00
|
|
|
row.addElement(std::make_shared<TextComponent>(mWindow, "NO GAMES FOUND", font, color),
|
|
|
|
true);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
if (mSkipCallback)
|
|
|
|
row.makeAcceptInputHandler(mSkipCallback);
|
|
|
|
|
|
|
|
mResultList->addRow(row);
|
|
|
|
mGrid.resetCursor();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mFoundGame = true;
|
2020-08-16 14:53:49 +00:00
|
|
|
ComponentListRow row;
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2020-08-16 14:53:49 +00:00
|
|
|
for (size_t i = 0; i < results.size(); i++) {
|
|
|
|
row.elements.clear();
|
2021-07-07 18:03:42 +00:00
|
|
|
row.addElement(
|
|
|
|
std::make_shared<TextComponent>(
|
|
|
|
mWindow, Utils::String::toUpper(results.at(i).mdl.get("name")), font, color),
|
|
|
|
true);
|
2020-08-16 14:53:49 +00:00
|
|
|
row.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); });
|
|
|
|
mResultList->addRow(row);
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
2020-08-16 14:53:49 +00:00
|
|
|
mGrid.resetCursor();
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mBlockAccept = false;
|
|
|
|
updateInfoPane();
|
|
|
|
|
|
|
|
// If there is no thumbnail to download and we're in semi-automatic mode, proceed to return
|
|
|
|
// the results or we'll get stuck forever waiting for a thumbnail to be downloaded.
|
|
|
|
if (mSearchType == ACCEPT_SINGLE_MATCHES && results.size() == 1 &&
|
2021-07-07 18:03:42 +00:00
|
|
|
mScraperResults.front().thumbnailImageUrl == "")
|
2020-06-21 12:25:28 +00:00
|
|
|
returnResult(mScraperResults.front());
|
|
|
|
|
|
|
|
// For automatic mode, if there's no thumbnail to download or no matching games found,
|
|
|
|
// proceed directly or we'll get stuck forever.
|
|
|
|
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) {
|
2021-07-07 18:03:42 +00:00
|
|
|
if (mScraperResults.size() == 0 ||
|
|
|
|
(mScraperResults.size() > 0 && mScraperResults.front().thumbnailImageUrl == "")) {
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mScraperResults.size() == 0)
|
|
|
|
mSkipCallback();
|
|
|
|
else
|
|
|
|
returnResult(mScraperResults.front());
|
|
|
|
}
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2021-05-24 16:51:16 +00:00
|
|
|
void GuiScraperSearch::onSearchError(const std::string& error, HttpReq::Status status)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2021-05-24 16:51:16 +00:00
|
|
|
// This is a workaround for a somehow frequently recurring issue with screenscraper.fr
|
|
|
|
// where requests to download the thumbnails are randomly met with TLS verification errors.
|
|
|
|
// It's unclear why it only happens to the thumbnail requests, but it usually goes away
|
|
|
|
// after a few days or so. If this issue occurs and the corresponding setting has been
|
|
|
|
// enabled, we'll retry the search automatically up to FAILED_VERIFICATION_RETRIES number
|
|
|
|
// of times. Usually a few retries is enough to get the thumbnail to download. If not,
|
|
|
|
// the error dialog will be presented to the user, and if the "Retry" button is pressed,
|
|
|
|
// a new round of retries will take place.
|
|
|
|
if (status == HttpReq::REQ_FAILED_VERIFICATION && mRetryCount < FAILED_VERIFICATION_RETRIES &&
|
2021-07-07 18:03:42 +00:00
|
|
|
Settings::getInstance()->getBool("ScraperRetryPeerVerification")) {
|
2021-05-24 16:51:16 +00:00
|
|
|
LOG(LogError) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", "");
|
|
|
|
mRetrySearch = true;
|
|
|
|
mRetryCount++;
|
2021-07-07 18:03:42 +00:00
|
|
|
LOG(LogError) << "GuiScraperSearch: Attempting automatic retry " << mRetryCount << " of "
|
|
|
|
<< FAILED_VERIFICATION_RETRIES;
|
2021-05-24 16:51:16 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mRetryCount = 0;
|
|
|
|
}
|
|
|
|
|
2020-07-31 12:24:14 +00:00
|
|
|
if (mScrapeCount > 1) {
|
2021-05-24 16:51:16 +00:00
|
|
|
LOG(LogError) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", "");
|
2020-07-31 12:24:14 +00:00
|
|
|
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), Utils::String::toUpper(error),
|
2021-07-07 18:03:42 +00:00
|
|
|
"RETRY",
|
|
|
|
std::bind(&GuiScraperSearch::search, this, mLastSearch),
|
|
|
|
"SKIP", mSkipCallback, "CANCEL", mCancelCallback, true));
|
2020-07-31 12:24:14 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-05-24 16:51:16 +00:00
|
|
|
LOG(LogError) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", "");
|
2020-07-31 12:24:14 +00:00
|
|
|
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), Utils::String::toUpper(error),
|
2021-07-07 18:03:42 +00:00
|
|
|
"RETRY",
|
|
|
|
std::bind(&GuiScraperSearch::search, this, mLastSearch),
|
|
|
|
"CANCEL", mCancelCallback, "", nullptr, true));
|
2020-07-31 12:24:14 +00:00
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
int GuiScraperSearch::getSelectedIndex()
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (!mScraperResults.size() || mGrid.getSelectedComponent() != mResultList)
|
|
|
|
return -1;
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return mResultList->getCursorId();
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
void GuiScraperSearch::updateInfoPane()
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
int i = getSelectedIndex();
|
|
|
|
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT && mScraperResults.size())
|
|
|
|
i = 0;
|
|
|
|
|
2020-11-17 22:06:54 +00:00
|
|
|
if (i != -1 && static_cast<int>(mScraperResults.size() > i)) {
|
2020-06-21 12:25:28 +00:00
|
|
|
ScraperSearchResult& res = mScraperResults.at(i);
|
2020-08-16 14:53:49 +00:00
|
|
|
|
2021-01-23 14:22:30 +00:00
|
|
|
mResultName->setText(Utils::String::toUpper(res.mdl.get("name")));
|
2020-06-21 12:25:28 +00:00
|
|
|
mResultDesc->setText(Utils::String::toUpper(res.mdl.get("desc")));
|
|
|
|
mDescContainer->reset();
|
|
|
|
|
|
|
|
mResultThumbnail->setImage("");
|
|
|
|
const std::string& thumb = res.screenshotUrl.empty() ? res.coverUrl : res.screenshotUrl;
|
2020-11-14 19:46:08 +00:00
|
|
|
mScraperResults[i].thumbnailImageUrl = thumb;
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
// Cache the thumbnail image in mScraperResults so that we don't need to download
|
|
|
|
// it every time the list is scrolled back and forth.
|
2020-11-14 19:46:08 +00:00
|
|
|
if (mScraperResults[i].thumbnailImageData.size() > 0) {
|
|
|
|
std::string content = mScraperResults[i].thumbnailImageData;
|
2020-06-21 12:25:28 +00:00
|
|
|
mResultThumbnail->setImage(content.data(), content.length());
|
|
|
|
mGrid.onSizeChanged(); // A hack to fix the thumbnail position since its size changed.
|
|
|
|
}
|
|
|
|
// If it's not cached in mScraperResults it should mean that it's the first time
|
|
|
|
// we access the entry, and therefore we need to download the image.
|
|
|
|
else {
|
|
|
|
if (!thumb.empty()) {
|
|
|
|
// Make sure we don't attempt to download the same thumbnail twice.
|
2020-11-14 19:46:08 +00:00
|
|
|
if (mScraperResults[i].thumbnailDownloadStatus != IN_PROGRESS) {
|
2020-06-21 12:25:28 +00:00
|
|
|
mScraperResults[i].thumbnailDownloadStatus = IN_PROGRESS;
|
2020-11-14 19:46:08 +00:00
|
|
|
// Add an entry into the thumbnail map, this way we can track and download
|
|
|
|
// each thumbnail separately even as they're downloading while scrolling
|
|
|
|
// through the result list.
|
2021-07-07 18:03:42 +00:00
|
|
|
mThumbnailReqMap.insert(std::pair<std::string, std::unique_ptr<HttpReq>>(
|
|
|
|
mScraperResults[i].thumbnailImageUrl,
|
|
|
|
std::unique_ptr<HttpReq>(new HttpReq(thumb))));
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Metadata.
|
2020-07-30 14:29:38 +00:00
|
|
|
if (mScrapeRatings) {
|
2020-06-21 12:25:28 +00:00
|
|
|
mMD_Rating->setValue(Utils::String::toUpper(res.mdl.get("rating")));
|
|
|
|
mMD_Rating->setOpacity(255);
|
|
|
|
}
|
|
|
|
mMD_ReleaseDate->setValue(Utils::String::toUpper(res.mdl.get("releasedate")));
|
|
|
|
mMD_Developer->setText(Utils::String::toUpper(res.mdl.get("developer")));
|
|
|
|
mMD_Publisher->setText(Utils::String::toUpper(res.mdl.get("publisher")));
|
|
|
|
mMD_Genre->setText(Utils::String::toUpper(res.mdl.get("genre")));
|
|
|
|
mMD_Players->setText(Utils::String::toUpper(res.mdl.get("players")));
|
|
|
|
mGrid.onSizeChanged();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mResultName->setText("");
|
|
|
|
mResultDesc->setText("");
|
|
|
|
mResultThumbnail->setImage("");
|
|
|
|
|
|
|
|
// Metadata.
|
2020-07-30 14:29:38 +00:00
|
|
|
if (mScrapeRatings) {
|
2020-06-21 12:25:28 +00:00
|
|
|
mMD_Rating->setValue("");
|
|
|
|
mMD_Rating->setOpacity(0);
|
|
|
|
}
|
2020-07-20 08:19:15 +00:00
|
|
|
// Set the release date to this value to force DateTimeEditComponent to put a
|
|
|
|
// blank instead of the text 'unknown' prior to the scrape result being returned.
|
|
|
|
mMD_ReleaseDate->setValue("19700101T010101");
|
2020-06-21 12:25:28 +00:00
|
|
|
mMD_Developer->setText("");
|
|
|
|
mMD_Publisher->setText("");
|
|
|
|
mMD_Genre->setText("");
|
|
|
|
mMD_Players->setText("");
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
bool GuiScraperSearch::input(InputConfig* config, Input input)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (config->isMappedTo("a", input) && input.value != 0) {
|
|
|
|
if (mBlockAccept)
|
|
|
|
return true;
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2021-07-11 20:26:53 +00:00
|
|
|
// Refine the search, unless the result has already been accepted or we're in semi-automatic
|
|
|
|
// mode and there are less than 2 search results.
|
|
|
|
if (!mAcceptedResult && config->isMappedTo("y", input) && input.value != 0) {
|
|
|
|
if (mSearchType != ACCEPT_SINGLE_MATCHES ||
|
|
|
|
mSearchType == ACCEPT_SINGLE_MATCHES && mScraperResults.size() > 1) {
|
|
|
|
openInputScreen(mLastSearch);
|
|
|
|
}
|
|
|
|
}
|
2020-12-18 15:35:19 +00:00
|
|
|
|
2021-07-11 20:26:53 +00:00
|
|
|
// Skip game, unless the result has already been accepted.
|
|
|
|
if (!mAcceptedResult && mScrapeCount > 1 && config->isMappedTo("x", input) && input.value != 0)
|
2020-08-06 13:12:04 +00:00
|
|
|
mSkipCallback();
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return GuiComponent::input(config, input);
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
void GuiScraperSearch::render(const Transform4x4f& parentTrans)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
Transform4x4f trans = parentTrans * getTransform();
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
renderChildren(trans);
|
2020-07-30 14:29:38 +00:00
|
|
|
Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), 0x00000009, 0x00000009);
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mBlockAccept) {
|
|
|
|
Renderer::setMatrix(trans);
|
|
|
|
mBusyAnim.render(trans);
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
void GuiScraperSearch::returnResult(ScraperSearchResult result)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
mBlockAccept = true;
|
2021-07-11 20:26:53 +00:00
|
|
|
mAcceptedResult = true;
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Resolve metadata image before returning.
|
|
|
|
if (result.mediaFilesDownloadStatus != COMPLETED) {
|
|
|
|
result.mediaFilesDownloadStatus = IN_PROGRESS;
|
|
|
|
mMDResolveHandle = resolveMetaDataAssets(result, mLastSearch);
|
|
|
|
return;
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-07-31 12:24:14 +00:00
|
|
|
mScrapeCount -= 1;
|
2020-06-21 12:25:28 +00:00
|
|
|
mAcceptCallback(result);
|
2021-01-26 16:40:37 +00:00
|
|
|
mRefinedSearch = false;
|
2021-05-24 16:51:16 +00:00
|
|
|
mRetryCount = 0;
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
void GuiScraperSearch::update(int deltaTime)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
GuiComponent::update(deltaTime);
|
|
|
|
|
2021-05-24 16:51:16 +00:00
|
|
|
// There was a failure and we're attempting an automatic retry.
|
|
|
|
if (mRetrySearch) {
|
|
|
|
mRetrySearch = false;
|
|
|
|
stop();
|
|
|
|
search(mLastSearch);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mBlockAccept)
|
|
|
|
mBusyAnim.update(deltaTime);
|
|
|
|
|
2020-11-14 19:46:08 +00:00
|
|
|
// Check if the thumbnail for the currently selected game has finished downloading.
|
|
|
|
if (mScraperResults.size() > 0) {
|
2021-07-07 18:03:42 +00:00
|
|
|
auto it =
|
|
|
|
mThumbnailReqMap.find(mScraperResults[mResultList->getCursorId()].thumbnailImageUrl);
|
2020-11-14 19:46:08 +00:00
|
|
|
if (it != mThumbnailReqMap.end() && it->second->status() != HttpReq::REQ_IN_PROGRESS)
|
|
|
|
updateThumbnail();
|
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
if (mSearchHandle && mSearchHandle->status() != ASYNC_IN_PROGRESS) {
|
|
|
|
auto status = mSearchHandle->status();
|
|
|
|
mScraperResults = mSearchHandle->getResults();
|
|
|
|
auto statusString = mSearchHandle->getStatusString();
|
|
|
|
|
|
|
|
// We reset here because onSearchDone in auto mode can call mSkipCallback() which
|
|
|
|
// can call another search() which will set our mSearchHandle to something important.
|
|
|
|
mSearchHandle.reset();
|
|
|
|
|
|
|
|
if (status == ASYNC_DONE && mScraperResults.size() == 0)
|
|
|
|
onSearchDone(mScraperResults);
|
|
|
|
|
|
|
|
if (status == ASYNC_DONE && mScraperResults.size() > 0) {
|
|
|
|
if (mScraperResults.front().mediaURLFetch == COMPLETED) {
|
|
|
|
onSearchDone(mScraperResults);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
std::string gameIDs;
|
|
|
|
for (auto it = mScraperResults.cbegin(); it != mScraperResults.cend(); it++)
|
|
|
|
gameIDs += it->gameID + ',';
|
|
|
|
|
|
|
|
// Remove the last comma
|
|
|
|
gameIDs.pop_back();
|
|
|
|
mMDRetrieveURLsHandle = startMediaURLsFetch(gameIDs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (status == ASYNC_ERROR) {
|
|
|
|
onSearchError(statusString);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mMDRetrieveURLsHandle && mMDRetrieveURLsHandle->status() != ASYNC_IN_PROGRESS) {
|
|
|
|
if (mMDRetrieveURLsHandle->status() == ASYNC_DONE) {
|
|
|
|
auto results_media = mMDRetrieveURLsHandle->getResults();
|
|
|
|
auto statusString_media = mMDRetrieveURLsHandle->getStatusString();
|
|
|
|
auto results_scrape = mScraperResults;
|
|
|
|
mMDRetrieveURLsHandle.reset();
|
|
|
|
mScraperResults.clear();
|
|
|
|
|
|
|
|
// Combine the intial scrape results with the media URL results.
|
|
|
|
for (auto it = results_media.cbegin(); it != results_media.cend(); it++) {
|
|
|
|
for (unsigned int i = 0; i < results_scrape.size(); i++) {
|
|
|
|
if (results_scrape[i].gameID == it->gameID) {
|
2021-05-30 10:28:17 +00:00
|
|
|
results_scrape[i].box3DUrl = it->box3DUrl;
|
2020-06-21 12:25:28 +00:00
|
|
|
results_scrape[i].coverUrl = it->coverUrl;
|
|
|
|
results_scrape[i].marqueeUrl = it->marqueeUrl;
|
|
|
|
results_scrape[i].screenshotUrl = it->screenshotUrl;
|
2020-08-05 20:38:44 +00:00
|
|
|
results_scrape[i].videoUrl = it->videoUrl;
|
2020-06-21 12:25:28 +00:00
|
|
|
results_scrape[i].scraperRequestAllowance = it->scraperRequestAllowance;
|
|
|
|
results_scrape[i].mediaURLFetch = COMPLETED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
onSearchDone(results_scrape);
|
|
|
|
}
|
|
|
|
else if (mMDRetrieveURLsHandle->status() == ASYNC_ERROR) {
|
|
|
|
onSearchError(mMDRetrieveURLsHandle->getStatusString());
|
|
|
|
mMDRetrieveURLsHandle.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-07 21:02:42 +00:00
|
|
|
// Check if a miximage generator thread was started, and if the processing has been completed.
|
|
|
|
if (mMiximageGenerator && mGeneratorFuture.valid()) {
|
2021-06-09 18:56:41 +00:00
|
|
|
// Only wait one millisecond as this update() function runs very frequently.
|
2021-06-07 21:02:42 +00:00
|
|
|
if (mGeneratorFuture.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready) {
|
|
|
|
mMDResolveHandle.reset();
|
2021-06-08 19:07:35 +00:00
|
|
|
// We always let the miximage generator thread complete.
|
|
|
|
mMiximageGeneratorThread.join();
|
2021-06-08 20:25:53 +00:00
|
|
|
if (!mGeneratorFuture.get())
|
2021-06-07 21:02:42 +00:00
|
|
|
mScrapeResult.savedNewMedia = true;
|
|
|
|
returnResult(mScrapeResult);
|
|
|
|
mMiximageGenerator.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mMDResolveHandle && mMDResolveHandle->status() != ASYNC_IN_PROGRESS) {
|
|
|
|
if (mMDResolveHandle->status() == ASYNC_DONE) {
|
2021-06-07 21:02:42 +00:00
|
|
|
mScrapeResult = mMDResolveHandle->getResult();
|
|
|
|
mScrapeResult.mediaFilesDownloadStatus = COMPLETED;
|
2020-06-21 12:25:28 +00:00
|
|
|
mMDResolveHandle.reset();
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (mScrapeResult.mediaFilesDownloadStatus == COMPLETED &&
|
2021-07-07 18:03:42 +00:00
|
|
|
Settings::getInstance()->getBool("MiximageGenerate")) {
|
2021-06-07 21:02:42 +00:00
|
|
|
std::string currentMiximage = mLastSearch.game->getMiximagePath();
|
2021-07-07 18:03:42 +00:00
|
|
|
if (currentMiximage == "" ||
|
|
|
|
(currentMiximage != "" &&
|
|
|
|
Settings::getInstance()->getBool("MiximageOverwrite"))) {
|
2021-06-07 21:02:42 +00:00
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
mMiximageGenerator =
|
|
|
|
std::make_unique<MiximageGenerator>(mLastSearch.game, mResultMessage);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
// The promise/future mechanism is used as signaling for the thread to
|
|
|
|
// indicate that processing has been completed. The reason to run a separate
|
|
|
|
// thread is that the busy animation will then be played and that the user
|
|
|
|
// interface does not become completely unresponsive during the miximage
|
|
|
|
// generation.
|
|
|
|
std::promise<bool>().swap(mGeneratorPromise);
|
|
|
|
mGeneratorFuture = mGeneratorPromise.get_future();
|
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
mMiximageGeneratorThread =
|
|
|
|
std::thread(&MiximageGenerator::startThread, mMiximageGenerator.get(),
|
|
|
|
&mGeneratorPromise);
|
2021-06-07 21:02:42 +00:00
|
|
|
}
|
2021-06-07 22:43:14 +00:00
|
|
|
else {
|
|
|
|
returnResult(mScrapeResult);
|
|
|
|
}
|
2021-06-07 21:02:42 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
returnResult(mScrapeResult);
|
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
else if (mMDResolveHandle->status() == ASYNC_ERROR) {
|
|
|
|
onSearchError(mMDResolveHandle->getStatusString());
|
|
|
|
mMDResolveHandle.reset();
|
|
|
|
}
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
void GuiScraperSearch::updateThumbnail()
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-11-14 19:46:08 +00:00
|
|
|
auto it = mThumbnailReqMap.find(mScraperResults[mResultList->getCursorId()].thumbnailImageUrl);
|
|
|
|
|
|
|
|
if (it != mThumbnailReqMap.end() && it->second->status() == HttpReq::REQ_SUCCESS) {
|
2020-06-21 12:25:28 +00:00
|
|
|
// Save thumbnail to mScraperResults cache and set the flag that the
|
|
|
|
// thumbnail download has been completed for this game.
|
2020-11-14 19:46:08 +00:00
|
|
|
if (mScraperResults[mResultList->getCursorId()].thumbnailDownloadStatus == IN_PROGRESS) {
|
|
|
|
mScraperResults[mResultList->getCursorId()].thumbnailImageData =
|
2021-07-07 18:03:42 +00:00
|
|
|
it->second->getContent();
|
2020-11-14 19:46:08 +00:00
|
|
|
mScraperResults[mResultList->getCursorId()].thumbnailDownloadStatus = COMPLETED;
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
// Activate the thumbnail in the GUI.
|
2020-11-14 19:46:08 +00:00
|
|
|
std::string content = mScraperResults[mResultList->getCursorId()].thumbnailImageData;
|
|
|
|
if (content.size() > 0) {
|
|
|
|
mResultThumbnail->setImage(content.data(), content.length());
|
|
|
|
mGrid.onSizeChanged(); // A hack to fix the thumbnail position since its size changed.
|
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
mResultThumbnail->setImage("");
|
2021-05-24 16:51:16 +00:00
|
|
|
onSearchError("Error downloading thumbnail:\n " + it->second->getErrorMsg(),
|
2021-07-07 18:03:42 +00:00
|
|
|
it->second->status());
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
|
2020-11-14 19:46:08 +00:00
|
|
|
mThumbnailReqMap.erase(it);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
// When the thumbnail has been downloaded and we are in automatic mode, or if
|
|
|
|
// we are in semi-automatic mode with a single matching game result, we proceed
|
|
|
|
// to immediately download the rest of the media files.
|
|
|
|
if ((mSearchType == ALWAYS_ACCEPT_FIRST_RESULT ||
|
2021-07-07 18:03:42 +00:00
|
|
|
(mSearchType == ACCEPT_SINGLE_MATCHES && mScraperResults.size() == 1 &&
|
|
|
|
mRefinedSearch == false)) &&
|
|
|
|
mScraperResults.front().thumbnailDownloadStatus == COMPLETED) {
|
2020-12-18 15:35:19 +00:00
|
|
|
mRefinedSearch = false;
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mScraperResults.size() == 0)
|
|
|
|
mSkipCallback();
|
|
|
|
else
|
|
|
|
returnResult(mScraperResults.front());
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
void GuiScraperSearch::openInputScreen(ScraperSearchParams& params)
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
auto searchForFunc = [&](const std::string& name) {
|
2021-07-11 20:30:23 +00:00
|
|
|
stop();
|
2020-12-18 15:35:19 +00:00
|
|
|
mRefinedSearch = true;
|
2020-06-21 12:25:28 +00:00
|
|
|
params.nameOverride = name;
|
|
|
|
search(params);
|
|
|
|
};
|
|
|
|
|
2021-05-24 16:51:16 +00:00
|
|
|
mRetryCount = 0;
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2020-10-10 11:05:12 +00:00
|
|
|
std::string searchString;
|
|
|
|
|
|
|
|
if (params.nameOverride.empty()) {
|
|
|
|
// If the setting to search based on metadata name has been set, then show this string
|
|
|
|
// regardless of whether the entry is an arcade game and TheGamesDB is used.
|
|
|
|
if (Settings::getInstance()->getBool("ScraperSearchMetadataName")) {
|
2020-11-14 14:30:49 +00:00
|
|
|
searchString = Utils::String::removeParenthesis(params.game->metadata.get("name"));
|
2020-10-10 11:05:12 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// If searching based on the actual file name, then expand to the full game name
|
2021-07-11 20:26:53 +00:00
|
|
|
// in case the scraper is set to TheGamesDB and it's an arcade game. This is
|
|
|
|
// required as TheGamesDB does not support searches using the short MAME names.
|
2020-12-16 17:46:38 +00:00
|
|
|
if (params.game->isArcadeGame() &&
|
2021-07-07 18:03:42 +00:00
|
|
|
Settings::getInstance()->getString("Scraper") == "thegamesdb")
|
2020-10-10 11:05:12 +00:00
|
|
|
searchString = MameNames::getInstance()->getCleanName(params.game->getCleanName());
|
|
|
|
else
|
|
|
|
searchString = params.game->getCleanName();
|
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
else {
|
2020-10-10 11:05:12 +00:00
|
|
|
searchString = params.nameOverride;
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
2020-10-10 11:05:12 +00:00
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "REFINE SEARCH", searchString,
|
|
|
|
searchForFunc, false, "SEARCH", "APPLY CHANGES?"));
|
2020-06-06 11:10:33 +00:00
|
|
|
}
|
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
bool GuiScraperSearch::saveMetadata(const ScraperSearchResult& result,
|
|
|
|
MetaDataList& metadata,
|
|
|
|
FileData* scrapedGame)
|
2020-06-06 11:10:33 +00:00
|
|
|
{
|
2021-01-26 16:40:37 +00:00
|
|
|
bool metadataUpdated = false;
|
|
|
|
bool hasDefaultName = false;
|
2020-06-21 12:25:28 +00:00
|
|
|
std::vector<MetaDataDecl> mMetaDataDecl = metadata.getMDD();
|
2021-01-26 16:40:37 +00:00
|
|
|
std::string defaultName;
|
|
|
|
|
|
|
|
// Get the default name, which is either the MAME name or the name of the physical file
|
|
|
|
// or directory.
|
|
|
|
if (scrapedGame->isArcadeGame())
|
|
|
|
defaultName = MameNames::getInstance()->getCleanName(scrapedGame->getCleanName());
|
|
|
|
else
|
|
|
|
defaultName = Utils::FileSystem::getStem(scrapedGame->getFileName());
|
|
|
|
|
|
|
|
// We want the comparison to be case sensitive.
|
|
|
|
if (defaultName == metadata.get("name"))
|
|
|
|
hasDefaultName = true;
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
for (unsigned int i = 0; i < mMetaDataDecl.size(); i++) {
|
|
|
|
|
|
|
|
// Skip elements that are tagged not to be scraped.
|
|
|
|
if (!mMetaDataDecl.at(i).shouldScrape)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const std::string& key = mMetaDataDecl.at(i).key;
|
|
|
|
|
|
|
|
// Skip element if the setting to not scrape metadata has been set,
|
|
|
|
// unless its type is rating or name.
|
|
|
|
if (!Settings::getInstance()->getBool("ScrapeMetadata") &&
|
2021-07-07 18:03:42 +00:00
|
|
|
(key != "rating" && key != "name"))
|
2020-06-21 12:25:28 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
// Skip saving of rating if the corresponding option has been set to false.
|
|
|
|
if (key == "rating" && !Settings::getInstance()->getBool("ScrapeRatings"))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Skip saving of game name if the corresponding option has been set to false.
|
|
|
|
if (key == "name" && !Settings::getInstance()->getBool("ScrapeGameNames"))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Skip elements that are empty.
|
|
|
|
if (result.mdl.get(key) == "")
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Skip elements that are the same as the default metadata value.
|
|
|
|
if (result.mdl.get(key) == mMetaDataDecl.at(i).defaultValue)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Skip elements that are identical to the existing value.
|
|
|
|
if (result.mdl.get(key) == metadata.get(key))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Make sure to set releasedate to the proper default value.
|
|
|
|
if (key == "releasedate" && metadata.get(key) == "19700101T010000")
|
|
|
|
metadata.set(key, mMetaDataDecl.at(i).defaultValue);
|
|
|
|
|
|
|
|
// Overwrite all the other values if the flag to overwrite data has been set.
|
|
|
|
if (Settings::getInstance()->getBool("ScraperOverwriteData")) {
|
|
|
|
metadata.set(key, result.mdl.get(key));
|
2021-01-26 16:40:37 +00:00
|
|
|
metadataUpdated = true;
|
|
|
|
}
|
|
|
|
// If the key is the game name and it's set to its default value, then update.
|
|
|
|
else if (key == "name" && hasDefaultName) {
|
|
|
|
metadata.set(key, result.mdl.get(key));
|
|
|
|
metadataUpdated = true;
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
// Else only update the value if it is set to the default metadata value.
|
|
|
|
else if (metadata.get(key) == mMetaDataDecl.at(i).defaultValue) {
|
|
|
|
metadata.set(key, result.mdl.get(key));
|
2021-01-26 16:40:37 +00:00
|
|
|
metadataUpdated = true;
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
2020-07-30 12:54:52 +00:00
|
|
|
|
|
|
|
// For the description, expand any escaped HTML quotation marks to literal
|
|
|
|
// quotation marks.
|
2021-01-26 16:40:37 +00:00
|
|
|
if (key == "desc" && metadataUpdated)
|
2020-07-30 12:54:52 +00:00
|
|
|
metadata.set(key, Utils::String::replace(metadata.get(key), """, "\""));
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
|
2021-01-26 16:40:37 +00:00
|
|
|
return metadataUpdated;
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:14:13 +00:00
|
|
|
std::vector<HelpPrompt> GuiScraperSearch::getHelpPrompts()
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
std::vector<HelpPrompt> prompts;
|
2020-06-09 18:03:31 +00:00
|
|
|
|
2020-12-18 15:35:19 +00:00
|
|
|
prompts.push_back(HelpPrompt("y", "refine search"));
|
|
|
|
if (mScrapeCount > 1)
|
|
|
|
prompts.push_back(HelpPrompt("x", "skip"));
|
2021-01-06 20:57:39 +00:00
|
|
|
if (mFoundGame && (mRefinedSearch || mSearchType != ACCEPT_SINGLE_MATCHES ||
|
2021-07-07 18:03:42 +00:00
|
|
|
(mSearchType == ACCEPT_SINGLE_MATCHES && mScraperResults.size() > 1)))
|
2020-06-21 12:25:28 +00:00
|
|
|
prompts.push_back(HelpPrompt("a", "accept result"));
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return prompts;
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-07 18:09:02 +00:00
|
|
|
HelpStyle GuiScraperSearch::getHelpStyle()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
HelpStyle style = HelpStyle();
|
|
|
|
style.applyTheme(ViewController::get()->getState().getSystem()->getTheme(), "system");
|
|
|
|
return style;
|
2020-06-07 18:09:02 +00:00
|
|
|
}
|