mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-25 15:45:38 +00:00
Large code cleanup and code documentation update, mostly related to the scraper. Only cosmetic changes in this commit.
This commit is contained in:
parent
da946279de
commit
fd7da08bf9
|
@ -1,3 +1,9 @@
|
||||||
|
//
|
||||||
|
// AsyncReqComponent.cpp
|
||||||
|
//
|
||||||
|
// Deprecated, not in use any longer?
|
||||||
|
//
|
||||||
|
|
||||||
#include "components/AsyncReqComponent.h"
|
#include "components/AsyncReqComponent.h"
|
||||||
|
|
||||||
#include "renderers/Renderer.h"
|
#include "renderers/Renderer.h"
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
//
|
||||||
|
// AsyncReqComponent.h
|
||||||
|
//
|
||||||
|
// Deprecated, not in use any longer?
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_APP_COMPONENTS_ASYNC_REQ_COMPONENT_H
|
#ifndef ES_APP_COMPONENTS_ASYNC_REQ_COMPONENT_H
|
||||||
#define ES_APP_COMPONENTS_ASYNC_REQ_COMPONENT_H
|
#define ES_APP_COMPONENTS_ASYNC_REQ_COMPONENT_H
|
||||||
|
|
|
@ -1,3 +1,15 @@
|
||||||
|
//
|
||||||
|
// ScraperSearchComponent.cpp
|
||||||
|
//
|
||||||
|
// User interface component 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 GuiScraperStart.
|
||||||
|
//
|
||||||
|
// This component is called from GuiGameScraper for single-game scraping and
|
||||||
|
// from GuiScraperMulti for multi-game scraping.
|
||||||
|
//
|
||||||
|
|
||||||
#include "components/ScraperSearchComponent.h"
|
#include "components/ScraperSearchComponent.h"
|
||||||
|
|
||||||
#include "components/ComponentList.h"
|
#include "components/ComponentList.h"
|
||||||
|
@ -14,32 +26,39 @@
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "Window.h"
|
#include "Window.h"
|
||||||
|
|
||||||
ScraperSearchComponent::ScraperSearchComponent(Window* window, SearchType type) : GuiComponent(window),
|
ScraperSearchComponent::ScraperSearchComponent(
|
||||||
mGrid(window, Vector2i(4, 3)), mBusyAnim(window),
|
Window* window,
|
||||||
mSearchType(type)
|
SearchType type)
|
||||||
|
: GuiComponent(window),
|
||||||
|
mGrid(window, Vector2i(4, 3)),
|
||||||
|
mBusyAnim(window),
|
||||||
|
mSearchType(type)
|
||||||
{
|
{
|
||||||
addChild(&mGrid);
|
addChild(&mGrid);
|
||||||
|
|
||||||
mBlockAccept = false;
|
mBlockAccept = false;
|
||||||
|
|
||||||
// left spacer (empty component, needed for borders)
|
// Left spacer (empty component, needed for borders).
|
||||||
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 0), false, false, Vector2i(1, 3), GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
|
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 0),
|
||||||
|
false, false, Vector2i(1, 3), GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
|
||||||
|
|
||||||
// selected result name
|
// Selected result name.
|
||||||
mResultName = std::make_shared<TextComponent>(mWindow, "Result name", Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
|
mResultName = std::make_shared<TextComponent>(mWindow, "Result name",
|
||||||
|
Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
|
||||||
|
|
||||||
// selected result thumbnail
|
// Selected result thumbnail.
|
||||||
mResultThumbnail = std::make_shared<ImageComponent>(mWindow);
|
mResultThumbnail = std::make_shared<ImageComponent>(mWindow);
|
||||||
mGrid.setEntry(mResultThumbnail, Vector2i(1, 1), false, false, Vector2i(1, 1));
|
mGrid.setEntry(mResultThumbnail, Vector2i(1, 1), false, false, Vector2i(1, 1));
|
||||||
|
|
||||||
// selected result desc + container
|
// Selected result description and container.
|
||||||
mDescContainer = std::make_shared<ScrollableContainer>(mWindow);
|
mDescContainer = std::make_shared<ScrollableContainer>(mWindow);
|
||||||
mResultDesc = std::make_shared<TextComponent>(mWindow, "Result desc", Font::get(FONT_SIZE_SMALL), 0x777777FF);
|
mResultDesc = std::make_shared<TextComponent>(mWindow, "Result desc",
|
||||||
|
Font::get(FONT_SIZE_SMALL), 0x777777FF);
|
||||||
mDescContainer->addChild(mResultDesc.get());
|
mDescContainer->addChild(mResultDesc.get());
|
||||||
mDescContainer->setAutoScroll(true);
|
mDescContainer->setAutoScroll(true);
|
||||||
|
|
||||||
// metadata
|
// Metadata.
|
||||||
auto font = Font::get(FONT_SIZE_SMALL); // this gets replaced in onSizeChanged() so its just a placeholder
|
auto font = Font::get(FONT_SIZE_SMALL); // Placeholder, gets replaced in onSizeChanged().
|
||||||
const unsigned int mdColor = 0x777777FF;
|
const unsigned int mdColor = 0x777777FF;
|
||||||
const unsigned int mdLblColor = 0x666666FF;
|
const unsigned int mdLblColor = 0x666666FF;
|
||||||
mMD_Rating = std::make_shared<RatingComponent>(mWindow);
|
mMD_Rating = std::make_shared<RatingComponent>(mWindow);
|
||||||
|
@ -50,17 +69,23 @@ ScraperSearchComponent::ScraperSearchComponent(Window* window, SearchType type)
|
||||||
mMD_Genre = std::make_shared<TextComponent>(mWindow, "", font, mdColor);
|
mMD_Genre = std::make_shared<TextComponent>(mWindow, "", font, mdColor);
|
||||||
mMD_Players = std::make_shared<TextComponent>(mWindow, "", font, mdColor);
|
mMD_Players = std::make_shared<TextComponent>(mWindow, "", font, mdColor);
|
||||||
|
|
||||||
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>
|
||||||
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>(mWindow, "RELEASED:", font, mdLblColor), mMD_ReleaseDate));
|
(mWindow, "RATING:", font, mdLblColor), mMD_Rating, false));
|
||||||
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>(mWindow, "DEVELOPER:", font, mdLblColor), mMD_Developer));
|
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>
|
||||||
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>(mWindow, "PUBLISHER:", font, mdLblColor), mMD_Publisher));
|
(mWindow, "RELEASED:", font, mdLblColor), mMD_ReleaseDate));
|
||||||
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>(mWindow, "GENRE:", font, mdLblColor), mMD_Genre));
|
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>
|
||||||
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>(mWindow, "PLAYERS:", font, mdLblColor), mMD_Players));
|
(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));
|
||||||
|
|
||||||
mMD_Grid = std::make_shared<ComponentGrid>(mWindow, Vector2i(2, (int)mMD_Pairs.size()*2 - 1));
|
mMD_Grid = std::make_shared<ComponentGrid>(mWindow,
|
||||||
|
Vector2i(2, (int)mMD_Pairs.size()*2 - 1));
|
||||||
unsigned int i = 0;
|
unsigned int i = 0;
|
||||||
for(auto it = mMD_Pairs.cbegin(); it != mMD_Pairs.cend(); it++)
|
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->first, Vector2i(0, i), false, true);
|
||||||
mMD_Grid->setEntry(it->second, Vector2i(1, i), false, it->resize);
|
mMD_Grid->setEntry(it->second, Vector2i(1, i), false, it->resize);
|
||||||
i += 2;
|
i += 2;
|
||||||
|
@ -68,9 +93,10 @@ ScraperSearchComponent::ScraperSearchComponent(Window* window, SearchType type)
|
||||||
|
|
||||||
mGrid.setEntry(mMD_Grid, Vector2i(2, 1), false, false);
|
mGrid.setEntry(mMD_Grid, Vector2i(2, 1), false, false);
|
||||||
|
|
||||||
// result list
|
// Result list.
|
||||||
mResultList = std::make_shared<ComponentList>(mWindow);
|
mResultList = std::make_shared<ComponentList>(mWindow);
|
||||||
mResultList->setCursorChangedCallback([this](CursorState state) { if(state == CURSOR_STOPPED) updateInfoPane(); });
|
mResultList->setCursorChangedCallback([this](CursorState state) {
|
||||||
|
if (state == CURSOR_STOPPED) updateInfoPane(); });
|
||||||
|
|
||||||
updateViewStyle();
|
updateViewStyle();
|
||||||
}
|
}
|
||||||
|
@ -79,46 +105,49 @@ void ScraperSearchComponent::onSizeChanged()
|
||||||
{
|
{
|
||||||
mGrid.setSize(mSize);
|
mGrid.setSize(mSize);
|
||||||
|
|
||||||
if(mSize.x() == 0 || mSize.y() == 0)
|
if (mSize.x() == 0 || mSize.y() == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// column widths
|
// Column widths.
|
||||||
if(mSearchType == ALWAYS_ACCEPT_FIRST_RESULT)
|
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT)
|
||||||
mGrid.setColWidthPerc(0, 0.02f); // looks better when this is higher in auto mode
|
mGrid.setColWidthPerc(0, 0.02f); // Looks better when this is higher in auto mode.
|
||||||
else
|
else
|
||||||
mGrid.setColWidthPerc(0, 0.01f);
|
mGrid.setColWidthPerc(0, 0.01f);
|
||||||
|
|
||||||
mGrid.setColWidthPerc(1, 0.25f);
|
mGrid.setColWidthPerc(1, 0.25f);
|
||||||
mGrid.setColWidthPerc(2, 0.25f);
|
mGrid.setColWidthPerc(2, 0.25f);
|
||||||
|
|
||||||
// row heights
|
// Row heights.
|
||||||
if(mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) // show name
|
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) // Show name.
|
||||||
mGrid.setRowHeightPerc(0, (mResultName->getFont()->getHeight() * 1.6f) / mGrid.getSize().y()); // result name
|
mGrid.setRowHeightPerc(0, (mResultName->getFont()->getHeight() * 1.6f) /
|
||||||
|
mGrid.getSize().y()); // Result name.
|
||||||
else
|
else
|
||||||
mGrid.setRowHeightPerc(0, 0.0825f); // hide name but do padding
|
mGrid.setRowHeightPerc(0, 0.0825f); // Hide name but do padding.
|
||||||
|
|
||||||
if(mSearchType == ALWAYS_ACCEPT_FIRST_RESULT)
|
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT)
|
||||||
{
|
|
||||||
mGrid.setRowHeightPerc(2, 0.2f);
|
mGrid.setRowHeightPerc(2, 0.2f);
|
||||||
}else{
|
else
|
||||||
mGrid.setRowHeightPerc(1, 0.505f);
|
mGrid.setRowHeightPerc(1, 0.505f);
|
||||||
}
|
|
||||||
|
|
||||||
const float boxartCellScale = 0.9f;
|
const float boxartCellScale = 0.9f;
|
||||||
|
|
||||||
// limit thumbnail size using setMaxHeight - we do this instead of letting mGrid call setSize because it maintains the aspect ratio
|
// Limit thumbnail size using setMaxHeight - we do this instead of letting mGrid
|
||||||
// we also pad a little so it doesn't rub up against the metadata labels
|
// 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));
|
mResultThumbnail->setMaxSize(mGrid.getColWidth(1) * boxartCellScale, mGrid.getRowHeight(1));
|
||||||
|
|
||||||
// metadata
|
// Metadata.
|
||||||
resizeMetadata();
|
resizeMetadata();
|
||||||
|
|
||||||
if(mSearchType != ALWAYS_ACCEPT_FIRST_RESULT)
|
if (mSearchType != ALWAYS_ACCEPT_FIRST_RESULT)
|
||||||
mDescContainer->setSize(mGrid.getColWidth(1)*boxartCellScale + mGrid.getColWidth(2), mResultDesc->getFont()->getHeight() * 3);
|
mDescContainer->setSize(mGrid.getColWidth(1)*boxartCellScale +
|
||||||
|
mGrid.getColWidth(2), mResultDesc->getFont()->getHeight() * 3);
|
||||||
else
|
else
|
||||||
mDescContainer->setSize(mGrid.getColWidth(3)*boxartCellScale, mResultDesc->getFont()->getHeight() * 8);
|
mDescContainer->setSize(mGrid.getColWidth(3)*boxartCellScale,
|
||||||
|
mResultDesc->getFont()->getHeight() * 8);
|
||||||
|
|
||||||
mResultDesc->setSize(mDescContainer->getSize().x(), 0); // make desc text wrap at edge of container
|
// Make description text wrap at edge of container.
|
||||||
|
mResultDesc->setSize(mDescContainer->getSize().x(), 0);
|
||||||
|
|
||||||
mGrid.onSizeChanged();
|
mGrid.onSizeChanged();
|
||||||
|
|
||||||
|
@ -128,28 +157,25 @@ void ScraperSearchComponent::onSizeChanged()
|
||||||
void ScraperSearchComponent::resizeMetadata()
|
void ScraperSearchComponent::resizeMetadata()
|
||||||
{
|
{
|
||||||
mMD_Grid->setSize(mGrid.getColWidth(2), mGrid.getRowHeight(1));
|
mMD_Grid->setSize(mGrid.getColWidth(2), mGrid.getRowHeight(1));
|
||||||
if(mMD_Grid->getSize().y() > mMD_Pairs.size())
|
if (mMD_Grid->getSize().y() > mMD_Pairs.size()) {
|
||||||
{
|
|
||||||
const int fontHeight = (int)(mMD_Grid->getSize().y() / mMD_Pairs.size() * 0.8f);
|
const int fontHeight = (int)(mMD_Grid->getSize().y() / mMD_Pairs.size() * 0.8f);
|
||||||
auto fontLbl = Font::get(fontHeight, FONT_PATH_REGULAR);
|
auto fontLbl = Font::get(fontHeight, FONT_PATH_REGULAR);
|
||||||
auto fontComp = Font::get(fontHeight, FONT_PATH_LIGHT);
|
auto fontComp = Font::get(fontHeight, FONT_PATH_LIGHT);
|
||||||
|
|
||||||
// update label fonts
|
// Update label fonts.
|
||||||
float maxLblWidth = 0;
|
float maxLblWidth = 0;
|
||||||
for(auto it = mMD_Pairs.cbegin(); it != mMD_Pairs.cend(); it++)
|
for (auto it = mMD_Pairs.cbegin(); it != mMD_Pairs.cend(); it++) {
|
||||||
{
|
|
||||||
it->first->setFont(fontLbl);
|
it->first->setFont(fontLbl);
|
||||||
it->first->setSize(0, 0);
|
it->first->setSize(0, 0);
|
||||||
if(it->first->getSize().x() > maxLblWidth)
|
if (it->first->getSize().x() > maxLblWidth)
|
||||||
maxLblWidth = it->first->getSize().x() + 6;
|
maxLblWidth = it->first->getSize().x() + 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(unsigned int i = 0; i < mMD_Pairs.size(); i++)
|
for (unsigned int i = 0; i < mMD_Pairs.size(); i++)
|
||||||
{
|
mMD_Grid->setRowHeightPerc(i*2, (fontLbl->getLetterHeight() + 2) /
|
||||||
mMD_Grid->setRowHeightPerc(i*2, (fontLbl->getLetterHeight() + 2) / mMD_Grid->getSize().y());
|
mMD_Grid->getSize().y());
|
||||||
}
|
|
||||||
|
|
||||||
// update component fonts
|
// Update component fonts.
|
||||||
mMD_ReleaseDate->setFont(fontComp);
|
mMD_ReleaseDate->setFont(fontComp);
|
||||||
mMD_Developer->setFont(fontComp);
|
mMD_Developer->setFont(fontComp);
|
||||||
mMD_Publisher->setFont(fontComp);
|
mMD_Publisher->setFont(fontComp);
|
||||||
|
@ -158,44 +184,52 @@ void ScraperSearchComponent::resizeMetadata()
|
||||||
|
|
||||||
mMD_Grid->setColWidthPerc(0, maxLblWidth / mMD_Grid->getSize().x());
|
mMD_Grid->setColWidthPerc(0, maxLblWidth / mMD_Grid->getSize().x());
|
||||||
|
|
||||||
// rating is manually sized
|
// Rating is manually sized.
|
||||||
mMD_Rating->setSize(mMD_Grid->getColWidth(1), fontLbl->getHeight() * 0.65f);
|
mMD_Rating->setSize(mMD_Grid->getColWidth(1), fontLbl->getHeight() * 0.65f);
|
||||||
mMD_Grid->onSizeChanged();
|
mMD_Grid->onSizeChanged();
|
||||||
|
|
||||||
// make result font follow label font
|
// Make result font follow label font.
|
||||||
mResultDesc->setFont(Font::get(fontHeight, FONT_PATH_REGULAR));
|
mResultDesc->setFont(Font::get(fontHeight, FONT_PATH_REGULAR));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScraperSearchComponent::updateViewStyle()
|
void ScraperSearchComponent::updateViewStyle()
|
||||||
{
|
{
|
||||||
// unlink description and result list and result name
|
// Unlink description, result list and result name.
|
||||||
mGrid.removeEntry(mResultName);
|
mGrid.removeEntry(mResultName);
|
||||||
mGrid.removeEntry(mResultDesc);
|
mGrid.removeEntry(mResultDesc);
|
||||||
mGrid.removeEntry(mResultList);
|
mGrid.removeEntry(mResultList);
|
||||||
|
|
||||||
// add them back depending on search type
|
// Add them back depending on search type.
|
||||||
if(mSearchType == ALWAYS_ACCEPT_FIRST_RESULT)
|
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) {
|
||||||
{
|
// Show name.
|
||||||
// show name
|
mGrid.setEntry(mResultName, Vector2i(1, 0), false, true, Vector2i(2, 1),
|
||||||
mGrid.setEntry(mResultName, Vector2i(1, 0), false, true, Vector2i(2, 1), GridFlags::BORDER_TOP);
|
GridFlags::BORDER_TOP);
|
||||||
|
|
||||||
// need a border on the bottom left
|
// Need a border on the bottom left.
|
||||||
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 2), false, false, Vector2i(3, 1), GridFlags::BORDER_BOTTOM);
|
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 2),
|
||||||
|
false, false, Vector2i(3, 1), GridFlags::BORDER_BOTTOM);
|
||||||
|
|
||||||
// show description on the right
|
// Show description on the right.
|
||||||
mGrid.setEntry(mDescContainer, Vector2i(3, 0), false, false, Vector2i(1, 3), GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
|
mGrid.setEntry(mDescContainer, Vector2i(3, 0), false, false, Vector2i(1, 3),
|
||||||
mResultDesc->setSize(mDescContainer->getSize().x(), 0); // make desc text wrap at edge of container
|
GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
|
||||||
}else{
|
// Make description text wrap at edge of container.
|
||||||
// fake row where name would be
|
mResultDesc->setSize(mDescContainer->getSize().x(), 0);
|
||||||
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 0), false, true, Vector2i(2, 1), GridFlags::BORDER_TOP);
|
}
|
||||||
|
else {
|
||||||
|
// Fake row where name would be.
|
||||||
|
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 0),
|
||||||
|
false, true, Vector2i(2, 1), GridFlags::BORDER_TOP);
|
||||||
|
|
||||||
// show result list on the right
|
// Show result list on the right.
|
||||||
mGrid.setEntry(mResultList, Vector2i(3, 0), true, true, Vector2i(1, 3), GridFlags::BORDER_LEFT | GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
|
mGrid.setEntry(mResultList, Vector2i(3, 0), true, true, Vector2i(1, 3),
|
||||||
|
GridFlags::BORDER_LEFT | GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
|
||||||
|
|
||||||
// show description under image/info
|
// Show description under image/info.
|
||||||
mGrid.setEntry(mDescContainer, Vector2i(1, 2), false, false, Vector2i(2, 1), GridFlags::BORDER_BOTTOM);
|
mGrid.setEntry(mDescContainer, Vector2i(1, 2), false, false, Vector2i(2, 1),
|
||||||
mResultDesc->setSize(mDescContainer->getSize().x(), 0); // make desc text wrap at edge of container
|
GridFlags::BORDER_BOTTOM);
|
||||||
|
// Make description text wrap at edge of container.
|
||||||
|
mResultDesc->setSize(mDescContainer->getSize().x(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,31 +263,31 @@ void ScraperSearchComponent::onSearchDone(const std::vector<ScraperSearchResult>
|
||||||
|
|
||||||
auto font = Font::get(FONT_SIZE_MEDIUM);
|
auto font = Font::get(FONT_SIZE_MEDIUM);
|
||||||
unsigned int color = 0x777777FF;
|
unsigned int color = 0x777777FF;
|
||||||
if(results.empty())
|
if (results.empty()) {
|
||||||
{
|
// Check if the scraper used is still valid.
|
||||||
// Check if the scraper used is still valid
|
if (!isValidConfiguredScraper()) {
|
||||||
if (!isValidConfiguredScraper())
|
mWindow->pushGui(new GuiMsgBox(mWindow, Utils::String::toUpper("Configured scraper "
|
||||||
{
|
"is no longer available.\nPlease change the scraping source in the settings."),
|
||||||
mWindow->pushGui(new GuiMsgBox(mWindow, Utils::String::toUpper("Configured scraper is no longer available.\nPlease change the scraping source in the settings."),
|
|
||||||
"FINISH", mSkipCallback));
|
"FINISH", mSkipCallback));
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
ComponentListRow row;
|
ComponentListRow row;
|
||||||
row.addElement(std::make_shared<TextComponent>(mWindow, "NO GAMES FOUND - SKIP", font, color), true);
|
row.addElement(std::make_shared<TextComponent>(mWindow, "NO GAMES FOUND - SKIP",
|
||||||
|
font, color), true);
|
||||||
|
|
||||||
if(mSkipCallback)
|
if (mSkipCallback)
|
||||||
row.makeAcceptInputHandler(mSkipCallback);
|
row.makeAcceptInputHandler(mSkipCallback);
|
||||||
|
|
||||||
mResultList->addRow(row);
|
mResultList->addRow(row);
|
||||||
mGrid.resetCursor();
|
mGrid.resetCursor();
|
||||||
}
|
}
|
||||||
}else{
|
}
|
||||||
|
else {
|
||||||
ComponentListRow row;
|
ComponentListRow row;
|
||||||
for(size_t i = 0; i < results.size(); i++)
|
for (size_t i = 0; i < results.size(); i++) {
|
||||||
{
|
|
||||||
row.elements.clear();
|
row.elements.clear();
|
||||||
row.addElement(std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(results.at(i).mdl.get("name")), font, color), true);
|
row.addElement(std::make_shared<TextComponent>(mWindow,
|
||||||
|
Utils::String::toUpper(results.at(i).mdl.get("name")), font, color), true);
|
||||||
row.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); });
|
row.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); });
|
||||||
mResultList->addRow(row);
|
mResultList->addRow(row);
|
||||||
}
|
}
|
||||||
|
@ -263,14 +297,13 @@ void ScraperSearchComponent::onSearchDone(const std::vector<ScraperSearchResult>
|
||||||
mBlockAccept = false;
|
mBlockAccept = false;
|
||||||
updateInfoPane();
|
updateInfoPane();
|
||||||
|
|
||||||
if(mSearchType == ALWAYS_ACCEPT_FIRST_RESULT)
|
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) {
|
||||||
{
|
if (mScraperResults.size() == 0)
|
||||||
if(mScraperResults.size() == 0)
|
|
||||||
mSkipCallback();
|
mSkipCallback();
|
||||||
else
|
else
|
||||||
returnResult(mScraperResults.front());
|
returnResult(mScraperResults.front());
|
||||||
}else if(mSearchType == ALWAYS_ACCEPT_MATCHING_CRC)
|
}
|
||||||
{
|
else if (mSearchType == ALWAYS_ACCEPT_MATCHING_CRC) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,7 +319,7 @@ void ScraperSearchComponent::onSearchError(const std::string& error)
|
||||||
|
|
||||||
int ScraperSearchComponent::getSelectedIndex()
|
int ScraperSearchComponent::getSelectedIndex()
|
||||||
{
|
{
|
||||||
if(!mScraperResults.size() || mGrid.getSelectedComponent() != mResultList)
|
if (!mScraperResults.size() || mGrid.getSelectedComponent() != mResultList)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return mResultList->getCursorId();
|
return mResultList->getCursorId();
|
||||||
|
@ -295,13 +328,10 @@ int ScraperSearchComponent::getSelectedIndex()
|
||||||
void ScraperSearchComponent::updateInfoPane()
|
void ScraperSearchComponent::updateInfoPane()
|
||||||
{
|
{
|
||||||
int i = getSelectedIndex();
|
int i = getSelectedIndex();
|
||||||
if(mSearchType == ALWAYS_ACCEPT_FIRST_RESULT && mScraperResults.size())
|
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT && mScraperResults.size())
|
||||||
{
|
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
|
||||||
|
|
||||||
if(i != -1 && (int)mScraperResults.size() > i)
|
if (i != -1 && (int)mScraperResults.size() > i) {
|
||||||
{
|
|
||||||
ScraperSearchResult& res = mScraperResults.at(i);
|
ScraperSearchResult& res = mScraperResults.at(i);
|
||||||
mResultName->setText(Utils::String::toUpper(res.mdl.get("name")));
|
mResultName->setText(Utils::String::toUpper(res.mdl.get("name")));
|
||||||
mResultDesc->setText(Utils::String::toUpper(res.mdl.get("desc")));
|
mResultDesc->setText(Utils::String::toUpper(res.mdl.get("desc")));
|
||||||
|
@ -309,14 +339,12 @@ void ScraperSearchComponent::updateInfoPane()
|
||||||
|
|
||||||
mResultThumbnail->setImage("");
|
mResultThumbnail->setImage("");
|
||||||
const std::string& thumb = res.thumbnailUrl.empty() ? res.imageUrl : res.thumbnailUrl;
|
const std::string& thumb = res.thumbnailUrl.empty() ? res.imageUrl : res.thumbnailUrl;
|
||||||
if(!thumb.empty())
|
if (!thumb.empty())
|
||||||
{
|
|
||||||
mThumbnailReq = std::unique_ptr<HttpReq>(new HttpReq(thumb));
|
mThumbnailReq = std::unique_ptr<HttpReq>(new HttpReq(thumb));
|
||||||
}else{
|
else
|
||||||
mThumbnailReq.reset();
|
mThumbnailReq.reset();
|
||||||
}
|
|
||||||
|
|
||||||
// metadata
|
// Metadata.
|
||||||
mMD_Rating->setValue(Utils::String::toUpper(res.mdl.get("rating")));
|
mMD_Rating->setValue(Utils::String::toUpper(res.mdl.get("rating")));
|
||||||
mMD_ReleaseDate->setValue(Utils::String::toUpper(res.mdl.get("releasedate")));
|
mMD_ReleaseDate->setValue(Utils::String::toUpper(res.mdl.get("releasedate")));
|
||||||
mMD_Developer->setText(Utils::String::toUpper(res.mdl.get("developer")));
|
mMD_Developer->setText(Utils::String::toUpper(res.mdl.get("developer")));
|
||||||
|
@ -324,12 +352,13 @@ void ScraperSearchComponent::updateInfoPane()
|
||||||
mMD_Genre->setText(Utils::String::toUpper(res.mdl.get("genre")));
|
mMD_Genre->setText(Utils::String::toUpper(res.mdl.get("genre")));
|
||||||
mMD_Players->setText(Utils::String::toUpper(res.mdl.get("players")));
|
mMD_Players->setText(Utils::String::toUpper(res.mdl.get("players")));
|
||||||
mGrid.onSizeChanged();
|
mGrid.onSizeChanged();
|
||||||
}else{
|
}
|
||||||
|
else {
|
||||||
mResultName->setText("");
|
mResultName->setText("");
|
||||||
mResultDesc->setText("");
|
mResultDesc->setText("");
|
||||||
mResultThumbnail->setImage("");
|
mResultThumbnail->setImage("");
|
||||||
|
|
||||||
// metadata
|
// Metadata.
|
||||||
mMD_Rating->setValue("");
|
mMD_Rating->setValue("");
|
||||||
mMD_ReleaseDate->setValue("");
|
mMD_ReleaseDate->setValue("");
|
||||||
mMD_Developer->setText("");
|
mMD_Developer->setText("");
|
||||||
|
@ -341,9 +370,8 @@ void ScraperSearchComponent::updateInfoPane()
|
||||||
|
|
||||||
bool ScraperSearchComponent::input(InputConfig* config, Input input)
|
bool ScraperSearchComponent::input(InputConfig* config, Input input)
|
||||||
{
|
{
|
||||||
if(config->isMappedTo("a", input) && input.value != 0)
|
if (config->isMappedTo("a", input) && input.value != 0) {
|
||||||
{
|
if (mBlockAccept)
|
||||||
if(mBlockAccept)
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,8 +384,7 @@ void ScraperSearchComponent::render(const Transform4x4f& parentTrans)
|
||||||
|
|
||||||
renderChildren(trans);
|
renderChildren(trans);
|
||||||
|
|
||||||
if(mBlockAccept)
|
if (mBlockAccept) {
|
||||||
{
|
|
||||||
Renderer::setMatrix(trans);
|
Renderer::setMatrix(trans);
|
||||||
Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), 0x00000011, 0x00000011);
|
Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), 0x00000011, 0x00000011);
|
||||||
|
|
||||||
|
@ -369,9 +396,8 @@ void ScraperSearchComponent::returnResult(ScraperSearchResult result)
|
||||||
{
|
{
|
||||||
mBlockAccept = true;
|
mBlockAccept = true;
|
||||||
|
|
||||||
// resolve metadata image before returning
|
// Resolve metadata image before returning.
|
||||||
if(!result.imageUrl.empty())
|
if (!result.imageUrl.empty()) {
|
||||||
{
|
|
||||||
mMDResolveHandle = resolveMetaDataAssets(result, mLastSearch);
|
mMDResolveHandle = resolveMetaDataAssets(result, mLastSearch);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -383,46 +409,36 @@ void ScraperSearchComponent::update(int deltaTime)
|
||||||
{
|
{
|
||||||
GuiComponent::update(deltaTime);
|
GuiComponent::update(deltaTime);
|
||||||
|
|
||||||
if(mBlockAccept)
|
if (mBlockAccept)
|
||||||
{
|
|
||||||
mBusyAnim.update(deltaTime);
|
mBusyAnim.update(deltaTime);
|
||||||
}
|
|
||||||
|
|
||||||
if(mThumbnailReq && mThumbnailReq->status() != HttpReq::REQ_IN_PROGRESS)
|
if (mThumbnailReq && mThumbnailReq->status() != HttpReq::REQ_IN_PROGRESS)
|
||||||
{
|
|
||||||
updateThumbnail();
|
updateThumbnail();
|
||||||
}
|
|
||||||
|
|
||||||
if(mSearchHandle && mSearchHandle->status() != ASYNC_IN_PROGRESS)
|
if (mSearchHandle && mSearchHandle->status() != ASYNC_IN_PROGRESS) {
|
||||||
{
|
|
||||||
auto status = mSearchHandle->status();
|
auto status = mSearchHandle->status();
|
||||||
auto results = mSearchHandle->getResults();
|
auto results = mSearchHandle->getResults();
|
||||||
auto statusString = mSearchHandle->getStatusString();
|
auto statusString = mSearchHandle->getStatusString();
|
||||||
|
|
||||||
// we reset here because onSearchDone in auto mode can call mSkipCallback() which can call
|
// We reset here because onSearchDone in auto mode can call mSkipCallback() which
|
||||||
// another search() which will set our mSearchHandle to something important
|
// can call another search() which will set our mSearchHandle to something important.
|
||||||
mSearchHandle.reset();
|
mSearchHandle.reset();
|
||||||
|
|
||||||
if(status == ASYNC_DONE)
|
if (status == ASYNC_DONE)
|
||||||
{
|
|
||||||
onSearchDone(results);
|
onSearchDone(results);
|
||||||
}else if(status == ASYNC_ERROR)
|
else if (status == ASYNC_ERROR)
|
||||||
{
|
|
||||||
onSearchError(statusString);
|
onSearchError(statusString);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mMDResolveHandle && mMDResolveHandle->status() != ASYNC_IN_PROGRESS)
|
if (mMDResolveHandle && mMDResolveHandle->status() != ASYNC_IN_PROGRESS) {
|
||||||
{
|
if (mMDResolveHandle->status() == ASYNC_DONE) {
|
||||||
if(mMDResolveHandle->status() == ASYNC_DONE)
|
|
||||||
{
|
|
||||||
ScraperSearchResult result = mMDResolveHandle->getResult();
|
ScraperSearchResult result = mMDResolveHandle->getResult();
|
||||||
mMDResolveHandle.reset();
|
mMDResolveHandle.reset();
|
||||||
|
// This might end in us being deleted, depending on mAcceptCallback -
|
||||||
// this might end in us being deleted, depending on mAcceptCallback - so make sure this is the last thing we do in update()
|
// so make sure this is the last thing we do in update().
|
||||||
returnResult(result);
|
returnResult(result);
|
||||||
}else if(mMDResolveHandle->status() == ASYNC_ERROR)
|
}
|
||||||
{
|
else if (mMDResolveHandle->status() == ASYNC_ERROR) {
|
||||||
onSearchError(mMDResolveHandle->getStatusString());
|
onSearchError(mMDResolveHandle->getStatusString());
|
||||||
mMDResolveHandle.reset();
|
mMDResolveHandle.reset();
|
||||||
}
|
}
|
||||||
|
@ -431,12 +447,12 @@ void ScraperSearchComponent::update(int deltaTime)
|
||||||
|
|
||||||
void ScraperSearchComponent::updateThumbnail()
|
void ScraperSearchComponent::updateThumbnail()
|
||||||
{
|
{
|
||||||
if(mThumbnailReq && mThumbnailReq->status() == HttpReq::REQ_SUCCESS)
|
if (mThumbnailReq && mThumbnailReq->status() == HttpReq::REQ_SUCCESS) {
|
||||||
{
|
|
||||||
std::string content = mThumbnailReq->getContent();
|
std::string content = mThumbnailReq->getContent();
|
||||||
mResultThumbnail->setImage(content.data(), content.length());
|
mResultThumbnail->setImage(content.data(), content.length());
|
||||||
mGrid.onSizeChanged(); // a hack to fix the thumbnail position since its size changed
|
mGrid.onSizeChanged(); // A hack to fix the thumbnail position since its size changed.
|
||||||
}else{
|
}
|
||||||
|
else {
|
||||||
LOG(LogWarning) << "thumbnail req failed: " << mThumbnailReq->getErrorMsg();
|
LOG(LogWarning) << "thumbnail req failed: " << mThumbnailReq->getErrorMsg();
|
||||||
mResultThumbnail->setImage("");
|
mResultThumbnail->setImage("");
|
||||||
}
|
}
|
||||||
|
@ -446,15 +462,14 @@ void ScraperSearchComponent::updateThumbnail()
|
||||||
|
|
||||||
void ScraperSearchComponent::openInputScreen(ScraperSearchParams& params)
|
void ScraperSearchComponent::openInputScreen(ScraperSearchParams& params)
|
||||||
{
|
{
|
||||||
auto searchForFunc = [&](const std::string& name)
|
auto searchForFunc = [&](const std::string& name) {
|
||||||
{
|
|
||||||
params.nameOverride = name;
|
params.nameOverride = name;
|
||||||
search(params);
|
search(params);
|
||||||
};
|
};
|
||||||
|
|
||||||
stop();
|
stop();
|
||||||
mWindow->pushGui(new GuiTextEditPopup(mWindow, "SEARCH FOR",
|
mWindow->pushGui(new GuiTextEditPopup(mWindow, "SEARCH FOR",
|
||||||
// initial value is last search if there was one, otherwise the clean path name
|
// Initial value is last search if there was one, otherwise the clean path name.
|
||||||
params.nameOverride.empty() ? params.game->getCleanName() : params.nameOverride,
|
params.nameOverride.empty() ? params.game->getCleanName() : params.nameOverride,
|
||||||
searchForFunc, false, "SEARCH"));
|
searchForFunc, false, "SEARCH"));
|
||||||
}
|
}
|
||||||
|
@ -462,7 +477,7 @@ void ScraperSearchComponent::openInputScreen(ScraperSearchParams& params)
|
||||||
std::vector<HelpPrompt> ScraperSearchComponent::getHelpPrompts()
|
std::vector<HelpPrompt> ScraperSearchComponent::getHelpPrompts()
|
||||||
{
|
{
|
||||||
std::vector<HelpPrompt> prompts = mGrid.getHelpPrompts();
|
std::vector<HelpPrompt> prompts = mGrid.getHelpPrompts();
|
||||||
if(getSelectedIndex() != -1)
|
if (getSelectedIndex() != -1)
|
||||||
prompts.push_back(HelpPrompt("a", "accept result"));
|
prompts.push_back(HelpPrompt("a", "accept result"));
|
||||||
|
|
||||||
return prompts;
|
return prompts;
|
||||||
|
|
|
@ -1,3 +1,15 @@
|
||||||
|
//
|
||||||
|
// ScraperSearchComponent.h
|
||||||
|
//
|
||||||
|
// User interface component 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 GuiScraperStart.
|
||||||
|
//
|
||||||
|
// This component is called from GuiGameScraper for single-game scraping and
|
||||||
|
// from GuiScraperMulti for multi-game scraping.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_APP_COMPONENTS_SCRAPER_SEARCH_COMPONENT_H
|
#ifndef ES_APP_COMPONENTS_SCRAPER_SEARCH_COMPONENT_H
|
||||||
#define ES_APP_COMPONENTS_SCRAPER_SEARCH_COMPONENT_H
|
#define ES_APP_COMPONENTS_SCRAPER_SEARCH_COMPONENT_H
|
||||||
|
@ -17,8 +29,7 @@ class TextComponent;
|
||||||
class ScraperSearchComponent : public GuiComponent
|
class ScraperSearchComponent : public GuiComponent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum SearchType
|
enum SearchType {
|
||||||
{
|
|
||||||
ALWAYS_ACCEPT_FIRST_RESULT,
|
ALWAYS_ACCEPT_FIRST_RESULT,
|
||||||
ALWAYS_ACCEPT_MATCHING_CRC,
|
ALWAYS_ACCEPT_MATCHING_CRC,
|
||||||
NEVER_AUTO_ACCEPT
|
NEVER_AUTO_ACCEPT
|
||||||
|
@ -31,10 +42,14 @@ public:
|
||||||
void stop();
|
void stop();
|
||||||
inline SearchType getSearchType() const { return mSearchType; }
|
inline SearchType getSearchType() const { return mSearchType; }
|
||||||
|
|
||||||
// Metadata assets will be resolved before calling the accept callback (e.g. result.mdl's "image" is automatically downloaded and properly set).
|
// Metadata assets will be resolved before calling the accept callback
|
||||||
inline void setAcceptCallback(const std::function<void(const ScraperSearchResult&)>& acceptCallback) { mAcceptCallback = acceptCallback; }
|
// (e.g. result.mdl's "image" is automatically downloaded and properly set).
|
||||||
inline void setSkipCallback(const std::function<void()>& skipCallback) { mSkipCallback = skipCallback; };
|
inline void setAcceptCallback(const std::function<void(const ScraperSearchResult&)>&
|
||||||
inline void setCancelCallback(const std::function<void()>& cancelCallback) { mCancelCallback = cancelCallback; }
|
acceptCallback) { mAcceptCallback = acceptCallback; }
|
||||||
|
inline void setSkipCallback(const std::function<void()>&
|
||||||
|
skipCallback) { mSkipCallback = skipCallback; };
|
||||||
|
inline void setCancelCallback(const std::function<void()>&
|
||||||
|
cancelCallback) { mCancelCallback = cancelCallback; }
|
||||||
|
|
||||||
bool input(InputConfig* config, Input input) override;
|
bool input(InputConfig* config, Input input) override;
|
||||||
void update(int deltaTime) override;
|
void update(int deltaTime) override;
|
||||||
|
@ -56,7 +71,7 @@ private:
|
||||||
|
|
||||||
int getSelectedIndex();
|
int getSelectedIndex();
|
||||||
|
|
||||||
// resolve any metadata assets that need to be downloaded and return
|
// Resolve any metadata assets that need to be downloaded and return.
|
||||||
void returnResult(ScraperSearchResult result);
|
void returnResult(ScraperSearchResult result);
|
||||||
|
|
||||||
ComponentGrid mGrid;
|
ComponentGrid mGrid;
|
||||||
|
@ -75,14 +90,15 @@ private:
|
||||||
std::shared_ptr<TextComponent> mMD_Genre;
|
std::shared_ptr<TextComponent> mMD_Genre;
|
||||||
std::shared_ptr<TextComponent> mMD_Players;
|
std::shared_ptr<TextComponent> mMD_Players;
|
||||||
|
|
||||||
// label-component pair
|
// Label-component pair.
|
||||||
struct MetaDataPair
|
struct MetaDataPair {
|
||||||
{
|
|
||||||
std::shared_ptr<TextComponent> first;
|
std::shared_ptr<TextComponent> first;
|
||||||
std::shared_ptr<GuiComponent> second;
|
std::shared_ptr<GuiComponent> second;
|
||||||
bool resize;
|
bool resize;
|
||||||
|
|
||||||
MetaDataPair(const std::shared_ptr<TextComponent>& f, const std::shared_ptr<GuiComponent>& s, bool r = true) : first(f), second(s), resize(r) {};
|
MetaDataPair(const std::shared_ptr<TextComponent>& f,
|
||||||
|
const std::shared_ptr<GuiComponent>& s, bool r = true)
|
||||||
|
: first(f), second(s), resize(r) {};
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<MetaDataPair> mMD_Pairs;
|
std::vector<MetaDataPair> mMD_Pairs;
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
//
|
||||||
|
// GuiGameScraper.cpp
|
||||||
|
//
|
||||||
|
// Single game scraping user interface.
|
||||||
|
// This interface is triggered from GuiMetaDataEd.
|
||||||
|
// ScraperSearchComponent is called from here.
|
||||||
|
//
|
||||||
|
|
||||||
#include "guis/GuiGameScraper.h"
|
#include "guis/GuiGameScraper.h"
|
||||||
|
|
||||||
#include "components/ButtonComponent.h"
|
#include "components/ButtonComponent.h"
|
||||||
|
@ -7,48 +15,57 @@
|
||||||
#include "PowerSaver.h"
|
#include "PowerSaver.h"
|
||||||
#include "SystemData.h"
|
#include "SystemData.h"
|
||||||
|
|
||||||
GuiGameScraper::GuiGameScraper(Window* window, ScraperSearchParams params, std::function<void(const ScraperSearchResult&)> doneFunc) : GuiComponent(window),
|
GuiGameScraper::GuiGameScraper(
|
||||||
mGrid(window, Vector2i(1, 7)),
|
Window* window,
|
||||||
mBox(window, ":/frame.png"),
|
ScraperSearchParams params,
|
||||||
mSearchParams(params),
|
std::function<void(const ScraperSearchResult&)> doneFunc)
|
||||||
mClose(false)
|
: GuiComponent(window),
|
||||||
|
mGrid(window, Vector2i(1, 7)),
|
||||||
|
mBox(window, ":/frame.png"),
|
||||||
|
mSearchParams(params),
|
||||||
|
mClose(false)
|
||||||
{
|
{
|
||||||
PowerSaver::pause();
|
PowerSaver::pause();
|
||||||
addChild(&mBox);
|
addChild(&mBox);
|
||||||
addChild(&mGrid);
|
addChild(&mGrid);
|
||||||
|
|
||||||
// row 0 is a spacer
|
// Row 0 is a spacer.
|
||||||
|
|
||||||
mGameName = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(Utils::FileSystem::getFileName(mSearchParams.game->getPath())),
|
mGameName = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(
|
||||||
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER);
|
Utils::FileSystem::getFileName(mSearchParams.game->getPath())),
|
||||||
|
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER);
|
||||||
mGrid.setEntry(mGameName, Vector2i(0, 1), false, true);
|
mGrid.setEntry(mGameName, Vector2i(0, 1), false, true);
|
||||||
|
|
||||||
// row 2 is a spacer
|
// Row 2 is a spacer.
|
||||||
|
|
||||||
mSystemName = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(mSearchParams.system->getFullName()), Font::get(FONT_SIZE_SMALL),
|
mSystemName = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(
|
||||||
0x888888FF, ALIGN_CENTER);
|
mSearchParams.system->getFullName()), Font::get(FONT_SIZE_SMALL),
|
||||||
|
0x888888FF, ALIGN_CENTER);
|
||||||
mGrid.setEntry(mSystemName, Vector2i(0, 3), false, true);
|
mGrid.setEntry(mSystemName, Vector2i(0, 3), false, true);
|
||||||
|
|
||||||
// row 4 is a spacer
|
// Row 4 is a spacer.
|
||||||
|
|
||||||
// ScraperSearchComponent
|
// ScraperSearchComponent.
|
||||||
mSearch = std::make_shared<ScraperSearchComponent>(window, ScraperSearchComponent::NEVER_AUTO_ACCEPT);
|
mSearch = std::make_shared<ScraperSearchComponent>(window,
|
||||||
|
ScraperSearchComponent::NEVER_AUTO_ACCEPT);
|
||||||
mGrid.setEntry(mSearch, Vector2i(0, 5), true);
|
mGrid.setEntry(mSearch, Vector2i(0, 5), true);
|
||||||
|
|
||||||
// buttons
|
// Buttons
|
||||||
std::vector< std::shared_ptr<ButtonComponent> > buttons;
|
std::vector< std::shared_ptr<ButtonComponent> > buttons;
|
||||||
|
|
||||||
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "INPUT", "search", [&] {
|
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "INPUT", "search", [&] {
|
||||||
mSearch->openInputScreen(mSearchParams);
|
mSearch->openInputScreen(mSearchParams);
|
||||||
mGrid.resetCursor();
|
mGrid.resetCursor();
|
||||||
}));
|
}));
|
||||||
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "cancel", [&] { delete this; }));
|
buttons.push_back(std::make_shared<ButtonComponent>(
|
||||||
|
mWindow, "CANCEL", "cancel", [&] { delete this; }));
|
||||||
mButtonGrid = makeButtonGrid(mWindow, buttons);
|
mButtonGrid = makeButtonGrid(mWindow, buttons);
|
||||||
|
|
||||||
mGrid.setEntry(mButtonGrid, Vector2i(0, 6), true, false);
|
mGrid.setEntry(mButtonGrid, Vector2i(0, 6), true, false);
|
||||||
|
|
||||||
// we call this->close() instead of just delete this; in the accept callback:
|
// We call this->close() instead of just 'delete this' in the accept callback.
|
||||||
// this is because of how GuiComponent::update works. if it was just delete this, this would happen when the metadata resolver is done:
|
// This is because of how GuiComponent::update works. If it was just 'delete this',
|
||||||
|
// the following would happen when the metadata resolver is done:
|
||||||
// GuiGameScraper::update()
|
// GuiGameScraper::update()
|
||||||
// GuiComponent::update()
|
// GuiComponent::update()
|
||||||
// it = mChildren.cbegin();
|
// it = mChildren.cbegin();
|
||||||
|
@ -56,9 +73,9 @@ GuiGameScraper::GuiGameScraper(Window* window, ScraperSearchParams params, std::
|
||||||
// it++;
|
// it++;
|
||||||
// mSearchComponent::update()
|
// mSearchComponent::update()
|
||||||
// acceptCallback -> delete this
|
// acceptCallback -> delete this
|
||||||
// it++; // error, mChildren has been deleted because it was part of this
|
// it++; // Error, mChildren has been deleted because it was part of 'this'.
|
||||||
|
|
||||||
// so instead we do this:
|
// So instead we do this:
|
||||||
// GuiGameScraper::update()
|
// GuiGameScraper::update()
|
||||||
// GuiComponent::update()
|
// GuiComponent::update()
|
||||||
// it = mChildren.cbegin();
|
// it = mChildren.cbegin();
|
||||||
|
@ -66,17 +83,19 @@ GuiGameScraper::GuiGameScraper(Window* window, ScraperSearchParams params, std::
|
||||||
// it++;
|
// it++;
|
||||||
// mSearchComponent::update()
|
// mSearchComponent::update()
|
||||||
// acceptCallback -> close() -> mClose = true
|
// acceptCallback -> close() -> mClose = true
|
||||||
// it++; // ok
|
// it++; // OK.
|
||||||
// if(mClose)
|
// if(mClose)
|
||||||
// delete this;
|
// delete this;
|
||||||
mSearch->setAcceptCallback([this, doneFunc](const ScraperSearchResult& result) { doneFunc(result); close(); });
|
mSearch->setAcceptCallback([this, doneFunc](const ScraperSearchResult& result) {
|
||||||
|
doneFunc(result); close(); });
|
||||||
mSearch->setCancelCallback([&] { delete this; });
|
mSearch->setCancelCallback([&] { delete this; });
|
||||||
|
|
||||||
setSize(Renderer::getScreenWidth() * 0.95f, Renderer::getScreenHeight() * 0.747f);
|
setSize(Renderer::getScreenWidth() * 0.95f, Renderer::getScreenHeight() * 0.747f);
|
||||||
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, (Renderer::getScreenHeight() - mSize.y()) / 2);
|
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, (Renderer::getScreenHeight() -
|
||||||
|
mSize.y()) / 2);
|
||||||
|
|
||||||
mGrid.resetCursor();
|
mGrid.resetCursor();
|
||||||
mSearch->search(params); // start the search
|
mSearch->search(params); // Start the search.
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiGameScraper::onSizeChanged()
|
void GuiGameScraper::onSizeChanged()
|
||||||
|
@ -84,18 +103,19 @@ void GuiGameScraper::onSizeChanged()
|
||||||
mBox.fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32));
|
mBox.fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32));
|
||||||
|
|
||||||
mGrid.setRowHeightPerc(0, 0.04f, false);
|
mGrid.setRowHeightPerc(0, 0.04f, false);
|
||||||
mGrid.setRowHeightPerc(1, mGameName->getFont()->getLetterHeight() / mSize.y(), false); // game name
|
mGrid.setRowHeightPerc(1, mGameName->getFont()->getLetterHeight() /
|
||||||
|
mSize.y(), false); // Game name.
|
||||||
mGrid.setRowHeightPerc(2, 0.04f, false);
|
mGrid.setRowHeightPerc(2, 0.04f, false);
|
||||||
mGrid.setRowHeightPerc(3, mSystemName->getFont()->getLetterHeight() / mSize.y(), false); // system name
|
mGrid.setRowHeightPerc(3, mSystemName->getFont()->getLetterHeight() /
|
||||||
|
mSize.y(), false); // System name.
|
||||||
mGrid.setRowHeightPerc(4, 0.04f, false);
|
mGrid.setRowHeightPerc(4, 0.04f, false);
|
||||||
mGrid.setRowHeightPerc(6, mButtonGrid->getSize().y() / mSize.y(), false); // buttons
|
mGrid.setRowHeightPerc(6, mButtonGrid->getSize().y() / mSize.y(), false); // Buttons.
|
||||||
mGrid.setSize(mSize);
|
mGrid.setSize(mSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GuiGameScraper::input(InputConfig* config, Input input)
|
bool GuiGameScraper::input(InputConfig* config, Input input)
|
||||||
{
|
{
|
||||||
if(config->isMappedTo("b", input) && input.value)
|
if(config->isMappedTo("b", input) && input.value) {
|
||||||
{
|
|
||||||
PowerSaver::resume();
|
PowerSaver::resume();
|
||||||
delete this;
|
delete this;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
//
|
||||||
|
// GuiGameScraper.h
|
||||||
|
//
|
||||||
|
// Single game scraping user interface.
|
||||||
|
// This interface is triggered from GuiMetaDataEd.
|
||||||
|
// ScraperSearchComponent is called from here.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_APP_GUIS_GUI_GAME_SCRAPER_H
|
#ifndef ES_APP_GUIS_GUI_GAME_SCRAPER_H
|
||||||
#define ES_APP_GUIS_GUI_GAME_SCRAPER_H
|
#define ES_APP_GUIS_GUI_GAME_SCRAPER_H
|
||||||
|
@ -9,7 +17,10 @@
|
||||||
class GuiGameScraper : public GuiComponent
|
class GuiGameScraper : public GuiComponent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
GuiGameScraper(Window* window, ScraperSearchParams params, std::function<void(const ScraperSearchResult&)> doneFunc);
|
GuiGameScraper(
|
||||||
|
Window* window,
|
||||||
|
ScraperSearchParams params,
|
||||||
|
std::function<void(const ScraperSearchResult&)> doneFunc);
|
||||||
|
|
||||||
void onSizeChanged() override;
|
void onSizeChanged() override;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
|
//
|
||||||
|
// GuiGamelistFilter.cpp
|
||||||
|
//
|
||||||
|
// User interface for the gamelist filters.
|
||||||
|
// Triggered from the GuiGamelistOptions menu.
|
||||||
|
// Actual filter logic is covered by FileFilterIndex.
|
||||||
|
//
|
||||||
|
|
||||||
#include "guis/GuiGamelistFilter.h"
|
#include "guis/GuiGamelistFilter.h"
|
||||||
|
|
||||||
#include "components/OptionListComponent.h"
|
#include "components/OptionListComponent.h"
|
||||||
#include "views/UIModeController.h"
|
#include "views/UIModeController.h"
|
||||||
#include "SystemData.h"
|
#include "SystemData.h"
|
||||||
|
|
||||||
GuiGamelistFilter::GuiGamelistFilter(Window* window, SystemData* system) : GuiComponent(window), mMenu(window, "FILTER GAMELIST BY"), mSystem(system)
|
GuiGamelistFilter::GuiGamelistFilter(
|
||||||
|
Window* window,
|
||||||
|
SystemData* system)
|
||||||
|
: GuiComponent(window),
|
||||||
|
mMenu(window, "FILTER GAMELIST BY"),
|
||||||
|
mSystem(system)
|
||||||
{
|
{
|
||||||
initializeMenu();
|
initializeMenu();
|
||||||
}
|
}
|
||||||
|
@ -13,15 +26,15 @@ void GuiGamelistFilter::initializeMenu()
|
||||||
{
|
{
|
||||||
addChild(&mMenu);
|
addChild(&mMenu);
|
||||||
|
|
||||||
// get filters from system
|
// Get filters from system.
|
||||||
|
|
||||||
mFilterIndex = mSystem->getIndex();
|
mFilterIndex = mSystem->getIndex();
|
||||||
|
|
||||||
ComponentListRow row;
|
ComponentListRow row;
|
||||||
|
|
||||||
// show filtered menu
|
// Show filtered menu.
|
||||||
row.elements.clear();
|
row.elements.clear();
|
||||||
row.addElement(std::make_shared<TextComponent>(mWindow, "RESET ALL FILTERS", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
row.addElement(std::make_shared<TextComponent>(mWindow, "RESET ALL FILTERS",
|
||||||
|
Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||||
row.makeAcceptInputHandler(std::bind(&GuiGamelistFilter::resetAllFilters, this));
|
row.makeAcceptInputHandler(std::bind(&GuiGamelistFilter::resetAllFilters, this));
|
||||||
mMenu.addRow(row);
|
mMenu.addRow(row);
|
||||||
row.elements.clear();
|
row.elements.clear();
|
||||||
|
@ -30,13 +43,15 @@ void GuiGamelistFilter::initializeMenu()
|
||||||
|
|
||||||
mMenu.addButton("BACK", "back", std::bind(&GuiGamelistFilter::applyFilters, this));
|
mMenu.addButton("BACK", "back", std::bind(&GuiGamelistFilter::applyFilters, this));
|
||||||
|
|
||||||
mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2, Renderer::getScreenHeight() * 0.15f);
|
mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2,
|
||||||
|
Renderer::getScreenHeight() * 0.15f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiGamelistFilter::resetAllFilters()
|
void GuiGamelistFilter::resetAllFilters()
|
||||||
{
|
{
|
||||||
mFilterIndex->resetFilters();
|
mFilterIndex->resetFilters();
|
||||||
for (std::map<FilterIndexType, std::shared_ptr< OptionListComponent<std::string> >>::const_iterator it = mFilterOptions.cbegin(); it != mFilterOptions.cend(); ++it ) {
|
for (std::map<FilterIndexType, std::shared_ptr< OptionListComponent<std::string>
|
||||||
|
>>::const_iterator it = mFilterOptions.cbegin(); it != mFilterOptions.cend(); ++it ) {
|
||||||
std::shared_ptr< OptionListComponent<std::string> > optionList = it->second;
|
std::shared_ptr< OptionListComponent<std::string> > optionList = it->second;
|
||||||
optionList->selectNone();
|
optionList->selectNone();
|
||||||
}
|
}
|
||||||
|
@ -57,23 +72,22 @@ void GuiGamelistFilter::addFiltersToMenu()
|
||||||
if (UIModeController::getInstance()->isUIModeKid())
|
if (UIModeController::getInstance()->isUIModeKid())
|
||||||
skip = 2;
|
skip = 2;
|
||||||
|
|
||||||
for (std::vector<FilterDataDecl>::const_iterator it = decls.cbegin(); it != decls.cend()-skip; ++it ) {
|
for (std::vector<FilterDataDecl>::const_iterator it = decls.cbegin();
|
||||||
|
it != decls.cend()-skip; ++it ) {
|
||||||
|
|
||||||
FilterIndexType type = (*it).type; // type of filter
|
FilterIndexType type = (*it).type; // Type of filter.
|
||||||
std::map<std::string, int>* allKeys = (*it).allIndexKeys; // all possible filters for this type
|
// All possible filters for this type.
|
||||||
std::string menuLabel = (*it).menuLabel; // text to show in menu
|
std::map<std::string, int>* allKeys = (*it).allIndexKeys;
|
||||||
|
std::string menuLabel = (*it).menuLabel; // Text to show in menu.
|
||||||
std::shared_ptr< OptionListComponent<std::string> > optionList;
|
std::shared_ptr< OptionListComponent<std::string> > optionList;
|
||||||
|
|
||||||
|
// Add filters (with first one selected).
|
||||||
// add filters (with first one selected)
|
|
||||||
ComponentListRow row;
|
ComponentListRow row;
|
||||||
|
|
||||||
// add genres
|
// Add genres.
|
||||||
optionList = std::make_shared< OptionListComponent<std::string> >(mWindow, menuLabel, true);
|
optionList = std::make_shared< OptionListComponent<std::string> >(mWindow, menuLabel, true);
|
||||||
for(auto it: *allKeys)
|
for (auto it: *allKeys)
|
||||||
{
|
|
||||||
optionList->add(it.first, it.first, mFilterIndex->isKeyBeingFilteredBy(it.first, type));
|
optionList->add(it.first, it.first, mFilterIndex->isKeyBeingFilteredBy(it.first, type));
|
||||||
}
|
|
||||||
if (allKeys->size() > 0)
|
if (allKeys->size() > 0)
|
||||||
mMenu.addWithLabel(menuLabel, optionList);
|
mMenu.addWithLabel(menuLabel, optionList);
|
||||||
|
|
||||||
|
@ -84,27 +98,24 @@ void GuiGamelistFilter::addFiltersToMenu()
|
||||||
void GuiGamelistFilter::applyFilters()
|
void GuiGamelistFilter::applyFilters()
|
||||||
{
|
{
|
||||||
std::vector<FilterDataDecl> decls = mFilterIndex->getFilterDataDecls();
|
std::vector<FilterDataDecl> decls = mFilterIndex->getFilterDataDecls();
|
||||||
for (std::map<FilterIndexType, std::shared_ptr< OptionListComponent<std::string> >>::const_iterator it = mFilterOptions.cbegin(); it != mFilterOptions.cend(); ++it ) {
|
for (std::map<FilterIndexType, std::shared_ptr< OptionListComponent<std::string>
|
||||||
|
>>::const_iterator it = mFilterOptions.cbegin(); it != mFilterOptions.cend(); ++it ) {
|
||||||
std::shared_ptr< OptionListComponent<std::string> > optionList = it->second;
|
std::shared_ptr< OptionListComponent<std::string> > optionList = it->second;
|
||||||
std::vector<std::string> filters = optionList->getSelectedObjects();
|
std::vector<std::string> filters = optionList->getSelectedObjects();
|
||||||
mFilterIndex->setFilter(it->first, &filters);
|
mFilterIndex->setFilter(it->first, &filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete this;
|
delete this;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GuiGamelistFilter::input(InputConfig* config, Input input)
|
bool GuiGamelistFilter::input(InputConfig* config, Input input)
|
||||||
{
|
{
|
||||||
bool consumed = GuiComponent::input(config, input);
|
bool consumed = GuiComponent::input(config, input);
|
||||||
if(consumed)
|
if (consumed)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if(config->isMappedTo("b", input) && input.value != 0)
|
if (config->isMappedTo("b", input) && input.value != 0)
|
||||||
{
|
|
||||||
applyFilters();
|
applyFilters();
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
//
|
||||||
|
// GuiGamelistFilter.h
|
||||||
|
//
|
||||||
|
// User interface for the gamelist filters.
|
||||||
|
// Triggered from the GuiGamelistOptions menu.
|
||||||
|
// Actual filter logic is covered by FileFilterIndex.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_APP_GUIS_GUI_GAME_LIST_FILTER_H
|
#ifndef ES_APP_GUIS_GUI_GAME_LIST_FILTER_H
|
||||||
#define ES_APP_GUIS_GUI_GAME_LIST_FILTER_H
|
#define ES_APP_GUIS_GUI_GAME_LIST_FILTER_H
|
||||||
|
@ -14,6 +22,7 @@ class GuiGamelistFilter : public GuiComponent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
GuiGamelistFilter(Window* window, SystemData* system);
|
GuiGamelistFilter(Window* window, SystemData* system);
|
||||||
|
|
||||||
~GuiGamelistFilter();
|
~GuiGamelistFilter();
|
||||||
bool input(InputConfig* config, Input input) override;
|
bool input(InputConfig* config, Input input) override;
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
// Gamelist options menu for the 'Jump to...' quick selector,
|
// Gamelist options menu for the 'Jump to...' quick selector,
|
||||||
// game sorting, game filters, and metadata edit.
|
// game sorting, game filters, and metadata edit.
|
||||||
//
|
//
|
||||||
|
// The filter interface is covered by GuiGamelistFilter and the
|
||||||
|
// metadata edit interface is covered by GuiMetaDataEd.
|
||||||
|
//
|
||||||
|
|
||||||
#include "GuiGamelistOptions.h"
|
#include "GuiGamelistOptions.h"
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
// Gamelist options menu for the 'Jump to...' quick selector,
|
// Gamelist options menu for the 'Jump to...' quick selector,
|
||||||
// game sorting, game filters, and metadata edit.
|
// game sorting, game filters, and metadata edit.
|
||||||
//
|
//
|
||||||
|
// The filter interface is covered by GuiGamelistFilter and the
|
||||||
|
// metadata edit interface is covered by GuiMetaDataEd.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H
|
#ifndef ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
//
|
||||||
|
// GuiMetaDataEd.cpp
|
||||||
|
//
|
||||||
|
// Game metadata edit user interface.
|
||||||
|
// This interface is triggered from the GuiGamelistOptions menu.
|
||||||
|
// The scraping interface is handled by GuiGameScraper which calls
|
||||||
|
// ScraperSearchComponent.
|
||||||
|
//
|
||||||
|
|
||||||
#include "guis/GuiMetaDataEd.h"
|
#include "guis/GuiMetaDataEd.h"
|
||||||
|
|
||||||
#include "components/ButtonComponent.h"
|
#include "components/ButtonComponent.h"
|
||||||
|
@ -20,25 +29,35 @@
|
||||||
#include "SystemData.h"
|
#include "SystemData.h"
|
||||||
#include "Window.h"
|
#include "Window.h"
|
||||||
|
|
||||||
GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector<MetaDataDecl>& mdd, ScraperSearchParams scraperParams,
|
GuiMetaDataEd::GuiMetaDataEd(
|
||||||
const std::string& /*header*/, std::function<void()> saveCallback, std::function<void()> deleteFunc) : GuiComponent(window),
|
Window* window,
|
||||||
mScraperParams(scraperParams),
|
MetaDataList* md,
|
||||||
|
const std::vector<MetaDataDecl>& mdd,
|
||||||
|
ScraperSearchParams scraperParams,
|
||||||
|
const std::string& /*header*/,
|
||||||
|
std::function<void()> saveCallback,
|
||||||
|
std::function<void()> deleteFunc)
|
||||||
|
: GuiComponent(window),
|
||||||
|
mScraperParams(scraperParams),
|
||||||
|
|
||||||
mBackground(window, ":/frame.png"),
|
mBackground(window, ":/frame.png"),
|
||||||
mGrid(window, Vector2i(1, 3)),
|
mGrid(window, Vector2i(1, 3)),
|
||||||
|
|
||||||
mMetaDataDecl(mdd),
|
mMetaDataDecl(mdd),
|
||||||
mMetaData(md),
|
mMetaData(md),
|
||||||
mSavedCallback(saveCallback), mDeleteFunc(deleteFunc)
|
mSavedCallback(saveCallback),
|
||||||
|
mDeleteFunc(deleteFunc)
|
||||||
{
|
{
|
||||||
addChild(&mBackground);
|
addChild(&mBackground);
|
||||||
addChild(&mGrid);
|
addChild(&mGrid);
|
||||||
|
|
||||||
mHeaderGrid = std::make_shared<ComponentGrid>(mWindow, Vector2i(1, 5));
|
mHeaderGrid = std::make_shared<ComponentGrid>(mWindow, Vector2i(1, 5));
|
||||||
|
|
||||||
mTitle = std::make_shared<TextComponent>(mWindow, "EDIT METADATA", Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER);
|
mTitle = std::make_shared<TextComponent>(mWindow, "EDIT METADATA",
|
||||||
mSubtitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(Utils::FileSystem::getFileName(scraperParams.game->getPath())),
|
Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER);
|
||||||
Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER);
|
mSubtitle = std::make_shared<TextComponent>(mWindow,
|
||||||
|
Utils::String::toUpper(Utils::FileSystem::getFileName(scraperParams.game->
|
||||||
|
getPath())), Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER);
|
||||||
mHeaderGrid->setEntry(mTitle, Vector2i(0, 1), false, true);
|
mHeaderGrid->setEntry(mTitle, Vector2i(0, 1), false, true);
|
||||||
mHeaderGrid->setEntry(mSubtitle, Vector2i(0, 3), false, true);
|
mHeaderGrid->setEntry(mSubtitle, Vector2i(0, 3), false, true);
|
||||||
|
|
||||||
|
@ -47,19 +66,19 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
|
||||||
mList = std::make_shared<ComponentList>(mWindow);
|
mList = std::make_shared<ComponentList>(mWindow);
|
||||||
mGrid.setEntry(mList, Vector2i(0, 1), true, true);
|
mGrid.setEntry(mList, Vector2i(0, 1), true, true);
|
||||||
|
|
||||||
// populate list
|
// Populate list.
|
||||||
for(auto iter = mdd.cbegin(); iter != mdd.cend(); iter++)
|
for (auto iter = mdd.cbegin(); iter != mdd.cend(); iter++) {
|
||||||
{
|
|
||||||
std::shared_ptr<GuiComponent> ed;
|
std::shared_ptr<GuiComponent> ed;
|
||||||
|
|
||||||
// don't add statistics
|
// Don't add statistics.
|
||||||
if(iter->isStatistic)
|
if (iter->isStatistic)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Don't show the launch string override entry if this option has been disabled in the settings
|
// Don't show the launch string override entry if this option has been disabled.
|
||||||
if(!Settings::getInstance()->getBool("LaunchstringOverride") && iter->type == MD_LAUNCHSTRING)
|
if (!Settings::getInstance()->getBool("LaunchstringOverride") &&
|
||||||
{
|
iter->type == MD_LAUNCHSTRING) {
|
||||||
ed = std::make_shared<TextComponent>(window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
|
ed = std::make_shared<TextComponent>(window, "", Font::get(FONT_SIZE_SMALL,
|
||||||
|
FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
|
||||||
assert(ed);
|
assert(ed);
|
||||||
ed->setValue(mMetaData->get(iter->key));
|
ed->setValue(mMetaData->get(iter->key));
|
||||||
mEditors.push_back(ed);
|
mEditors.push_back(ed);
|
||||||
|
@ -67,22 +86,20 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// create ed and add it (and any related components) to mMenu
|
// Create ed and add it (and any related components) to mMenu.
|
||||||
// ed's value will be set below
|
// ed's value will be set below.
|
||||||
ComponentListRow row;
|
ComponentListRow row;
|
||||||
auto lbl = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(iter->displayName), Font::get(FONT_SIZE_SMALL), 0x777777FF);
|
auto lbl = std::make_shared<TextComponent>(mWindow,
|
||||||
row.addElement(lbl, true); // label
|
Utils::String::toUpper(iter->displayName), Font::get(FONT_SIZE_SMALL), 0x777777FF);
|
||||||
|
row.addElement(lbl, true); // Label.
|
||||||
|
|
||||||
switch(iter->type)
|
switch (iter->type) {
|
||||||
{
|
case MD_BOOL: {
|
||||||
case MD_BOOL:
|
|
||||||
{
|
|
||||||
ed = std::make_shared<SwitchComponent>(window);
|
ed = std::make_shared<SwitchComponent>(window);
|
||||||
row.addElement(ed, false, true);
|
row.addElement(ed, false, true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MD_RATING:
|
case MD_RATING: {
|
||||||
{
|
|
||||||
ed = std::make_shared<RatingComponent>(window);
|
ed = std::make_shared<RatingComponent>(window);
|
||||||
const float height = lbl->getSize().y() * 0.71f;
|
const float height = lbl->getSize().y() * 0.71f;
|
||||||
ed->setSize(0, height);
|
ed->setSize(0, height);
|
||||||
|
@ -92,13 +109,12 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
|
||||||
spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0);
|
spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0);
|
||||||
row.addElement(spacer, false);
|
row.addElement(spacer, false);
|
||||||
|
|
||||||
// pass input to the actual RatingComponent instead of the spacer
|
// Pass input to the actual RatingComponent instead of the spacer.
|
||||||
row.input_handler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1, std::placeholders::_2);
|
row.input_handler = std::bind(&GuiComponent::input,
|
||||||
|
ed.get(), std::placeholders::_1, std::placeholders::_2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MD_DATE:
|
case MD_DATE: {
|
||||||
{
|
|
||||||
ed = std::make_shared<DateTimeEditComponent>(window);
|
ed = std::make_shared<DateTimeEditComponent>(window);
|
||||||
row.addElement(ed, false);
|
row.addElement(ed, false);
|
||||||
|
|
||||||
|
@ -106,20 +122,20 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
|
||||||
spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0);
|
spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0);
|
||||||
row.addElement(spacer, false);
|
row.addElement(spacer, false);
|
||||||
|
|
||||||
// pass input to the actual DateTimeEditComponent instead of the spacer
|
// Pass input to the actual DateTimeEditComponent instead of the spacer.
|
||||||
row.input_handler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1, std::placeholders::_2);
|
row.input_handler = std::bind(&GuiComponent::input, ed.get(),
|
||||||
|
std::placeholders::_1, std::placeholders::_2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MD_TIME:
|
case MD_TIME: {
|
||||||
{
|
ed = std::make_shared<DateTimeEditComponent>(window,
|
||||||
ed = std::make_shared<DateTimeEditComponent>(window, DateTimeEditComponent::DISP_RELATIVE_TO_NOW);
|
DateTimeEditComponent::DISP_RELATIVE_TO_NOW);
|
||||||
row.addElement(ed, false);
|
row.addElement(ed, false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MD_LAUNCHSTRING:
|
case MD_LAUNCHSTRING: {
|
||||||
{
|
ed = std::make_shared<TextComponent>(window, "",
|
||||||
ed = std::make_shared<TextComponent>(window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
|
Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
|
||||||
row.addElement(ed, true);
|
row.addElement(ed, true);
|
||||||
|
|
||||||
auto spacer = std::make_shared<GuiComponent>(mWindow);
|
auto spacer = std::make_shared<GuiComponent>(mWindow);
|
||||||
|
@ -133,21 +149,26 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
|
||||||
|
|
||||||
bool multiLine = false;
|
bool multiLine = false;
|
||||||
const std::string title = iter->displayPrompt;
|
const std::string title = iter->displayPrompt;
|
||||||
auto updateVal = [ed](const std::string& newVal) { ed->setValue(newVal); }; // ok callback (apply new value to ed)
|
auto updateVal = [ed](const std::string& newVal) {
|
||||||
|
ed->setValue(newVal); }; // OK callback (apply new value to ed).
|
||||||
|
|
||||||
std::string staticTextString = "Default value from es_systems.cfg:";
|
std::string staticTextString = "Default value from es_systems.cfg:";
|
||||||
std::string defaultLaunchString = scraperParams.system->getSystemEnvData()->mLaunchCommand;
|
std::string defaultLaunchString = scraperParams.system->
|
||||||
|
getSystemEnvData()->mLaunchCommand;
|
||||||
|
|
||||||
row.makeAcceptInputHandler([this, title, staticTextString, defaultLaunchString, ed, updateVal, multiLine] {
|
row.makeAcceptInputHandler([this, title, staticTextString,
|
||||||
mWindow->pushGui(new GuiComplexTextEditPopup(mWindow, title, staticTextString, defaultLaunchString, ed->getValue(), updateVal, multiLine));
|
defaultLaunchString, ed, updateVal, multiLine] {
|
||||||
|
mWindow->pushGui(new GuiComplexTextEditPopup(mWindow, title,
|
||||||
|
staticTextString, defaultLaunchString, ed->getValue(),
|
||||||
|
updateVal, multiLine));
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MD_MULTILINE_STRING:
|
case MD_MULTILINE_STRING:
|
||||||
default:
|
default: {
|
||||||
{
|
// MD_STRING.
|
||||||
// MD_STRING
|
ed = std::make_shared<TextComponent>(window, "", Font::get(FONT_SIZE_SMALL,
|
||||||
ed = std::make_shared<TextComponent>(window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
|
FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
|
||||||
row.addElement(ed, true);
|
row.addElement(ed, true);
|
||||||
|
|
||||||
auto spacer = std::make_shared<GuiComponent>(mWindow);
|
auto spacer = std::make_shared<GuiComponent>(mWindow);
|
||||||
|
@ -161,9 +182,12 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
|
||||||
|
|
||||||
bool multiLine = iter->type == MD_MULTILINE_STRING;
|
bool multiLine = iter->type == MD_MULTILINE_STRING;
|
||||||
const std::string title = iter->displayPrompt;
|
const std::string title = iter->displayPrompt;
|
||||||
auto updateVal = [ed](const std::string& newVal) { ed->setValue(newVal); }; // ok callback (apply new value to ed)
|
|
||||||
|
// OK callback (apply new value to ed).
|
||||||
|
auto updateVal = [ed](const std::string& newVal) { ed->setValue(newVal); };
|
||||||
row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] {
|
row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] {
|
||||||
mWindow->pushGui(new GuiTextEditPopup(mWindow, title, ed->getValue(), updateVal, multiLine));
|
mWindow->pushGui(new GuiTextEditPopup(mWindow, title, ed->getValue(),
|
||||||
|
updateVal, multiLine));
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -177,26 +201,33 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
|
||||||
|
|
||||||
std::vector< std::shared_ptr<ButtonComponent> > buttons;
|
std::vector< std::shared_ptr<ButtonComponent> > buttons;
|
||||||
|
|
||||||
if(!scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
|
if (!scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
|
||||||
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SCRAPE", "scrape", std::bind(&GuiMetaDataEd::fetch, this)));
|
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SCRAPE", "scrape",
|
||||||
|
std::bind(&GuiMetaDataEd::fetch, this)));
|
||||||
|
|
||||||
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SAVE", "save", [&] { save(); delete this; }));
|
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SAVE", "save",
|
||||||
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "cancel", [&] { delete this; }));
|
[&] { save(); delete this; }));
|
||||||
|
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "cancel",
|
||||||
|
[&] { delete this; }));
|
||||||
|
|
||||||
if(mDeleteFunc)
|
if (mDeleteFunc) {
|
||||||
{
|
|
||||||
auto deleteFileAndSelf = [&] { mDeleteFunc(); delete this; };
|
auto deleteFileAndSelf = [&] { mDeleteFunc(); delete this; };
|
||||||
auto deleteBtnFunc = [this, deleteFileAndSelf] { mWindow->pushGui(new GuiMsgBox(mWindow, "THIS WILL DELETE THE ACTUAL GAME FILE(S)!\nARE YOU SURE?", "YES", deleteFileAndSelf, "NO", nullptr)); };
|
auto deleteBtnFunc = [this, deleteFileAndSelf] { mWindow->pushGui(
|
||||||
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "DELETE", "delete", deleteBtnFunc));
|
new GuiMsgBox(mWindow, "THIS WILL DELETE THE ACTUAL GAME FILE(S)!\nARE "
|
||||||
|
"YOU SURE?", "YES", deleteFileAndSelf, "NO", nullptr)); };
|
||||||
|
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "DELETE",
|
||||||
|
"delete", deleteBtnFunc));
|
||||||
}
|
}
|
||||||
|
|
||||||
mButtons = makeButtonGrid(mWindow, buttons);
|
mButtons = makeButtonGrid(mWindow, buttons);
|
||||||
mGrid.setEntry(mButtons, Vector2i(0, 2), true, false);
|
mGrid.setEntry(mButtons, Vector2i(0, 2), true, false);
|
||||||
|
|
||||||
// resize + center
|
// Resize + center.
|
||||||
float width = (float)Math::min(Renderer::getScreenHeight(), (int)(Renderer::getScreenWidth() * 0.90f));
|
float width = (float)Math::min(Renderer::getScreenHeight(),
|
||||||
|
(int)(Renderer::getScreenWidth() * 0.90f));
|
||||||
setSize(width, Renderer::getScreenHeight() * 0.82f);
|
setSize(width, Renderer::getScreenHeight() * 0.82f);
|
||||||
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, (Renderer::getScreenHeight() - mSize.y()) / 2);
|
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2,
|
||||||
|
(Renderer::getScreenHeight() - mSize.y()) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiMetaDataEd::onSizeChanged()
|
void GuiMetaDataEd::onSizeChanged()
|
||||||
|
@ -209,7 +240,8 @@ void GuiMetaDataEd::onSizeChanged()
|
||||||
const float subtitleHeight = mSubtitle->getFont()->getLetterHeight();
|
const float subtitleHeight = mSubtitle->getFont()->getLetterHeight();
|
||||||
const float titleSubtitleSpacing = mSize.y() * 0.03f;
|
const float titleSubtitleSpacing = mSize.y() * 0.03f;
|
||||||
|
|
||||||
mGrid.setRowHeightPerc(0, (titleHeight + titleSubtitleSpacing + subtitleHeight + TITLE_VERT_PADDING) / mSize.y());
|
mGrid.setRowHeightPerc(0, (titleHeight + titleSubtitleSpacing + subtitleHeight +
|
||||||
|
TITLE_VERT_PADDING) / mSize.y());
|
||||||
mGrid.setRowHeightPerc(2, mButtons->getSize().y() / mSize.y());
|
mGrid.setRowHeightPerc(2, mButtons->getSize().y() / mSize.y());
|
||||||
|
|
||||||
mHeaderGrid->setRowHeightPerc(1, titleHeight / mHeaderGrid->getSize().y());
|
mHeaderGrid->setRowHeightPerc(1, titleHeight / mHeaderGrid->getSize().y());
|
||||||
|
@ -219,24 +251,23 @@ void GuiMetaDataEd::onSizeChanged()
|
||||||
|
|
||||||
void GuiMetaDataEd::save()
|
void GuiMetaDataEd::save()
|
||||||
{
|
{
|
||||||
// remove game from index
|
// Remove game from index.
|
||||||
mScraperParams.system->getIndex()->removeFromIndex(mScraperParams.game);
|
mScraperParams.system->getIndex()->removeFromIndex(mScraperParams.game);
|
||||||
|
|
||||||
for(unsigned int i = 0; i < mEditors.size(); i++)
|
for (unsigned int i = 0; i < mEditors.size(); i++) {
|
||||||
{
|
if (mMetaDataDecl.at(i).isStatistic)
|
||||||
if(mMetaDataDecl.at(i).isStatistic)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
mMetaData->set(mMetaDataDecl.at(i).key, mEditors.at(i)->getValue());
|
mMetaData->set(mMetaDataDecl.at(i).key, mEditors.at(i)->getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// enter game in index
|
// Enter game in index.
|
||||||
mScraperParams.system->getIndex()->addToIndex(mScraperParams.game);
|
mScraperParams.system->getIndex()->addToIndex(mScraperParams.game);
|
||||||
|
|
||||||
if(mSavedCallback)
|
if (mSavedCallback)
|
||||||
mSavedCallback();
|
mSavedCallback();
|
||||||
|
|
||||||
// update respective Collection Entries
|
// Update respective Collection Entries.
|
||||||
CollectionSystemManager::get()->refreshCollectionSystems(mScraperParams.game);
|
CollectionSystemManager::get()->refreshCollectionSystems(mScraperParams.game);
|
||||||
|
|
||||||
mScraperParams.system->onMetaDataSavePoint();
|
mScraperParams.system->onMetaDataSavePoint();
|
||||||
|
@ -244,15 +275,15 @@ void GuiMetaDataEd::save()
|
||||||
|
|
||||||
void GuiMetaDataEd::fetch()
|
void GuiMetaDataEd::fetch()
|
||||||
{
|
{
|
||||||
GuiGameScraper* scr = new GuiGameScraper(mWindow, mScraperParams, std::bind(&GuiMetaDataEd::fetchDone, this, std::placeholders::_1));
|
GuiGameScraper* scr = new GuiGameScraper(mWindow, mScraperParams,
|
||||||
|
std::bind(&GuiMetaDataEd::fetchDone, this, std::placeholders::_1));
|
||||||
mWindow->pushGui(scr);
|
mWindow->pushGui(scr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
|
void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
|
||||||
{
|
{
|
||||||
for(unsigned int i = 0; i < mEditors.size(); i++)
|
for (unsigned int i = 0; i < mEditors.size(); i++) {
|
||||||
{
|
if (mMetaDataDecl.at(i).isStatistic)
|
||||||
if(mMetaDataDecl.at(i).isStatistic)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const std::string& key = mMetaDataDecl.at(i).key;
|
const std::string& key = mMetaDataDecl.at(i).key;
|
||||||
|
@ -262,52 +293,50 @@ void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
|
||||||
|
|
||||||
void GuiMetaDataEd::close(bool closeAllWindows)
|
void GuiMetaDataEd::close(bool closeAllWindows)
|
||||||
{
|
{
|
||||||
// find out if the user made any changes
|
// Find out if the user made any changes.
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
for(unsigned int i = 0; i < mEditors.size(); i++)
|
for (unsigned int i = 0; i < mEditors.size(); i++) {
|
||||||
{
|
|
||||||
const std::string& key = mMetaDataDecl.at(i).key;
|
const std::string& key = mMetaDataDecl.at(i).key;
|
||||||
if(mMetaData->get(key) != mEditors.at(i)->getValue())
|
if (mMetaData->get(key) != mEditors.at(i)->getValue()) {
|
||||||
{
|
|
||||||
dirty = true;
|
dirty = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::function<void()> closeFunc;
|
std::function<void()> closeFunc;
|
||||||
if(!closeAllWindows)
|
if (!closeAllWindows) {
|
||||||
{
|
|
||||||
closeFunc = [this] { delete this; };
|
closeFunc = [this] { delete this; };
|
||||||
}else{
|
}
|
||||||
|
else {
|
||||||
Window* window = mWindow;
|
Window* window = mWindow;
|
||||||
closeFunc = [window, this] {
|
closeFunc = [window, this] {
|
||||||
while(window->peekGui() != ViewController::get())
|
while (window->peekGui() != ViewController::get())
|
||||||
delete window->peekGui();
|
delete window->peekGui();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(dirty)
|
if (dirty)
|
||||||
{
|
{
|
||||||
// changes were made, ask if the user wants to save them
|
// Changes were made, ask if the user wants to save them.
|
||||||
mWindow->pushGui(new GuiMsgBox(mWindow,
|
mWindow->pushGui(new GuiMsgBox(mWindow,
|
||||||
"SAVE CHANGES?",
|
"SAVE CHANGES?",
|
||||||
"YES", [this, closeFunc] { save(); closeFunc(); },
|
"YES", [this, closeFunc] { save(); closeFunc(); },
|
||||||
"NO", closeFunc
|
"NO", closeFunc
|
||||||
));
|
));
|
||||||
}else{
|
}
|
||||||
|
else {
|
||||||
closeFunc();
|
closeFunc();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GuiMetaDataEd::input(InputConfig* config, Input input)
|
bool GuiMetaDataEd::input(InputConfig* config, Input input)
|
||||||
{
|
{
|
||||||
if(GuiComponent::input(config, input))
|
if (GuiComponent::input(config, input))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
const bool isStart = config->isMappedTo("start", input);
|
const bool isStart = config->isMappedTo("start", input);
|
||||||
if(input.value != 0 && (config->isMappedTo("b", input) || isStart))
|
if (input.value != 0 && (config->isMappedTo("b", input) || isStart)) {
|
||||||
{
|
|
||||||
close(isStart);
|
close(isStart);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
//
|
||||||
|
// GuiMetaDataEd.h
|
||||||
|
//
|
||||||
|
// Game metadata edit user interface.
|
||||||
|
// This interface is triggered from the GuiGamelistOptions menu.
|
||||||
|
// The scraping interface is handled by GuiGameScraper which calls
|
||||||
|
// ScraperSearchComponent.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_APP_GUIS_GUI_META_DATA_ED_H
|
#ifndef ES_APP_GUIS_GUI_META_DATA_ED_H
|
||||||
#define ES_APP_GUIS_GUI_META_DATA_ED_H
|
#define ES_APP_GUIS_GUI_META_DATA_ED_H
|
||||||
|
@ -14,8 +23,13 @@ class TextComponent;
|
||||||
class GuiMetaDataEd : public GuiComponent
|
class GuiMetaDataEd : public GuiComponent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector<MetaDataDecl>& mdd, ScraperSearchParams params,
|
GuiMetaDataEd(
|
||||||
const std::string& header, std::function<void()> savedCallback, std::function<void()> deleteFunc);
|
Window* window,
|
||||||
|
MetaDataList* md, const std::vector<MetaDataDecl>&mdd,
|
||||||
|
ScraperSearchParams params,
|
||||||
|
const std::string& header,
|
||||||
|
std::function<void()> savedCallback,
|
||||||
|
std::function<void()> deleteFunc);
|
||||||
|
|
||||||
bool input(InputConfig* config, Input input) override;
|
bool input(InputConfig* config, Input input) override;
|
||||||
void onSizeChanged() override;
|
void onSizeChanged() override;
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
//
|
||||||
|
// GuiScraperMulti.cpp
|
||||||
|
//
|
||||||
|
// Multiple game scraping user interface.
|
||||||
|
// Shows the progress for the scraping as it's running.
|
||||||
|
// This interface is triggered from the GuiScraperStart menu.
|
||||||
|
// ScraperSearchComponent is called from here.
|
||||||
|
//
|
||||||
|
|
||||||
#include "guis/GuiScraperMulti.h"
|
#include "guis/GuiScraperMulti.h"
|
||||||
|
|
||||||
#include "components/ButtonComponent.h"
|
#include "components/ButtonComponent.h"
|
||||||
|
@ -11,9 +20,14 @@
|
||||||
#include "SystemData.h"
|
#include "SystemData.h"
|
||||||
#include "Window.h"
|
#include "Window.h"
|
||||||
|
|
||||||
GuiScraperMulti::GuiScraperMulti(Window* window, const std::queue<ScraperSearchParams>& searches, bool approveResults) :
|
GuiScraperMulti::GuiScraperMulti(
|
||||||
GuiComponent(window), mBackground(window, ":/frame.png"), mGrid(window, Vector2i(1, 5)),
|
Window* window,
|
||||||
mSearchQueue(searches)
|
const std::queue<ScraperSearchParams>& searches,
|
||||||
|
bool approveResults)
|
||||||
|
: GuiComponent(window),
|
||||||
|
mBackground(window, ":/frame.png"),
|
||||||
|
mGrid(window, Vector2i(1, 5)),
|
||||||
|
mSearchQueue(searches)
|
||||||
{
|
{
|
||||||
assert(mSearchQueue.size());
|
assert(mSearchQueue.size());
|
||||||
|
|
||||||
|
@ -28,27 +42,32 @@ GuiScraperMulti::GuiScraperMulti(Window* window, const std::queue<ScraperSearchP
|
||||||
mTotalSuccessful = 0;
|
mTotalSuccessful = 0;
|
||||||
mTotalSkipped = 0;
|
mTotalSkipped = 0;
|
||||||
|
|
||||||
// set up grid
|
// Set up grid.
|
||||||
mTitle = std::make_shared<TextComponent>(mWindow, "SCRAPING IN PROGRESS", Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER);
|
mTitle = std::make_shared<TextComponent>(mWindow, "SCRAPING IN PROGRESS",
|
||||||
|
Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER);
|
||||||
mGrid.setEntry(mTitle, Vector2i(0, 0), false, true);
|
mGrid.setEntry(mTitle, Vector2i(0, 0), false, true);
|
||||||
|
|
||||||
mSystem = std::make_shared<TextComponent>(mWindow, "SYSTEM", Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER);
|
mSystem = std::make_shared<TextComponent>(mWindow, "SYSTEM",
|
||||||
|
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER);
|
||||||
mGrid.setEntry(mSystem, Vector2i(0, 1), false, true);
|
mGrid.setEntry(mSystem, Vector2i(0, 1), false, true);
|
||||||
|
|
||||||
mSubtitle = std::make_shared<TextComponent>(mWindow, "subtitle text", Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER);
|
mSubtitle = std::make_shared<TextComponent>(mWindow, "subtitle text",
|
||||||
|
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER);
|
||||||
mGrid.setEntry(mSubtitle, Vector2i(0, 2), false, true);
|
mGrid.setEntry(mSubtitle, Vector2i(0, 2), false, true);
|
||||||
|
|
||||||
mSearchComp = std::make_shared<ScraperSearchComponent>(mWindow,
|
mSearchComp = std::make_shared<ScraperSearchComponent>(mWindow,
|
||||||
approveResults ? ScraperSearchComponent::ALWAYS_ACCEPT_MATCHING_CRC : ScraperSearchComponent::ALWAYS_ACCEPT_FIRST_RESULT);
|
approveResults ? ScraperSearchComponent::ALWAYS_ACCEPT_MATCHING_CRC
|
||||||
mSearchComp->setAcceptCallback(std::bind(&GuiScraperMulti::acceptResult, this, std::placeholders::_1));
|
: ScraperSearchComponent::ALWAYS_ACCEPT_FIRST_RESULT);
|
||||||
|
mSearchComp->setAcceptCallback(std::bind(&GuiScraperMulti::acceptResult,
|
||||||
|
this, std::placeholders::_1));
|
||||||
mSearchComp->setSkipCallback(std::bind(&GuiScraperMulti::skip, this));
|
mSearchComp->setSkipCallback(std::bind(&GuiScraperMulti::skip, this));
|
||||||
mSearchComp->setCancelCallback(std::bind(&GuiScraperMulti::finish, this));
|
mSearchComp->setCancelCallback(std::bind(&GuiScraperMulti::finish, this));
|
||||||
mGrid.setEntry(mSearchComp, Vector2i(0, 3), mSearchComp->getSearchType() != ScraperSearchComponent::ALWAYS_ACCEPT_FIRST_RESULT, true);
|
mGrid.setEntry(mSearchComp, Vector2i(0, 3), mSearchComp->getSearchType() !=
|
||||||
|
ScraperSearchComponent::ALWAYS_ACCEPT_FIRST_RESULT, true);
|
||||||
|
|
||||||
std::vector< std::shared_ptr<ButtonComponent> > buttons;
|
std::vector< std::shared_ptr<ButtonComponent> > buttons;
|
||||||
|
|
||||||
if(approveResults)
|
if (approveResults) {
|
||||||
{
|
|
||||||
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "INPUT", "search", [&] {
|
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "INPUT", "search", [&] {
|
||||||
mSearchComp->openInputScreen(mSearchQueue.front());
|
mSearchComp->openInputScreen(mSearchQueue.front());
|
||||||
mGrid.resetCursor();
|
mGrid.resetCursor();
|
||||||
|
@ -60,21 +79,24 @@ GuiScraperMulti::GuiScraperMulti(Window* window, const std::queue<ScraperSearchP
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "STOP", "stop (progress saved)", std::bind(&GuiScraperMulti::finish, this)));
|
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "STOP",
|
||||||
|
"stop (progress saved)", std::bind(&GuiScraperMulti::finish, this)));
|
||||||
|
|
||||||
mButtonGrid = makeButtonGrid(mWindow, buttons);
|
mButtonGrid = makeButtonGrid(mWindow, buttons);
|
||||||
mGrid.setEntry(mButtonGrid, Vector2i(0, 4), true, false);
|
mGrid.setEntry(mButtonGrid, Vector2i(0, 4), true, false);
|
||||||
|
|
||||||
setSize(Renderer::getScreenWidth() * 0.95f, Renderer::getScreenHeight() * 0.849f);
|
setSize(Renderer::getScreenWidth() * 0.95f, Renderer::getScreenHeight() * 0.849f);
|
||||||
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, (Renderer::getScreenHeight() - mSize.y()) / 2);
|
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, (Renderer::getScreenHeight() -
|
||||||
|
mSize.y()) / 2);
|
||||||
|
|
||||||
doNextSearch();
|
doNextSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
GuiScraperMulti::~GuiScraperMulti()
|
GuiScraperMulti::~GuiScraperMulti()
|
||||||
{
|
{
|
||||||
// view type probably changed (basic -> detailed)
|
// View type probably changed (basic -> detailed).
|
||||||
for(auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); it++)
|
for (auto it = SystemData::sSystemVector.cbegin();
|
||||||
|
it !=SystemData::sSystemVector.cend(); it++)
|
||||||
ViewController::get()->reloadGameListView(*it, false);
|
ViewController::get()->reloadGameListView(*it, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,19 +113,20 @@ void GuiScraperMulti::onSizeChanged()
|
||||||
|
|
||||||
void GuiScraperMulti::doNextSearch()
|
void GuiScraperMulti::doNextSearch()
|
||||||
{
|
{
|
||||||
if(mSearchQueue.empty())
|
if (mSearchQueue.empty()) {
|
||||||
{
|
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update title
|
// Update title.
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
mSystem->setText(Utils::String::toUpper(mSearchQueue.front().system->getFullName()));
|
mSystem->setText(Utils::String::toUpper(mSearchQueue.front().system->getFullName()));
|
||||||
|
|
||||||
// update subtitle
|
// Update subtitle.
|
||||||
ss.str(""); // clear
|
ss.str(""); // Clear.
|
||||||
ss << "GAME " << (mCurrentGame + 1) << " OF " << mTotalGames << " - " << Utils::String::toUpper(Utils::FileSystem::getFileName(mSearchQueue.front().game->getPath()));
|
ss << "GAME " << (mCurrentGame + 1) << " OF " << mTotalGames << " - " <<
|
||||||
|
Utils::String::toUpper(Utils::FileSystem::getFileName(
|
||||||
|
mSearchQueue.front().game->getPath()));
|
||||||
mSubtitle->setText(ss.str());
|
mSubtitle->setText(ss.str());
|
||||||
|
|
||||||
mSearchComp->search(mSearchQueue.front());
|
mSearchComp->search(mSearchQueue.front());
|
||||||
|
@ -133,18 +156,20 @@ void GuiScraperMulti::skip()
|
||||||
void GuiScraperMulti::finish()
|
void GuiScraperMulti::finish()
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
if(mTotalSuccessful == 0)
|
if (mTotalSuccessful == 0) {
|
||||||
{
|
|
||||||
ss << "NO GAMES WERE SCRAPED.";
|
ss << "NO GAMES WERE SCRAPED.";
|
||||||
}else{
|
}
|
||||||
ss << mTotalSuccessful << " GAME" << ((mTotalSuccessful > 1) ? "S" : "") << " SUCCESSFULLY SCRAPED!";
|
else {
|
||||||
|
ss << mTotalSuccessful << " GAME" << ((mTotalSuccessful > 1) ? "S" : "") <<
|
||||||
|
" SUCCESSFULLY SCRAPED!";
|
||||||
|
|
||||||
if(mTotalSkipped > 0)
|
if (mTotalSkipped > 0)
|
||||||
ss << "\n" << mTotalSkipped << " GAME" << ((mTotalSkipped > 1) ? "S" : "") << " SKIPPED.";
|
ss << "\n" << mTotalSkipped << " GAME" << ((mTotalSkipped > 1) ? "S" : "") <<
|
||||||
|
" SKIPPED.";
|
||||||
}
|
}
|
||||||
|
|
||||||
mWindow->pushGui(new GuiMsgBox(mWindow, ss.str(),
|
mWindow->pushGui(new GuiMsgBox(mWindow, ss.str(), "OK", [&] {
|
||||||
"OK", [&] { delete this; }));
|
delete this; }));
|
||||||
|
|
||||||
mIsProcessing = false;
|
mIsProcessing = false;
|
||||||
PowerSaver::resume();
|
PowerSaver::resume();
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
//
|
||||||
|
// GuiScraperMulti.h
|
||||||
|
//
|
||||||
|
// Multiple game scraping user interface.
|
||||||
|
// Shows the progress for the scraping as it's running.
|
||||||
|
// This interface is triggered from the GuiScraperStart menu.
|
||||||
|
// ScraperSearchComponent is called from here.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_APP_GUIS_GUI_SCRAPER_MULTI_H
|
#ifndef ES_APP_GUIS_GUI_SCRAPER_MULTI_H
|
||||||
#define ES_APP_GUIS_GUI_SCRAPER_MULTI_H
|
#define ES_APP_GUIS_GUI_SCRAPER_MULTI_H
|
||||||
|
@ -13,7 +22,11 @@ class TextComponent;
|
||||||
class GuiScraperMulti : public GuiComponent
|
class GuiScraperMulti : public GuiComponent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
GuiScraperMulti(Window* window, const std::queue<ScraperSearchParams>& searches, bool approveResults);
|
GuiScraperMulti(
|
||||||
|
Window* window,
|
||||||
|
const std::queue<ScraperSearchParams>& searches,
|
||||||
|
bool approveResults);
|
||||||
|
|
||||||
virtual ~GuiScraperMulti();
|
virtual ~GuiScraperMulti();
|
||||||
|
|
||||||
void onSizeChanged() override;
|
void onSizeChanged() override;
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
//
|
||||||
|
// GuiScraperStart.cpp
|
||||||
|
//
|
||||||
|
// Submenu to the GuiMenu main menu.
|
||||||
|
// Configuration options for the scraper and start button to intiate the scraping.
|
||||||
|
//
|
||||||
|
|
||||||
#include "guis/GuiScraperStart.h"
|
#include "guis/GuiScraperStart.h"
|
||||||
|
|
||||||
#include "components/OptionListComponent.h"
|
#include "components/OptionListComponent.h"
|
||||||
|
@ -13,19 +20,22 @@ GuiScraperStart::GuiScraperStart(Window* window) : GuiComponent(window),
|
||||||
{
|
{
|
||||||
addChild(&mMenu);
|
addChild(&mMenu);
|
||||||
|
|
||||||
// add filters (with first one selected)
|
// Add filters (with first one selected).
|
||||||
mFilters = std::make_shared< OptionListComponent<GameFilterFunc> >(mWindow, "SCRAPE THESE GAMES", false);
|
mFilters = std::make_shared< OptionListComponent<GameFilterFunc>
|
||||||
|
>(mWindow, "SCRAPE THESE GAMES", false);
|
||||||
mFilters->add("All Games",
|
mFilters->add("All Games",
|
||||||
[](SystemData*, FileData*) -> bool { return true; }, false);
|
[](SystemData*, FileData*) -> bool { return true; }, false);
|
||||||
mFilters->add("Only missing image",
|
mFilters->add("Only missing image",
|
||||||
[](SystemData*, FileData* g) -> bool { return g->metadata.get("image").empty(); }, true);
|
[](SystemData*, FileData* g) -> bool {
|
||||||
|
return g->metadata.get("image").empty(); }, true);
|
||||||
mMenu.addWithLabel("Filter", mFilters);
|
mMenu.addWithLabel("Filter", mFilters);
|
||||||
|
|
||||||
//add systems (all with a platformid specified selected)
|
// Add systems (all systems with an existing platform ID are listed).
|
||||||
mSystems = std::make_shared< OptionListComponent<SystemData*> >(mWindow, "SCRAPE THESE SYSTEMS", true);
|
mSystems = std::make_shared< OptionListComponent<SystemData*>
|
||||||
for(auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); it++)
|
>(mWindow, "SCRAPE THESE SYSTEMS", true);
|
||||||
{
|
for (auto it = SystemData::sSystemVector.cbegin();
|
||||||
if(!(*it)->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
|
it != SystemData::sSystemVector.cend(); it++) {
|
||||||
|
if (!(*it)->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
|
||||||
mSystems->add((*it)->getFullName(), *it, !(*it)->getPlatformIds().empty());
|
mSystems->add((*it)->getFullName(), *it, !(*it)->getPlatformIds().empty());
|
||||||
}
|
}
|
||||||
mMenu.addWithLabel("Systems", mSystems);
|
mMenu.addWithLabel("Systems", mSystems);
|
||||||
|
@ -37,18 +47,19 @@ GuiScraperStart::GuiScraperStart(Window* window) : GuiComponent(window),
|
||||||
mMenu.addButton("START", "start", std::bind(&GuiScraperStart::pressedStart, this));
|
mMenu.addButton("START", "start", std::bind(&GuiScraperStart::pressedStart, this));
|
||||||
mMenu.addButton("BACK", "back", [&] { delete this; });
|
mMenu.addButton("BACK", "back", [&] { delete this; });
|
||||||
|
|
||||||
mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2, Renderer::getScreenHeight() * 0.15f);
|
mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2,
|
||||||
|
Renderer::getScreenHeight() * 0.15f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiScraperStart::pressedStart()
|
void GuiScraperStart::pressedStart()
|
||||||
{
|
{
|
||||||
std::vector<SystemData*> sys = mSystems->getSelectedObjects();
|
std::vector<SystemData*> sys = mSystems->getSelectedObjects();
|
||||||
for(auto it = sys.cbegin(); it != sys.cend(); it++)
|
for (auto it = sys.cbegin(); it != sys.cend(); it++) {
|
||||||
{
|
if ((*it)->getPlatformIds().empty()) {
|
||||||
if((*it)->getPlatformIds().empty())
|
|
||||||
{
|
|
||||||
mWindow->pushGui(new GuiMsgBox(mWindow,
|
mWindow->pushGui(new GuiMsgBox(mWindow,
|
||||||
Utils::String::toUpper("Warning: some of your selected systems do not have a platform set. Results may be even more inaccurate than usual!\nContinue anyway?"),
|
Utils::String::toUpper("Warning: some of your selected systems do not "
|
||||||
|
"have a platform set. Results may be even more inaccurate than "
|
||||||
|
"usual!\nContinue anyway?"),
|
||||||
"YES", std::bind(&GuiScraperStart::start, this),
|
"YES", std::bind(&GuiScraperStart::start, this),
|
||||||
"NO", nullptr));
|
"NO", nullptr));
|
||||||
return;
|
return;
|
||||||
|
@ -60,29 +71,28 @@ void GuiScraperStart::pressedStart()
|
||||||
|
|
||||||
void GuiScraperStart::start()
|
void GuiScraperStart::start()
|
||||||
{
|
{
|
||||||
std::queue<ScraperSearchParams> searches = getSearches(mSystems->getSelectedObjects(), mFilters->getSelected());
|
std::queue<ScraperSearchParams> searches = getSearches(mSystems->getSelectedObjects(),
|
||||||
|
mFilters->getSelected());
|
||||||
|
|
||||||
if(searches.empty())
|
if (searches.empty()) {
|
||||||
{
|
|
||||||
mWindow->pushGui(new GuiMsgBox(mWindow,
|
mWindow->pushGui(new GuiMsgBox(mWindow,
|
||||||
"NO GAMES FIT THAT CRITERIA."));
|
"NO GAMES FIT THAT CRITERIA."));
|
||||||
}else{
|
}
|
||||||
|
else {
|
||||||
GuiScraperMulti* gsm = new GuiScraperMulti(mWindow, searches, mApproveResults->getState());
|
GuiScraperMulti* gsm = new GuiScraperMulti(mWindow, searches, mApproveResults->getState());
|
||||||
mWindow->pushGui(gsm);
|
mWindow->pushGui(gsm);
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::queue<ScraperSearchParams> GuiScraperStart::getSearches(std::vector<SystemData*> systems, GameFilterFunc selector)
|
std::queue<ScraperSearchParams> GuiScraperStart::getSearches(
|
||||||
|
std::vector<SystemData*> systems, GameFilterFunc selector)
|
||||||
{
|
{
|
||||||
std::queue<ScraperSearchParams> queue;
|
std::queue<ScraperSearchParams> queue;
|
||||||
for(auto sys = systems.cbegin(); sys != systems.cend(); sys++)
|
for (auto sys = systems.cbegin(); sys != systems.cend(); sys++) {
|
||||||
{
|
|
||||||
std::vector<FileData*> games = (*sys)->getRootFolder()->getFilesRecursive(GAME);
|
std::vector<FileData*> games = (*sys)->getRootFolder()->getFilesRecursive(GAME);
|
||||||
for(auto game = games.cbegin(); game != games.cend(); game++)
|
for (auto game = games.cbegin(); game != games.cend(); game++) {
|
||||||
{
|
if (selector((*sys), (*game))) {
|
||||||
if(selector((*sys), (*game)))
|
|
||||||
{
|
|
||||||
ScraperSearchParams search;
|
ScraperSearchParams search;
|
||||||
search.game = *game;
|
search.game = *game;
|
||||||
search.system = *sys;
|
search.system = *sys;
|
||||||
|
@ -91,31 +101,27 @@ std::queue<ScraperSearchParams> GuiScraperStart::getSearches(std::vector<SystemD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return queue;
|
return queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GuiScraperStart::input(InputConfig* config, Input input)
|
bool GuiScraperStart::input(InputConfig* config, Input input)
|
||||||
{
|
{
|
||||||
bool consumed = GuiComponent::input(config, input);
|
bool consumed = GuiComponent::input(config, input);
|
||||||
if(consumed)
|
if (consumed)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if(input.value != 0 && config->isMappedTo("b", input))
|
if (input.value != 0 && config->isMappedTo("b", input)) {
|
||||||
{
|
|
||||||
delete this;
|
delete this;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config->isMappedTo("start", input) && input.value != 0)
|
if (config->isMappedTo("start", input) && input.value != 0) {
|
||||||
{
|
// Close everything.
|
||||||
// close everything
|
|
||||||
Window* window = mWindow;
|
Window* window = mWindow;
|
||||||
while(window->peekGui() && window->peekGui() != ViewController::get())
|
while (window->peekGui() && window->peekGui() != ViewController::get())
|
||||||
delete window->peekGui();
|
delete window->peekGui();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
//
|
||||||
|
// GuiScraperStart.h
|
||||||
|
//
|
||||||
|
// Submenu to the GuiMenu main menu.
|
||||||
|
// Configuration options for the scraper and start button to intiate the scraping.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_APP_GUIS_GUI_SCRAPER_START_H
|
#ifndef ES_APP_GUIS_GUI_SCRAPER_START_H
|
||||||
#define ES_APP_GUIS_GUI_SCRAPER_START_H
|
#define ES_APP_GUIS_GUI_SCRAPER_START_H
|
||||||
|
@ -13,9 +20,10 @@ class SystemData;
|
||||||
|
|
||||||
typedef std::function<bool(SystemData*, FileData*)> GameFilterFunc;
|
typedef std::function<bool(SystemData*, FileData*)> GameFilterFunc;
|
||||||
|
|
||||||
//The starting point for a multi-game scrape.
|
// The starting point for a multi-game scrape.
|
||||||
//Allows the user to set various parameters (to set filters, to set which systems to scrape, to enable manual mode).
|
// Allows the user to set various parameters (filters, which systems to scrape and
|
||||||
//Generates a list of "searches" that will be carried out by GuiScraperLog.
|
// whether to use manual mode). Generates a list of "searches" that will be carried
|
||||||
|
// out by GuiScraperLog.
|
||||||
class GuiScraperStart : public GuiComponent
|
class GuiScraperStart : public GuiComponent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -28,7 +36,8 @@ public:
|
||||||
private:
|
private:
|
||||||
void pressedStart();
|
void pressedStart();
|
||||||
void start();
|
void start();
|
||||||
std::queue<ScraperSearchParams> getSearches(std::vector<SystemData*> systems, GameFilterFunc selector);
|
std::queue<ScraperSearchParams> getSearches(
|
||||||
|
std::vector<SystemData*> systems, GameFilterFunc selector);
|
||||||
|
|
||||||
std::shared_ptr< OptionListComponent<GameFilterFunc> > mFilters;
|
std::shared_ptr< OptionListComponent<GameFilterFunc> > mFilters;
|
||||||
std::shared_ptr< OptionListComponent<SystemData*> > mSystems;
|
std::shared_ptr< OptionListComponent<SystemData*> > mSystems;
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
// Improved and extended by the RetroPie community.
|
// Improved and extended by the RetroPie community.
|
||||||
// Desktop Edition fork by Leon Styhre.
|
// Desktop Edition fork by Leon Styhre.
|
||||||
//
|
//
|
||||||
|
// The line length limit is 100 characters.
|
||||||
|
//
|
||||||
// main.cpp
|
// main.cpp
|
||||||
//
|
//
|
||||||
// Main program loop. Interprets command-line arguments, checks for the
|
// Main program loop. Interprets command-line arguments, checks for the
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
//
|
||||||
|
// GamesDBJSONScraper.cpp
|
||||||
|
//
|
||||||
|
// Functions specifically for scraping from thegamesdb.net
|
||||||
|
// Called from Scraper.
|
||||||
|
//
|
||||||
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
@ -12,15 +19,14 @@
|
||||||
#include "utils/TimeUtil.h"
|
#include "utils/TimeUtil.h"
|
||||||
#include <pugixml/src/pugixml.hpp>
|
#include <pugixml/src/pugixml.hpp>
|
||||||
|
|
||||||
/* When raspbian will get an up to date version of rapidjson we'll be
|
// When raspbian will get an up to date version of rapidjson we'll be
|
||||||
able to have it throw in case of error with the following:
|
// able to have it throw in case of error with the following:
|
||||||
#ifndef RAPIDJSON_ASSERT
|
//ifndef RAPIDJSON_ASSERT
|
||||||
#define RAPIDJSON_ASSERT(x) \
|
//#define RAPIDJSON_ASSERT(x) \
|
||||||
if (!(x)) { \
|
// if (!(x)) { \
|
||||||
throw std::runtime_error("rapidjson internal assertion failure: " #x); \
|
// throw std::runtime_error("rapidjson internal assertion failure: " #x); \
|
||||||
}
|
// }
|
||||||
#endif // RAPIDJSON_ASSERT
|
//#endif // RAPIDJSON_ASSERT
|
||||||
*/
|
|
||||||
|
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <rapidjson/error/en.h>
|
#include <rapidjson/error/en.h>
|
||||||
|
@ -28,12 +34,11 @@
|
||||||
using namespace PlatformIds;
|
using namespace PlatformIds;
|
||||||
using namespace rapidjson;
|
using namespace rapidjson;
|
||||||
|
|
||||||
namespace
|
namespace {
|
||||||
{
|
|
||||||
TheGamesDBJSONRequestResources resources;
|
TheGamesDBJSONRequestResources resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::map<PlatformId, std::string> gamesdb_new_platformid_map{
|
const std::map<PlatformId, std::string> gamesdb_new_platformid_map {
|
||||||
{ THREEDO, "25" },
|
{ THREEDO, "25" },
|
||||||
{ AMIGA, "4911" },
|
{ AMIGA, "4911" },
|
||||||
{ AMSTRAD_CPC, "4914" },
|
{ AMSTRAD_CPC, "4914" },
|
||||||
|
@ -88,8 +93,8 @@ const std::map<PlatformId, std::string> gamesdb_new_platformid_map{
|
||||||
{ PLAYSTATION_VITA, "39" },
|
{ PLAYSTATION_VITA, "39" },
|
||||||
{ PLAYSTATION_PORTABLE, "13" },
|
{ PLAYSTATION_PORTABLE, "13" },
|
||||||
{ SUPER_NINTENDO, "6" },
|
{ SUPER_NINTENDO, "6" },
|
||||||
{ TURBOGRAFX_16, "34" }, // HuCards only
|
{ TURBOGRAFX_16, "34" }, // HuCards only.
|
||||||
{ TURBOGRAFX_CD, "4955" }, // CD-ROMs only
|
{ TURBOGRAFX_CD, "4955" }, // CD-ROMs only.
|
||||||
{ WONDERSWAN, "4925" },
|
{ WONDERSWAN, "4925" },
|
||||||
{ WONDERSWAN_COLOR, "4926" },
|
{ WONDERSWAN_COLOR, "4926" },
|
||||||
{ ZX_SPECTRUM, "4913" },
|
{ ZX_SPECTRUM, "4913" },
|
||||||
|
@ -99,16 +104,17 @@ const std::map<PlatformId, std::string> gamesdb_new_platformid_map{
|
||||||
{ TANDY, "4941" },
|
{ TANDY, "4941" },
|
||||||
};
|
};
|
||||||
|
|
||||||
void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params,
|
void thegamesdb_generate_json_scraper_requests(
|
||||||
std::queue<std::unique_ptr<ScraperRequest>>& requests, std::vector<ScraperSearchResult>& results)
|
const ScraperSearchParams& params,
|
||||||
|
std::queue<std::unique_ptr<ScraperRequest>>& requests,
|
||||||
|
std::vector<ScraperSearchResult>& results)
|
||||||
{
|
{
|
||||||
resources.prepare();
|
resources.prepare();
|
||||||
std::string path = "https://api.thegamesdb.net/v1";
|
std::string path = "https://api.thegamesdb.net/v1";
|
||||||
bool usingGameID = false;
|
bool usingGameID = false;
|
||||||
const std::string apiKey = std::string("apikey=") + resources.getApiKey();
|
const std::string apiKey = std::string("apikey=") + resources.getApiKey();
|
||||||
std::string cleanName = params.nameOverride;
|
std::string cleanName = params.nameOverride;
|
||||||
if (!cleanName.empty() && cleanName.substr(0, 3) == "id:")
|
if (!cleanName.empty() && cleanName.substr(0, 3) == "id:") {
|
||||||
{
|
|
||||||
std::string gameID = cleanName.substr(3);
|
std::string gameID = cleanName.substr(3);
|
||||||
path += "/Games/ByGameID?" + apiKey +
|
path += "/Games/ByGameID?" + apiKey +
|
||||||
"&fields=players,publishers,genres,overview,last_updated,rating,"
|
"&fields=players,publishers,genres,overview,last_updated,rating,"
|
||||||
|
@ -116,42 +122,37 @@ void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params
|
||||||
"include=boxart&id=" +
|
"include=boxart&id=" +
|
||||||
HttpReq::urlEncode(gameID);
|
HttpReq::urlEncode(gameID);
|
||||||
usingGameID = true;
|
usingGameID = true;
|
||||||
} else
|
}
|
||||||
{
|
else {
|
||||||
if (cleanName.empty())
|
if (cleanName.empty())
|
||||||
cleanName = params.game->getCleanName();
|
cleanName = params.game->getCleanName();
|
||||||
path += "/Games/ByGameName?" + apiKey +
|
path += "/Games/ByGameName?" + apiKey +
|
||||||
"&fields=players,publishers,genres,overview,last_updated,rating,"
|
"&fields=players,publishers,genres,overview,last_updated,rating,"
|
||||||
"platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates&"
|
"platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates&"
|
||||||
"include=boxart&name=" +
|
"include=boxart&name=" +
|
||||||
HttpReq::urlEncode(cleanName);
|
HttpReq::urlEncode(cleanName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usingGameID)
|
if (usingGameID) {
|
||||||
{
|
// Ff we have the ID already, we don't need the GetGameList request.
|
||||||
// if we have the ID already, we don't need the GetGameList request
|
|
||||||
requests.push(std::unique_ptr<ScraperRequest>(new TheGamesDBJSONRequest(results, path)));
|
requests.push(std::unique_ptr<ScraperRequest>(new TheGamesDBJSONRequest(results, path)));
|
||||||
} else
|
}
|
||||||
{
|
else {
|
||||||
std::string platformQueryParam;
|
std::string platformQueryParam;
|
||||||
auto& platforms = params.system->getPlatformIds();
|
auto& platforms = params.system->getPlatformIds();
|
||||||
if (!platforms.empty())
|
if (!platforms.empty()) {
|
||||||
{
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
platformQueryParam += "&filter%5Bplatform%5D=";
|
platformQueryParam += "&filter%5Bplatform%5D=";
|
||||||
for (auto platformIt = platforms.cbegin(); platformIt != platforms.cend(); platformIt++)
|
for (auto platformIt = platforms.cbegin();
|
||||||
{
|
platformIt != platforms.cend(); platformIt++) {
|
||||||
auto mapIt = gamesdb_new_platformid_map.find(*platformIt);
|
auto mapIt = gamesdb_new_platformid_map.find(*platformIt);
|
||||||
if (mapIt != gamesdb_new_platformid_map.cend())
|
if (mapIt != gamesdb_new_platformid_map.cend()) {
|
||||||
{
|
|
||||||
if (!first)
|
if (!first)
|
||||||
{
|
|
||||||
platformQueryParam += ",";
|
platformQueryParam += ",";
|
||||||
}
|
|
||||||
platformQueryParam += HttpReq::urlEncode(mapIt->second);
|
platformQueryParam += HttpReq::urlEncode(mapIt->second);
|
||||||
first = false;
|
first = false;
|
||||||
} else
|
}
|
||||||
{
|
else {
|
||||||
LOG(LogWarning) << "TheGamesDB scraper warning - no support for platform "
|
LOG(LogWarning) << "TheGamesDB scraper warning - no support for platform "
|
||||||
<< getPlatformName(*platformIt);
|
<< getPlatformName(*platformIt);
|
||||||
}
|
}
|
||||||
|
@ -159,7 +160,8 @@ void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params
|
||||||
path += platformQueryParam;
|
path += platformQueryParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
requests.push(std::unique_ptr<ScraperRequest>(new TheGamesDBJSONRequest(requests, results, path)));
|
requests.push(std::unique_ptr<ScraperRequest>
|
||||||
|
(new TheGamesDBJSONRequest(requests, results, path)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,26 +170,25 @@ namespace
|
||||||
|
|
||||||
std::string getStringOrThrow(const Value& v, const std::string& key)
|
std::string getStringOrThrow(const Value& v, const std::string& key)
|
||||||
{
|
{
|
||||||
if (!v.HasMember(key.c_str()) || !v[key.c_str()].IsString())
|
if (!v.HasMember(key.c_str()) || !v[key.c_str()].IsString()) {
|
||||||
{
|
throw std::runtime_error(
|
||||||
throw std::runtime_error("rapidjson internal assertion failure: missing or non string key:" + key);
|
"rapidjson internal assertion failure: missing or non string key:" + key);
|
||||||
}
|
}
|
||||||
return v[key.c_str()].GetString();
|
return v[key.c_str()].GetString();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getIntOrThrow(const Value& v, const std::string& key)
|
int getIntOrThrow(const Value& v, const std::string& key)
|
||||||
{
|
{
|
||||||
if (!v.HasMember(key.c_str()) || !v[key.c_str()].IsInt())
|
if (!v.HasMember(key.c_str()) || !v[key.c_str()].IsInt()) {
|
||||||
{
|
throw std::runtime_error(
|
||||||
throw std::runtime_error("rapidjson internal assertion failure: missing or non int key:" + key);
|
"rapidjson internal assertion failure: missing or non int key:" + key);
|
||||||
}
|
}
|
||||||
return v[key.c_str()].GetInt();
|
return v[key.c_str()].GetInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getIntOrThrow(const Value& v)
|
int getIntOrThrow(const Value& v)
|
||||||
{
|
{
|
||||||
if (!v.IsInt())
|
if (!v.IsInt()) {
|
||||||
{
|
|
||||||
throw std::runtime_error("rapidjson internal assertion failure: not an int");
|
throw std::runtime_error("rapidjson internal assertion failure: not an int");
|
||||||
}
|
}
|
||||||
return v.GetInt();
|
return v.GetInt();
|
||||||
|
@ -196,18 +197,14 @@ int getIntOrThrow(const Value& v)
|
||||||
std::string getBoxartImage(const Value& v)
|
std::string getBoxartImage(const Value& v)
|
||||||
{
|
{
|
||||||
if (!v.IsArray() || v.Size() == 0)
|
if (!v.IsArray() || v.Size() == 0)
|
||||||
{
|
|
||||||
return "";
|
return "";
|
||||||
}
|
|
||||||
for (int i = 0; i < (int)v.Size(); ++i)
|
for (int i = 0; i < (int)v.Size(); ++i) {
|
||||||
{
|
|
||||||
auto& im = v[i];
|
auto& im = v[i];
|
||||||
std::string type = getStringOrThrow(im, "type");
|
std::string type = getStringOrThrow(im, "type");
|
||||||
std::string side = getStringOrThrow(im, "side");
|
std::string side = getStringOrThrow(im, "side");
|
||||||
if (type == "boxart" && side == "front")
|
if (type == "boxart" && side == "front")
|
||||||
{
|
|
||||||
return getStringOrThrow(im, "filename");
|
return getStringOrThrow(im, "filename");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return getStringOrThrow(v[0], "filename");
|
return getStringOrThrow(v[0], "filename");
|
||||||
}
|
}
|
||||||
|
@ -215,22 +212,19 @@ std::string getBoxartImage(const Value& v)
|
||||||
std::string getDeveloperString(const Value& v)
|
std::string getDeveloperString(const Value& v)
|
||||||
{
|
{
|
||||||
if (!v.IsArray())
|
if (!v.IsArray())
|
||||||
{
|
|
||||||
return "";
|
return "";
|
||||||
}
|
|
||||||
std::string out = "";
|
std::string out = "";
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (int i = 0; i < (int)v.Size(); ++i)
|
for (int i = 0; i < (int)v.Size(); ++i) {
|
||||||
{
|
|
||||||
auto mapIt = resources.gamesdb_new_developers_map.find(getIntOrThrow(v[i]));
|
auto mapIt = resources.gamesdb_new_developers_map.find(getIntOrThrow(v[i]));
|
||||||
|
|
||||||
if (mapIt == resources.gamesdb_new_developers_map.cend())
|
if (mapIt == resources.gamesdb_new_developers_map.cend())
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
if (!first)
|
if (!first)
|
||||||
{
|
|
||||||
out += ", ";
|
out += ", ";
|
||||||
}
|
|
||||||
out += mapIt->second;
|
out += mapIt->second;
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
@ -240,22 +234,19 @@ std::string getDeveloperString(const Value& v)
|
||||||
std::string getPublisherString(const Value& v)
|
std::string getPublisherString(const Value& v)
|
||||||
{
|
{
|
||||||
if (!v.IsArray())
|
if (!v.IsArray())
|
||||||
{
|
|
||||||
return "";
|
return "";
|
||||||
}
|
|
||||||
std::string out = "";
|
std::string out = "";
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (int i = 0; i < (int)v.Size(); ++i)
|
for (int i = 0; i < (int)v.Size(); ++i) {
|
||||||
{
|
|
||||||
auto mapIt = resources.gamesdb_new_publishers_map.find(getIntOrThrow(v[i]));
|
auto mapIt = resources.gamesdb_new_publishers_map.find(getIntOrThrow(v[i]));
|
||||||
|
|
||||||
if (mapIt == resources.gamesdb_new_publishers_map.cend())
|
if (mapIt == resources.gamesdb_new_publishers_map.cend())
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
if (!first)
|
if (!first)
|
||||||
{
|
|
||||||
out += ", ";
|
out += ", ";
|
||||||
}
|
|
||||||
out += mapIt->second;
|
out += mapIt->second;
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
@ -265,22 +256,18 @@ std::string getPublisherString(const Value& v)
|
||||||
std::string getGenreString(const Value& v)
|
std::string getGenreString(const Value& v)
|
||||||
{
|
{
|
||||||
if (!v.IsArray())
|
if (!v.IsArray())
|
||||||
{
|
|
||||||
return "";
|
return "";
|
||||||
}
|
|
||||||
std::string out = "";
|
std::string out = "";
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (int i = 0; i < (int)v.Size(); ++i)
|
for (int i = 0; i < (int)v.Size(); ++i) {
|
||||||
{
|
|
||||||
auto mapIt = resources.gamesdb_new_genres_map.find(getIntOrThrow(v[i]));
|
auto mapIt = resources.gamesdb_new_genres_map.find(getIntOrThrow(v[i]));
|
||||||
if (mapIt == resources.gamesdb_new_genres_map.cend())
|
if (mapIt == resources.gamesdb_new_genres_map.cend())
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
if (!first)
|
if (!first)
|
||||||
{
|
|
||||||
out += ", ";
|
out += ", ";
|
||||||
}
|
|
||||||
out += mapIt->second;
|
out += mapIt->second;
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
@ -296,38 +283,27 @@ void processGame(const Value& game, const Value& boxart, std::vector<ScraperSear
|
||||||
|
|
||||||
result.mdl.set("name", getStringOrThrow(game, "game_title"));
|
result.mdl.set("name", getStringOrThrow(game, "game_title"));
|
||||||
if (game.HasMember("overview") && game["overview"].IsString())
|
if (game.HasMember("overview") && game["overview"].IsString())
|
||||||
{
|
|
||||||
result.mdl.set("desc", game["overview"].GetString());
|
result.mdl.set("desc", game["overview"].GetString());
|
||||||
}
|
|
||||||
if (game.HasMember("release_date") && game["release_date"].IsString())
|
if (game.HasMember("release_date") && game["release_date"].IsString())
|
||||||
{
|
result.mdl.set("releasedate", Utils::Time::DateTime(Utils::Time::stringToTime(
|
||||||
result.mdl.set(
|
game["release_date"].GetString(), "%Y-%m-%d")));
|
||||||
"releasedate", Utils::Time::DateTime(Utils::Time::stringToTime(game["release_date"].GetString(), "%Y-%m-%d")));
|
|
||||||
}
|
|
||||||
if (game.HasMember("developers") && game["developers"].IsArray())
|
if (game.HasMember("developers") && game["developers"].IsArray())
|
||||||
{
|
|
||||||
result.mdl.set("developer", getDeveloperString(game["developers"]));
|
result.mdl.set("developer", getDeveloperString(game["developers"]));
|
||||||
}
|
|
||||||
if (game.HasMember("publishers") && game["publishers"].IsArray())
|
if (game.HasMember("publishers") && game["publishers"].IsArray())
|
||||||
{
|
|
||||||
result.mdl.set("publisher", getPublisherString(game["publishers"]));
|
result.mdl.set("publisher", getPublisherString(game["publishers"]));
|
||||||
}
|
|
||||||
if (game.HasMember("genres") && game["genres"].IsArray())
|
if (game.HasMember("genres") && game["genres"].IsArray())
|
||||||
{
|
|
||||||
|
|
||||||
result.mdl.set("genre", getGenreString(game["genres"]));
|
result.mdl.set("genre", getGenreString(game["genres"]));
|
||||||
}
|
|
||||||
if (game.HasMember("players") && game["players"].IsInt())
|
if (game.HasMember("players") && game["players"].IsInt())
|
||||||
{
|
|
||||||
result.mdl.set("players", std::to_string(game["players"].GetInt()));
|
result.mdl.set("players", std::to_string(game["players"].GetInt()));
|
||||||
}
|
|
||||||
|
|
||||||
|
if (boxart.HasMember("data") && boxart["data"].IsObject()) {
|
||||||
if (boxart.HasMember("data") && boxart["data"].IsObject())
|
|
||||||
{
|
|
||||||
std::string id = std::to_string(getIntOrThrow(game, "id"));
|
std::string id = std::to_string(getIntOrThrow(game, "id"));
|
||||||
if (boxart["data"].HasMember(id.c_str()))
|
if (boxart["data"].HasMember(id.c_str())) {
|
||||||
{
|
|
||||||
std::string image = getBoxartImage(boxart["data"][id.c_str()]);
|
std::string image = getBoxartImage(boxart["data"][id.c_str()]);
|
||||||
result.thumbnailUrl = baseImageUrlThumb + "/" + image;
|
result.thumbnailUrl = baseImageUrlThumb + "/" + image;
|
||||||
result.imageUrl = baseImageUrlLarge + "/" + image;
|
result.imageUrl = baseImageUrlLarge + "/" + image;
|
||||||
|
@ -338,32 +314,32 @@ void processGame(const Value& game, const Value& boxart, std::vector<ScraperSear
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req, std::vector<ScraperSearchResult>& results)
|
void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req,
|
||||||
|
std::vector<ScraperSearchResult>& results)
|
||||||
{
|
{
|
||||||
assert(req->status() == HttpReq::REQ_SUCCESS);
|
assert(req->status() == HttpReq::REQ_SUCCESS);
|
||||||
|
|
||||||
Document doc;
|
Document doc;
|
||||||
doc.Parse(req->getContent().c_str());
|
doc.Parse(req->getContent().c_str());
|
||||||
|
|
||||||
if (doc.HasParseError())
|
if (doc.HasParseError()) {
|
||||||
{
|
|
||||||
std::string err =
|
std::string err =
|
||||||
std::string("TheGamesDBJSONRequest - Error parsing JSON. \n\t") + GetParseError_En(doc.GetParseError());
|
std::string("TheGamesDBJSONRequest - Error parsing JSON. \n\t") +
|
||||||
|
GetParseError_En(doc.GetParseError());
|
||||||
setError(err);
|
setError(err);
|
||||||
LOG(LogError) << err;
|
LOG(LogError) << err;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!doc.HasMember("data") || !doc["data"].HasMember("games") || !doc["data"]["games"].IsArray())
|
if (!doc.HasMember("data") || !doc["data"].HasMember("games") ||
|
||||||
{
|
!doc["data"]["games"].IsArray()) {
|
||||||
std::string warn = "TheGamesDBJSONRequest - Response had no game data.\n";
|
std::string warn = "TheGamesDBJSONRequest - Response had no game data.\n";
|
||||||
LOG(LogWarning) << warn;
|
LOG(LogWarning) << warn;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const Value& games = doc["data"]["games"];
|
const Value& games = doc["data"]["games"];
|
||||||
|
|
||||||
if (!doc.HasMember("include") || !doc["include"].HasMember("boxart"))
|
if (!doc.HasMember("include") || !doc["include"].HasMember("boxart")) {
|
||||||
{
|
|
||||||
std::string warn = "TheGamesDBJSONRequest - Response had no include boxart data.\n";
|
std::string warn = "TheGamesDBJSONRequest - Response had no include boxart data.\n";
|
||||||
LOG(LogWarning) << warn;
|
LOG(LogWarning) << warn;
|
||||||
return;
|
return;
|
||||||
|
@ -371,8 +347,7 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req, std::ve
|
||||||
|
|
||||||
const Value& boxart = doc["include"]["boxart"];
|
const Value& boxart = doc["include"]["boxart"];
|
||||||
|
|
||||||
if (!boxart.HasMember("base_url") || !boxart.HasMember("data") || !boxart.IsObject())
|
if (!boxart.HasMember("base_url") || !boxart.HasMember("data") || !boxart.IsObject()) {
|
||||||
{
|
|
||||||
std::string warn = "TheGamesDBJSONRequest - Response include had no usable boxart data.\n";
|
std::string warn = "TheGamesDBJSONRequest - Response include had no usable boxart data.\n";
|
||||||
LOG(LogWarning) << warn;
|
LOG(LogWarning) << warn;
|
||||||
return;
|
return;
|
||||||
|
@ -380,16 +355,12 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req, std::ve
|
||||||
|
|
||||||
resources.ensureResources();
|
resources.ensureResources();
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)games.Size(); ++i) {
|
||||||
for (int i = 0; i < (int)games.Size(); ++i)
|
|
||||||
{
|
|
||||||
auto& v = games[i];
|
auto& v = games[i];
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
processGame(v, boxart, results);
|
processGame(v, boxart, results);
|
||||||
}
|
}
|
||||||
catch (std::runtime_error& e)
|
catch (std::runtime_error& e) {
|
||||||
{
|
|
||||||
LOG(LogError) << "Error while processing game: " << e.what();
|
LOG(LogError) << "Error while processing game: " << e.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,49 @@
|
||||||
|
//
|
||||||
|
// GamesDBJSONScraper.h
|
||||||
|
//
|
||||||
|
// Functions specifically for scraping from thegamesdb.net
|
||||||
|
// Called from Scraper.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_H
|
#ifndef ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_H
|
||||||
#define ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_H
|
#define ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_H
|
||||||
|
|
||||||
#include "scrapers/Scraper.h"
|
#include "scrapers/Scraper.h"
|
||||||
|
|
||||||
namespace pugi
|
namespace pugi {
|
||||||
{
|
|
||||||
class xml_document;
|
class xml_document;
|
||||||
}
|
}
|
||||||
|
|
||||||
void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params,
|
void thegamesdb_generate_json_scraper_requests(
|
||||||
std::queue<std::unique_ptr<ScraperRequest>>& requests, std::vector<ScraperSearchResult>& results);
|
const ScraperSearchParams& params,
|
||||||
|
std::queue<std::unique_ptr<ScraperRequest>>& requests,
|
||||||
|
std::vector<ScraperSearchResult>& results);
|
||||||
|
|
||||||
class TheGamesDBJSONRequest : public ScraperHttpRequest
|
class TheGamesDBJSONRequest : public ScraperHttpRequest
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// ctor for a GetGameList request
|
// ctor for a GetGameList request.
|
||||||
TheGamesDBJSONRequest(std::queue<std::unique_ptr<ScraperRequest>>& requestsWrite,
|
TheGamesDBJSONRequest(
|
||||||
std::vector<ScraperSearchResult>& resultsWrite, const std::string& url)
|
std::queue<std::unique_ptr<ScraperRequest>>& requestsWrite,
|
||||||
: ScraperHttpRequest(resultsWrite, url), mRequestQueue(&requestsWrite)
|
std::vector<ScraperSearchResult>& resultsWrite,
|
||||||
|
const std::string& url)
|
||||||
|
: ScraperHttpRequest(resultsWrite, url),
|
||||||
|
mRequestQueue(&requestsWrite)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
// ctor for a GetGame request
|
// ctor for a GetGame request
|
||||||
TheGamesDBJSONRequest(std::vector<ScraperSearchResult>& resultsWrite, const std::string& url)
|
TheGamesDBJSONRequest(
|
||||||
: ScraperHttpRequest(resultsWrite, url), mRequestQueue(nullptr)
|
std::vector<ScraperSearchResult>& resultsWrite,
|
||||||
|
const std::string& url)
|
||||||
|
: ScraperHttpRequest(resultsWrite, url),
|
||||||
|
mRequestQueue(nullptr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void process(const std::unique_ptr<HttpReq>& req, std::vector<ScraperSearchResult>& results) override;
|
void process(const std::unique_ptr<HttpReq>& req,
|
||||||
|
std::vector<ScraperSearchResult>& results) override;
|
||||||
bool isGameRequest() { return !mRequestQueue; }
|
bool isGameRequest() { return !mRequestQueue; }
|
||||||
|
|
||||||
std::queue<std::unique_ptr<ScraperRequest>>* mRequestQueue;
|
std::queue<std::unique_ptr<ScraperRequest>>* mRequestQueue;
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
//
|
||||||
|
// GamesDBJSONScraperResources.cpp
|
||||||
|
//
|
||||||
|
// Functions specifically for scraping from thegamesdb.net
|
||||||
|
// Called from GamesDBJSONScraper.
|
||||||
|
//
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -8,17 +15,14 @@
|
||||||
#include "scrapers/GamesDBJSONScraperResources.h"
|
#include "scrapers/GamesDBJSONScraperResources.h"
|
||||||
#include "utils/FileSystemUtil.h"
|
#include "utils/FileSystemUtil.h"
|
||||||
|
|
||||||
|
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <rapidjson/error/en.h>
|
#include <rapidjson/error/en.h>
|
||||||
|
|
||||||
using namespace rapidjson;
|
using namespace rapidjson;
|
||||||
|
|
||||||
|
namespace {
|
||||||
namespace
|
constexpr char GamesDBAPIKey[] =
|
||||||
{
|
"445fcbc3f32bb2474bc27016b99eb963d318ee3a608212c543b9a79de1041600";
|
||||||
constexpr char GamesDBAPIKey[] = "445fcbc3f32bb2474bc27016b99eb963d318ee3a608212c543b9a79de1041600";
|
|
||||||
|
|
||||||
|
|
||||||
constexpr int MAX_WAIT_MS = 90000;
|
constexpr int MAX_WAIT_MS = 90000;
|
||||||
constexpr int POLL_TIME_MS = 500;
|
constexpr int POLL_TIME_MS = 500;
|
||||||
|
@ -46,73 +50,57 @@ void ensureScrapersResourcesDir()
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
std::string getScrapersResouceDir()
|
std::string getScrapersResouceDir()
|
||||||
{
|
{
|
||||||
return Utils::FileSystem::getGenericPath(
|
return Utils::FileSystem::getGenericPath(
|
||||||
Utils::FileSystem::getHomePath() + "/.emulationstation/" + SCRAPER_RESOURCES_DIR);
|
Utils::FileSystem::getHomePath() + "/.emulationstation/" + SCRAPER_RESOURCES_DIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TheGamesDBJSONRequestResources::getApiKey() const { return GamesDBAPIKey; }
|
std::string TheGamesDBJSONRequestResources::getApiKey() const { return GamesDBAPIKey; }
|
||||||
|
|
||||||
|
|
||||||
void TheGamesDBJSONRequestResources::prepare()
|
void TheGamesDBJSONRequestResources::prepare()
|
||||||
{
|
{
|
||||||
if (checkLoaded())
|
if (checkLoaded())
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (loadResource(gamesdb_new_developers_map, "developers", genFilePath(DEVELOPERS_JSON_FILE)) &&
|
if (loadResource(gamesdb_new_developers_map, "developers",
|
||||||
!gamesdb_developers_resource_request)
|
genFilePath(DEVELOPERS_JSON_FILE)) && !gamesdb_developers_resource_request)
|
||||||
{
|
|
||||||
gamesdb_developers_resource_request = fetchResource(DEVELOPERS_ENDPOINT);
|
gamesdb_developers_resource_request = fetchResource(DEVELOPERS_ENDPOINT);
|
||||||
}
|
|
||||||
if (loadResource(gamesdb_new_publishers_map, "publishers", genFilePath(PUBLISHERS_JSON_FILE)) &&
|
if (loadResource(gamesdb_new_publishers_map, "publishers",
|
||||||
!gamesdb_publishers_resource_request)
|
genFilePath(PUBLISHERS_JSON_FILE)) && !gamesdb_publishers_resource_request)
|
||||||
{
|
|
||||||
gamesdb_publishers_resource_request = fetchResource(PUBLISHERS_ENDPOINT);
|
gamesdb_publishers_resource_request = fetchResource(PUBLISHERS_ENDPOINT);
|
||||||
}
|
|
||||||
if (loadResource(gamesdb_new_genres_map, "genres", genFilePath(GENRES_JSON_FILE)) && !gamesdb_genres_resource_request)
|
if (loadResource(gamesdb_new_genres_map, "genres",
|
||||||
{
|
genFilePath(GENRES_JSON_FILE)) && !gamesdb_genres_resource_request)
|
||||||
gamesdb_genres_resource_request = fetchResource(GENRES_ENDPOINT);
|
gamesdb_genres_resource_request = fetchResource(GENRES_ENDPOINT);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TheGamesDBJSONRequestResources::ensureResources()
|
void TheGamesDBJSONRequestResources::ensureResources()
|
||||||
{
|
{
|
||||||
|
|
||||||
if (checkLoaded())
|
if (checkLoaded())
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_WAIT_ITER; ++i) {
|
||||||
|
|
||||||
for (int i = 0; i < MAX_WAIT_ITER; ++i)
|
|
||||||
{
|
|
||||||
if (gamesdb_developers_resource_request &&
|
if (gamesdb_developers_resource_request &&
|
||||||
saveResource(gamesdb_developers_resource_request.get(), gamesdb_new_developers_map, "developers",
|
saveResource(gamesdb_developers_resource_request.get(),
|
||||||
genFilePath(DEVELOPERS_JSON_FILE)))
|
gamesdb_new_developers_map, "developers", genFilePath(DEVELOPERS_JSON_FILE)))
|
||||||
{
|
|
||||||
|
|
||||||
gamesdb_developers_resource_request.reset(nullptr);
|
gamesdb_developers_resource_request.reset(nullptr);
|
||||||
}
|
|
||||||
if (gamesdb_publishers_resource_request &&
|
|
||||||
saveResource(gamesdb_publishers_resource_request.get(), gamesdb_new_publishers_map, "publishers",
|
|
||||||
genFilePath(PUBLISHERS_JSON_FILE)))
|
|
||||||
{
|
|
||||||
gamesdb_publishers_resource_request.reset(nullptr);
|
|
||||||
}
|
|
||||||
if (gamesdb_genres_resource_request && saveResource(gamesdb_genres_resource_request.get(), gamesdb_new_genres_map,
|
|
||||||
"genres", genFilePath(GENRES_JSON_FILE)))
|
|
||||||
{
|
|
||||||
gamesdb_genres_resource_request.reset(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!gamesdb_developers_resource_request && !gamesdb_publishers_resource_request && !gamesdb_genres_resource_request)
|
if (gamesdb_publishers_resource_request &&
|
||||||
{
|
saveResource(gamesdb_publishers_resource_request.get(),
|
||||||
|
gamesdb_new_publishers_map, "publishers", genFilePath(PUBLISHERS_JSON_FILE)))
|
||||||
|
gamesdb_publishers_resource_request.reset(nullptr);
|
||||||
|
|
||||||
|
if (gamesdb_genres_resource_request && saveResource(gamesdb_genres_resource_request.get(),
|
||||||
|
gamesdb_new_genres_map, "genres", genFilePath(GENRES_JSON_FILE)))
|
||||||
|
gamesdb_genres_resource_request.reset(nullptr);
|
||||||
|
|
||||||
|
if (!gamesdb_developers_resource_request && !gamesdb_publishers_resource_request &&
|
||||||
|
!gamesdb_genres_resource_request)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(POLL_TIME_MS));
|
std::this_thread::sleep_for(std::chrono::milliseconds(POLL_TIME_MS));
|
||||||
}
|
}
|
||||||
LOG(LogError) << "Timed out while waiting for resources\n";
|
LOG(LogError) << "Timed out while waiting for resources\n";
|
||||||
|
@ -120,26 +108,28 @@ void TheGamesDBJSONRequestResources::ensureResources()
|
||||||
|
|
||||||
bool TheGamesDBJSONRequestResources::checkLoaded()
|
bool TheGamesDBJSONRequestResources::checkLoaded()
|
||||||
{
|
{
|
||||||
return !gamesdb_new_genres_map.empty() && !gamesdb_new_developers_map.empty() && !gamesdb_new_publishers_map.empty();
|
return !gamesdb_new_genres_map.empty() && !gamesdb_new_developers_map.empty() &&
|
||||||
|
!gamesdb_new_publishers_map.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TheGamesDBJSONRequestResources::saveResource(HttpReq* req, std::unordered_map<int, std::string>& resource,
|
bool TheGamesDBJSONRequestResources::saveResource(
|
||||||
const std::string& resource_name, const std::string& file_name)
|
HttpReq* req,
|
||||||
|
std::unordered_map<int, std::string>& resource,
|
||||||
|
const std::string& resource_name,
|
||||||
|
const std::string& file_name)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (req == nullptr)
|
if (req == nullptr) {
|
||||||
{
|
|
||||||
LOG(LogError) << "Http request pointer was null\n";
|
LOG(LogError) << "Http request pointer was null\n";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (req->status() == HttpReq::REQ_IN_PROGRESS)
|
if (req->status() == HttpReq::REQ_IN_PROGRESS) {
|
||||||
{
|
return false; // Not ready: wait some more.
|
||||||
return false; // Not ready: wait some more
|
|
||||||
}
|
}
|
||||||
if (req->status() != HttpReq::REQ_SUCCESS)
|
if (req->status() != HttpReq::REQ_SUCCESS) {
|
||||||
{
|
LOG(LogError) << "Resource request for " << file_name <<
|
||||||
LOG(LogError) << "Resource request for " << file_name << " failed:\n\t" << req->getErrorMsg();
|
" failed:\n\t" << req->getErrorMsg();
|
||||||
return true; // Request failed, resetting request.
|
return true; // Request failed, resetting request..
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureScrapersResourcesDir();
|
ensureScrapersResourcesDir();
|
||||||
|
@ -160,47 +150,42 @@ std::unique_ptr<HttpReq> TheGamesDBJSONRequestResources::fetchResource(const std
|
||||||
return std::unique_ptr<HttpReq>(new HttpReq(path));
|
return std::unique_ptr<HttpReq>(new HttpReq(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int TheGamesDBJSONRequestResources::loadResource(
|
int TheGamesDBJSONRequestResources::loadResource(
|
||||||
std::unordered_map<int, std::string>& resource, const std::string& resource_name, const std::string& file_name)
|
std::unordered_map<int, std::string>& resource,
|
||||||
|
const std::string& resource_name,
|
||||||
|
const std::string& file_name)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
std::ifstream fin(file_name);
|
std::ifstream fin(file_name);
|
||||||
if (!fin.good())
|
if (!fin.good())
|
||||||
{
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
|
||||||
std::stringstream buffer;
|
std::stringstream buffer;
|
||||||
buffer << fin.rdbuf();
|
buffer << fin.rdbuf();
|
||||||
Document doc;
|
Document doc;
|
||||||
doc.Parse(buffer.str().c_str());
|
doc.Parse(buffer.str().c_str());
|
||||||
|
|
||||||
if (doc.HasParseError())
|
if (doc.HasParseError()) {
|
||||||
{
|
std::string err = std::string("TheGamesDBJSONRequest - "
|
||||||
std::string err = std::string("TheGamesDBJSONRequest - Error parsing JSON for resource file ") + file_name +
|
"Error parsing JSON for resource file ") + file_name +
|
||||||
":\n\t" + GetParseError_En(doc.GetParseError());
|
":\n\t" + GetParseError_En(doc.GetParseError());
|
||||||
LOG(LogError) << err;
|
LOG(LogError) << err;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!doc.HasMember("data") || !doc["data"].HasMember(resource_name.c_str()) ||
|
if (!doc.HasMember("data") || !doc["data"].HasMember(resource_name.c_str()) ||
|
||||||
!doc["data"][resource_name.c_str()].IsObject())
|
!doc["data"][resource_name.c_str()].IsObject()) {
|
||||||
{
|
|
||||||
std::string err = "TheGamesDBJSONRequest - Response had no resource data.\n";
|
std::string err = "TheGamesDBJSONRequest - Response had no resource data.\n";
|
||||||
LOG(LogError) << err;
|
LOG(LogError) << err;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
auto& data = doc["data"][resource_name.c_str()];
|
auto& data = doc["data"][resource_name.c_str()];
|
||||||
|
|
||||||
for (Value::ConstMemberIterator itr = data.MemberBegin(); itr != data.MemberEnd(); ++itr)
|
for (Value::ConstMemberIterator itr = data.MemberBegin(); itr != data.MemberEnd(); ++itr) {
|
||||||
{
|
|
||||||
auto& entry = itr->value;
|
auto& entry = itr->value;
|
||||||
if (!entry.IsObject() || !entry.HasMember("id") || !entry["id"].IsInt() || !entry.HasMember("name") ||
|
if (!entry.IsObject() || !entry.HasMember("id") || !entry["id"].IsInt() ||
|
||||||
!entry["name"].IsString())
|
!entry.HasMember("name") || !entry["name"].IsString())
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
resource[entry["id"].GetInt()] = entry["name"].GetString();
|
resource[entry["id"].GetInt()] = entry["name"].GetString();
|
||||||
}
|
}
|
||||||
return resource.empty();
|
return resource.empty();
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
//
|
||||||
|
// GamesDBJSONScraperResources.h
|
||||||
|
//
|
||||||
|
// Functions specifically for scraping from thegamesdb.net
|
||||||
|
// Called from GamesDBJSONScraper.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_RESOURCES_H
|
#ifndef ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_RESOURCES_H
|
||||||
#define ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_RESOURCES_H
|
#define ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_RESOURCES_H
|
||||||
|
@ -9,9 +16,7 @@
|
||||||
|
|
||||||
#include "HttpReq.h"
|
#include "HttpReq.h"
|
||||||
|
|
||||||
|
struct TheGamesDBJSONRequestResources {
|
||||||
struct TheGamesDBJSONRequestResources
|
|
||||||
{
|
|
||||||
TheGamesDBJSONRequestResources() = default;
|
TheGamesDBJSONRequestResources() = default;
|
||||||
|
|
||||||
void prepare();
|
void prepare();
|
||||||
|
@ -25,12 +30,17 @@ struct TheGamesDBJSONRequestResources
|
||||||
private:
|
private:
|
||||||
bool checkLoaded();
|
bool checkLoaded();
|
||||||
|
|
||||||
bool saveResource(HttpReq* req, std::unordered_map<int, std::string>& resource, const std::string& resource_name,
|
bool saveResource(
|
||||||
const std::string& file_name);
|
HttpReq* req,
|
||||||
std::unique_ptr<HttpReq> fetchResource(const std::string& endpoint);
|
std::unordered_map<int, std::string>& resource,
|
||||||
|
const std::string& resource_name,
|
||||||
|
const std::string& file_name);
|
||||||
|
std::unique_ptr<HttpReq> fetchResource(const std::string& endpoint);
|
||||||
|
|
||||||
int loadResource(
|
int loadResource(
|
||||||
std::unordered_map<int, std::string>& resource, const std::string& resource_name, const std::string& file_name);
|
std::unordered_map<int, std::string>& resource,
|
||||||
|
const std::string& resource_name,
|
||||||
|
const std::string& file_name);
|
||||||
|
|
||||||
std::unique_ptr<HttpReq> gamesdb_developers_resource_request;
|
std::unique_ptr<HttpReq> gamesdb_developers_resource_request;
|
||||||
std::unique_ptr<HttpReq> gamesdb_publishers_resource_request;
|
std::unique_ptr<HttpReq> gamesdb_publishers_resource_request;
|
||||||
|
@ -39,4 +49,4 @@ struct TheGamesDBJSONRequestResources
|
||||||
|
|
||||||
std::string getScrapersResouceDir();
|
std::string getScrapersResouceDir();
|
||||||
|
|
||||||
#endif // ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_H
|
#endif // ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_RESOURCES_H
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
//
|
||||||
|
// Scraper.cpp
|
||||||
|
//
|
||||||
|
// Main scraper logic.
|
||||||
|
// Called from ScraperSearchComponent.
|
||||||
|
// Calls either GamesDBJSONScraper or ScreenScraper.
|
||||||
|
//
|
||||||
|
|
||||||
#include "scrapers/Scraper.h"
|
#include "scrapers/Scraper.h"
|
||||||
|
|
||||||
#include "FileData.h"
|
#include "FileData.h"
|
||||||
|
@ -19,15 +27,11 @@ std::unique_ptr<ScraperSearchHandle> startScraperSearch(const ScraperSearchParam
|
||||||
const std::string& name = Settings::getInstance()->getString("Scraper");
|
const std::string& name = Settings::getInstance()->getString("Scraper");
|
||||||
std::unique_ptr<ScraperSearchHandle> handle(new ScraperSearchHandle());
|
std::unique_ptr<ScraperSearchHandle> handle(new ScraperSearchHandle());
|
||||||
|
|
||||||
// Check if the Scraper in the settings still exists as a registered scraping source.
|
// Check if the scraper in the settings still exists as a registered scraping source.
|
||||||
if (scraper_request_funcs.find(name) == scraper_request_funcs.end())
|
if (scraper_request_funcs.find(name) == scraper_request_funcs.end())
|
||||||
{
|
|
||||||
LOG(LogWarning) << "Configured scraper (" << name << ") unavailable, scraping aborted.";
|
LOG(LogWarning) << "Configured scraper (" << name << ") unavailable, scraping aborted.";
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
scraper_request_funcs.at(name)(params, handle->mRequestQueue, handle->mResults);
|
scraper_request_funcs.at(name)(params, handle->mRequestQueue, handle->mResults);
|
||||||
}
|
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
@ -36,9 +40,7 @@ std::vector<std::string> getScraperList()
|
||||||
{
|
{
|
||||||
std::vector<std::string> list;
|
std::vector<std::string> list;
|
||||||
for(auto it = scraper_request_funcs.cbegin(); it != scraper_request_funcs.cend(); it++)
|
for(auto it = scraper_request_funcs.cbegin(); it != scraper_request_funcs.cend(); it++)
|
||||||
{
|
|
||||||
list.push_back(it->first);
|
list.push_back(it->first);
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
@ -49,7 +51,7 @@ bool isValidConfiguredScraper()
|
||||||
return scraper_request_funcs.find(name) != scraper_request_funcs.end();
|
return scraper_request_funcs.find(name) != scraper_request_funcs.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScraperSearchHandle
|
// ScraperSearchHandle.
|
||||||
ScraperSearchHandle::ScraperSearchHandle()
|
ScraperSearchHandle::ScraperSearchHandle()
|
||||||
{
|
{
|
||||||
setStatus(ASYNC_IN_PROGRESS);
|
setStatus(ASYNC_IN_PROGRESS);
|
||||||
|
@ -62,51 +64,45 @@ void ScraperSearchHandle::update()
|
||||||
|
|
||||||
if(!mRequestQueue.empty())
|
if(!mRequestQueue.empty())
|
||||||
{
|
{
|
||||||
// a request can add more requests to the queue while running,
|
// A request can add more requests to the queue while running,
|
||||||
// so be careful with references into the queue
|
// so be careful with references into the queue.
|
||||||
auto& req = *(mRequestQueue.front());
|
auto& req = *(mRequestQueue.front());
|
||||||
AsyncHandleStatus status = req.status();
|
AsyncHandleStatus status = req.status();
|
||||||
|
|
||||||
if(status == ASYNC_ERROR)
|
if(status == ASYNC_ERROR) {
|
||||||
{
|
// Propagate error.
|
||||||
// propegate error
|
|
||||||
setError(req.getStatusString());
|
setError(req.getStatusString());
|
||||||
|
|
||||||
// empty our queue
|
// Empty our queue.
|
||||||
while(!mRequestQueue.empty())
|
while(!mRequestQueue.empty())
|
||||||
mRequestQueue.pop();
|
mRequestQueue.pop();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// finished this one, see if we have any more
|
// Finished this one, see if we have any more.
|
||||||
if(status == ASYNC_DONE)
|
if(status == ASYNC_DONE)
|
||||||
{
|
|
||||||
mRequestQueue.pop();
|
mRequestQueue.pop();
|
||||||
}
|
|
||||||
|
|
||||||
// status == ASYNC_IN_PROGRESS
|
// Status == ASYNC_IN_PROGRESS.
|
||||||
}
|
}
|
||||||
|
|
||||||
// we finished without any errors!
|
// We finished without any errors!
|
||||||
if(mRequestQueue.empty())
|
if(mRequestQueue.empty()) {
|
||||||
{
|
|
||||||
setStatus(ASYNC_DONE);
|
setStatus(ASYNC_DONE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ScraperRequest.
|
||||||
|
ScraperRequest::ScraperRequest(std::vector<ScraperSearchResult>& resultsWrite)
|
||||||
// ScraperRequest
|
: mResults(resultsWrite)
|
||||||
ScraperRequest::ScraperRequest(std::vector<ScraperSearchResult>& resultsWrite) : mResults(resultsWrite)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ScraperHttpRequest.
|
||||||
// ScraperHttpRequest
|
ScraperHttpRequest::ScraperHttpRequest(std::vector<ScraperSearchResult>&
|
||||||
ScraperHttpRequest::ScraperHttpRequest(std::vector<ScraperSearchResult>& resultsWrite, const std::string& url)
|
resultsWrite, const std::string& url) : ScraperRequest(resultsWrite)
|
||||||
: ScraperRequest(resultsWrite)
|
|
||||||
{
|
{
|
||||||
setStatus(ASYNC_IN_PROGRESS);
|
setStatus(ASYNC_IN_PROGRESS);
|
||||||
mReq = std::unique_ptr<HttpReq>(new HttpReq(url));
|
mReq = std::unique_ptr<HttpReq>(new HttpReq(url));
|
||||||
|
@ -117,41 +113,43 @@ void ScraperHttpRequest::update()
|
||||||
HttpReq::Status status = mReq->status();
|
HttpReq::Status status = mReq->status();
|
||||||
if(status == HttpReq::REQ_SUCCESS)
|
if(status == HttpReq::REQ_SUCCESS)
|
||||||
{
|
{
|
||||||
setStatus(ASYNC_DONE); // if process() has an error, status will be changed to ASYNC_ERROR
|
// If process() has an error, status will be changed to ASYNC_ERROR.
|
||||||
|
setStatus(ASYNC_DONE);
|
||||||
process(mReq, mResults);
|
process(mReq, mResults);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// not ready yet
|
// Not ready yet.
|
||||||
if(status == HttpReq::REQ_IN_PROGRESS)
|
if(status == HttpReq::REQ_IN_PROGRESS)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// everything else is some sort of error
|
// Everything else is some sort of error.
|
||||||
LOG(LogError) << "ScraperHttpRequest network error (status: " << status << ") - " << mReq->getErrorMsg();
|
LOG(LogError) << "ScraperHttpRequest network error (status: " << status<< ") - "
|
||||||
|
<< mReq->getErrorMsg();
|
||||||
setError(mReq->getErrorMsg());
|
setError(mReq->getErrorMsg());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metadata resolving stuff.
|
||||||
|
|
||||||
// metadata resolving stuff
|
std::unique_ptr<MDResolveHandle> resolveMetaDataAssets(const ScraperSearchResult& result,
|
||||||
|
const ScraperSearchParams& search)
|
||||||
std::unique_ptr<MDResolveHandle> resolveMetaDataAssets(const ScraperSearchResult& result, const ScraperSearchParams& search)
|
|
||||||
{
|
{
|
||||||
return std::unique_ptr<MDResolveHandle>(new MDResolveHandle(result, search));
|
return std::unique_ptr<MDResolveHandle>(new MDResolveHandle(result, search));
|
||||||
}
|
}
|
||||||
|
|
||||||
MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result, const ScraperSearchParams& search) : mResult(result)
|
MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result,
|
||||||
|
const ScraperSearchParams& search) : mResult(result)
|
||||||
{
|
{
|
||||||
if(!result.imageUrl.empty())
|
if(!result.imageUrl.empty()) {
|
||||||
{
|
|
||||||
|
|
||||||
std::string ext;
|
std::string ext;
|
||||||
|
|
||||||
// If we have a file extension returned by the scraper, then use it.
|
// If we have a file extension returned by the scraper, then use it.
|
||||||
// Otherwise, try to guess it by the name of the URL, which point to an image.
|
// Otherwise, try to guess it by the name of the URL, which point to an image.
|
||||||
if (!result.imageType.empty())
|
if (!result.imageType.empty()) {
|
||||||
{
|
|
||||||
ext = result.imageType;
|
ext = result.imageType;
|
||||||
}else{
|
}
|
||||||
|
else {
|
||||||
size_t dot = result.imageUrl.find_last_of('.');
|
size_t dot = result.imageUrl.find_last_of('.');
|
||||||
|
|
||||||
if (dot != std::string::npos)
|
if (dot != std::string::npos)
|
||||||
|
@ -160,8 +158,8 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result, const Scrape
|
||||||
|
|
||||||
std::string imgPath = getSaveAsPath(search, "image", ext);
|
std::string imgPath = getSaveAsPath(search, "image", ext);
|
||||||
|
|
||||||
mFuncs.push_back(ResolvePair(downloadImageAsync(result.imageUrl, imgPath), [this, imgPath]
|
mFuncs.push_back(ResolvePair(downloadImageAsync(result.imageUrl, imgPath),
|
||||||
{
|
[this, imgPath] {
|
||||||
mResult.mdl.set("image", imgPath);
|
mResult.mdl.set("image", imgPath);
|
||||||
mResult.imageUrl = "";
|
mResult.imageUrl = "";
|
||||||
}));
|
}));
|
||||||
|
@ -174,14 +172,12 @@ void MDResolveHandle::update()
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto it = mFuncs.cbegin();
|
auto it = mFuncs.cbegin();
|
||||||
while(it != mFuncs.cend())
|
while(it != mFuncs.cend()) {
|
||||||
{
|
if(it->first->status() == ASYNC_ERROR) {
|
||||||
if(it->first->status() == ASYNC_ERROR)
|
|
||||||
{
|
|
||||||
setError(it->first->getStatusString());
|
setError(it->first->getStatusString());
|
||||||
return;
|
return;
|
||||||
}else if(it->first->status() == ASYNC_DONE)
|
}
|
||||||
{
|
else if(it->first->status() == ASYNC_DONE) {
|
||||||
it->second();
|
it->second();
|
||||||
it = mFuncs.erase(it);
|
it = mFuncs.erase(it);
|
||||||
continue;
|
continue;
|
||||||
|
@ -193,14 +189,17 @@ void MDResolveHandle::update()
|
||||||
setStatus(ASYNC_DONE);
|
setStatus(ASYNC_DONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ImageDownloadHandle> downloadImageAsync(const std::string& url, const std::string& saveAs)
|
std::unique_ptr<ImageDownloadHandle> downloadImageAsync(const std::string& url,
|
||||||
|
const std::string& saveAs)
|
||||||
{
|
{
|
||||||
return std::unique_ptr<ImageDownloadHandle>(new ImageDownloadHandle(url, saveAs,
|
return std::unique_ptr<ImageDownloadHandle>(new ImageDownloadHandle(url, saveAs,
|
||||||
Settings::getInstance()->getInt("ScraperResizeWidth"), Settings::getInstance()->getInt("ScraperResizeHeight")));
|
Settings::getInstance()->getInt("ScraperResizeWidth"),
|
||||||
|
Settings::getInstance()->getInt("ScraperResizeHeight")));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageDownloadHandle::ImageDownloadHandle(const std::string& url, const std::string& path, int maxWidth, int maxHeight) :
|
ImageDownloadHandle::ImageDownloadHandle(const std::string& url,
|
||||||
mSavePath(path), mMaxWidth(maxWidth), mMaxHeight(maxHeight), mReq(new HttpReq(url))
|
const std::string& path, int maxWidth, int maxHeight) : mSavePath(path),
|
||||||
|
mMaxWidth(maxWidth), mMaxHeight(maxHeight), mReq(new HttpReq(url))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,18 +208,16 @@ void ImageDownloadHandle::update()
|
||||||
if(mReq->status() == HttpReq::REQ_IN_PROGRESS)
|
if(mReq->status() == HttpReq::REQ_IN_PROGRESS)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(mReq->status() != HttpReq::REQ_SUCCESS)
|
if(mReq->status() != HttpReq::REQ_SUCCESS) {
|
||||||
{
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Network error: " << mReq->getErrorMsg();
|
ss << "Network error: " << mReq->getErrorMsg();
|
||||||
setError(ss.str());
|
setError(ss.str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// download is done, save it to disk
|
// Download is done, save it to disk.
|
||||||
std::ofstream stream(mSavePath, std::ios_base::out | std::ios_base::binary);
|
std::ofstream stream(mSavePath, std::ios_base::out | std::ios_base::binary);
|
||||||
if(stream.bad())
|
if(stream.bad()) {
|
||||||
{
|
|
||||||
setError("Failed to open image path to write. Permission error? Disk full?");
|
setError("Failed to open image path to write. Permission error? Disk full?");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -228,15 +225,13 @@ void ImageDownloadHandle::update()
|
||||||
const std::string& content = mReq->getContent();
|
const std::string& content = mReq->getContent();
|
||||||
stream.write(content.data(), content.length());
|
stream.write(content.data(), content.length());
|
||||||
stream.close();
|
stream.close();
|
||||||
if(stream.bad())
|
if(stream.bad()) {
|
||||||
{
|
|
||||||
setError("Failed to save image. Disk full?");
|
setError("Failed to save image. Disk full?");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// resize it
|
// Resize it.
|
||||||
if(!resizeImage(mSavePath, mMaxWidth, mMaxHeight))
|
if(!resizeImage(mSavePath, mMaxWidth, mMaxHeight)) {
|
||||||
{
|
|
||||||
setError("Error saving resized image. Out of memory? Disk full?");
|
setError("Error saving resized image. Out of memory? Disk full?");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -244,31 +239,30 @@ void ImageDownloadHandle::update()
|
||||||
setStatus(ASYNC_DONE);
|
setStatus(ASYNC_DONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
//you can pass 0 for width or height to keep aspect ratio
|
// You can pass 0 for width or height to keep aspect ratio.
|
||||||
bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
|
bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
|
||||||
{
|
{
|
||||||
// nothing to do
|
// Nothing to do.
|
||||||
if(maxWidth == 0 && maxHeight == 0)
|
if(maxWidth == 0 && maxHeight == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
FREE_IMAGE_FORMAT format = FIF_UNKNOWN;
|
FREE_IMAGE_FORMAT format = FIF_UNKNOWN;
|
||||||
FIBITMAP* image = NULL;
|
FIBITMAP* image = NULL;
|
||||||
|
|
||||||
//detect the filetype
|
// Detect the filetype.
|
||||||
format = FreeImage_GetFileType(path.c_str(), 0);
|
format = FreeImage_GetFileType(path.c_str(), 0);
|
||||||
if(format == FIF_UNKNOWN)
|
if(format == FIF_UNKNOWN)
|
||||||
format = FreeImage_GetFIFFromFilename(path.c_str());
|
format = FreeImage_GetFIFFromFilename(path.c_str());
|
||||||
if(format == FIF_UNKNOWN)
|
if(format == FIF_UNKNOWN) {
|
||||||
{
|
|
||||||
LOG(LogError) << "Error - could not detect filetype for image \"" << path << "\"!";
|
LOG(LogError) << "Error - could not detect filetype for image \"" << path << "\"!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//make sure we can read this filetype first, then load it
|
// Make sure we can read this filetype first, then load it.
|
||||||
if(FreeImage_FIFSupportsReading(format))
|
if(FreeImage_FIFSupportsReading(format)) {
|
||||||
{
|
|
||||||
image = FreeImage_Load(format, path.c_str());
|
image = FreeImage_Load(format, path.c_str());
|
||||||
}else{
|
}
|
||||||
|
else {
|
||||||
LOG(LogError) << "Error - file format reading not supported for image \"" << path << "\"!";
|
LOG(LogError) << "Error - file format reading not supported for image \"" << path << "\"!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -277,18 +271,14 @@ bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
|
||||||
float height = (float)FreeImage_GetHeight(image);
|
float height = (float)FreeImage_GetHeight(image);
|
||||||
|
|
||||||
if(maxWidth == 0)
|
if(maxWidth == 0)
|
||||||
{
|
|
||||||
maxWidth = (int)((maxHeight / height) * width);
|
maxWidth = (int)((maxHeight / height) * width);
|
||||||
}else if(maxHeight == 0)
|
else if(maxHeight == 0)
|
||||||
{
|
|
||||||
maxHeight = (int)((maxWidth / width) * height);
|
maxHeight = (int)((maxWidth / width) * height);
|
||||||
}
|
|
||||||
|
|
||||||
FIBITMAP* imageRescaled = FreeImage_Rescale(image, maxWidth, maxHeight, FILTER_BILINEAR);
|
FIBITMAP* imageRescaled = FreeImage_Rescale(image, maxWidth, maxHeight, FILTER_BILINEAR);
|
||||||
FreeImage_Unload(image);
|
FreeImage_Unload(image);
|
||||||
|
|
||||||
if(imageRescaled == NULL)
|
if(imageRescaled == NULL) {
|
||||||
{
|
|
||||||
LOG(LogError) << "Could not resize image! (not enough memory? invalid bitdepth?)";
|
LOG(LogError) << "Could not resize image! (not enough memory? invalid bitdepth?)";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -302,7 +292,8 @@ bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
|
||||||
return saved;
|
return saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getSaveAsPath(const ScraperSearchParams& params, const std::string& suffix, const std::string& extension)
|
std::string getSaveAsPath(const ScraperSearchParams& params,
|
||||||
|
const std::string& suffix, const std::string& extension)
|
||||||
{
|
{
|
||||||
const std::string subdirectory = params.system->getName();
|
const std::string subdirectory = params.system->getName();
|
||||||
const std::string name = Utils::FileSystem::getStem(params.game->getPath()) + "-" + suffix;
|
const std::string name = Utils::FileSystem::getStem(params.game->getPath()) + "-" + suffix;
|
||||||
|
@ -317,7 +308,6 @@ std::string getSaveAsPath(const ScraperSearchParams& params, const std::string&
|
||||||
if(!Utils::FileSystem::exists(path))
|
if(!Utils::FileSystem::exists(path))
|
||||||
Utils::FileSystem::createDirectory(path);
|
Utils::FileSystem::createDirectory(path);
|
||||||
|
|
||||||
|
|
||||||
path += name + extension;
|
path += name + extension;
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
//
|
||||||
|
// Scraper.h
|
||||||
|
//
|
||||||
|
// Main scraper logic.
|
||||||
|
// Called from ScraperSearchComponent.
|
||||||
|
// Calls either GamesDBJSONScraper or ScreenScraper.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_APP_SCRAPERS_SCRAPER_H
|
#ifndef ES_APP_SCRAPERS_SCRAPER_H
|
||||||
#define ES_APP_SCRAPERS_SCRAPER_H
|
#define ES_APP_SCRAPERS_SCRAPER_H
|
||||||
|
@ -16,31 +24,29 @@
|
||||||
class FileData;
|
class FileData;
|
||||||
class SystemData;
|
class SystemData;
|
||||||
|
|
||||||
struct ScraperSearchParams
|
struct ScraperSearchParams {
|
||||||
{
|
|
||||||
SystemData* system;
|
SystemData* system;
|
||||||
FileData* game;
|
FileData* game;
|
||||||
|
|
||||||
std::string nameOverride;
|
std::string nameOverride;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ScraperSearchResult
|
struct ScraperSearchResult {
|
||||||
{
|
|
||||||
ScraperSearchResult() : mdl(GAME_METADATA) {};
|
ScraperSearchResult() : mdl(GAME_METADATA) {};
|
||||||
|
|
||||||
MetaDataList mdl;
|
MetaDataList mdl;
|
||||||
std::string imageUrl;
|
std::string imageUrl;
|
||||||
std::string thumbnailUrl;
|
std::string thumbnailUrl;
|
||||||
|
|
||||||
// Needed to pre-set the image type
|
// Needed to pre-set the image type.
|
||||||
std::string imageType;
|
std::string imageType;
|
||||||
};
|
};
|
||||||
|
|
||||||
// So let me explain why I've abstracted this so heavily.
|
// So let me explain why I've abstracted this so heavily.
|
||||||
// There are two ways I can think of that you'd want to write a scraper.
|
// There are two ways I can think of that you'd want to write a scraper.
|
||||||
|
|
||||||
// 1. Do some HTTP request(s) -> process it -> return the results
|
// 1. Do some HTTP request(s) -> process it -> return the results.
|
||||||
// 2. Do some local filesystem queries (an offline scraper) -> return the results
|
// 2. Do some local filesystem queries (an offline scraper) -> return the results.
|
||||||
|
|
||||||
// The first way needs to be asynchronous while it's waiting for the HTTP request to return.
|
// The first way needs to be asynchronous while it's waiting for the HTTP request to return.
|
||||||
// The second doesn't.
|
// The second doesn't.
|
||||||
|
@ -51,25 +57,26 @@ struct ScraperSearchResult
|
||||||
// ... process search ...
|
// ... process search ...
|
||||||
// return results;
|
// return results;
|
||||||
|
|
||||||
// We could do this if we used threads. Right now ES doesn't because I'm pretty sure I'll fuck it up,
|
// We could do this if we used threads. Right now ES doesn't because I'm pretty sure I'll
|
||||||
// and I'm not sure of the performance of threads on the Pi (single-core ARM).
|
// fuck it up, and I'm not sure of the performance of threads on the Pi (single-core ARM).
|
||||||
// We could also do this if we used coroutines.
|
// We could also do this if we used coroutines.
|
||||||
// I can't find a really good cross-platform coroutine library (x86/64/ARM Linux + Windows),
|
// I can't find a really good cross-platform coroutine library (x86/64/ARM Linux + Windows),
|
||||||
// and I don't want to spend more time chasing libraries than just writing it the long way once.
|
// and I don't want to spend more time chasing libraries than just writing it the long way once.
|
||||||
|
|
||||||
// So, I did it the "long" way.
|
// So, I did it the "long" way.
|
||||||
// ScraperSearchHandle - one logical search, e.g. "search for mario"
|
// ScraperSearchHandle - one logical search, e.g. "search for mario".
|
||||||
// ScraperRequest - encapsulates some sort of asynchronous request that will ultimately return some results
|
// ScraperRequest - encapsulates some sort of asynchronous request that will ultimately
|
||||||
// ScraperHttpRequest - implementation of ScraperRequest that waits on an HttpReq, then processes it with some processing function.
|
// return some results.
|
||||||
|
// ScraperHttpRequest - implementation of ScraperRequest that waits on an HttpReq, then
|
||||||
|
// processes it with some processing function.
|
||||||
|
|
||||||
|
// A scraper search gathers results from (potentially multiple) ScraperRequests.
|
||||||
// a scraper search gathers results from (potentially multiple) ScraperRequests
|
|
||||||
class ScraperRequest : public AsyncHandle
|
class ScraperRequest : public AsyncHandle
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ScraperRequest(std::vector<ScraperSearchResult>& resultsWrite);
|
ScraperRequest(std::vector<ScraperSearchResult>& resultsWrite);
|
||||||
|
|
||||||
// returns "true" once we're done
|
// Returns "true" once we're done.
|
||||||
virtual void update() = 0;
|
virtual void update() = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -77,7 +84,7 @@ protected:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// a single HTTP request that needs to be processed to get the results
|
// A single HTTP request that needs to be processed to get the results.
|
||||||
class ScraperHttpRequest : public ScraperRequest
|
class ScraperHttpRequest : public ScraperRequest
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -85,43 +92,46 @@ public:
|
||||||
virtual void update() override;
|
virtual void update() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void process(const std::unique_ptr<HttpReq>& req, std::vector<ScraperSearchResult>& results) = 0;
|
virtual void process(const std::unique_ptr<HttpReq>& req,
|
||||||
|
std::vector<ScraperSearchResult>& results) = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<HttpReq> mReq;
|
std::unique_ptr<HttpReq> mReq;
|
||||||
};
|
};
|
||||||
|
|
||||||
// a request to get a list of results
|
// A request to get a list of results.
|
||||||
class ScraperSearchHandle : public AsyncHandle
|
class ScraperSearchHandle : public AsyncHandle
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ScraperSearchHandle();
|
ScraperSearchHandle();
|
||||||
|
|
||||||
void update();
|
void update();
|
||||||
inline const std::vector<ScraperSearchResult>& getResults() const { assert(mStatus != ASYNC_IN_PROGRESS); return mResults; }
|
inline const std::vector<ScraperSearchResult>& getResults() const {
|
||||||
|
assert(mStatus != ASYNC_IN_PROGRESS); return mResults; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend std::unique_ptr<ScraperSearchHandle> startScraperSearch(const ScraperSearchParams& params);
|
friend std::unique_ptr<ScraperSearchHandle>
|
||||||
|
startScraperSearch(const ScraperSearchParams& params);
|
||||||
|
|
||||||
std::queue< std::unique_ptr<ScraperRequest> > mRequestQueue;
|
std::queue< std::unique_ptr<ScraperRequest> > mRequestQueue;
|
||||||
std::vector<ScraperSearchResult> mResults;
|
std::vector<ScraperSearchResult> mResults;
|
||||||
};
|
};
|
||||||
|
|
||||||
// will use the current scraper settings to pick the result source
|
// Will use the current scraper settings to pick the result source.
|
||||||
std::unique_ptr<ScraperSearchHandle> startScraperSearch(const ScraperSearchParams& params);
|
std::unique_ptr<ScraperSearchHandle> startScraperSearch(const ScraperSearchParams& params);
|
||||||
|
|
||||||
// returns a list of valid scraper names
|
// Returns a list of valid scraper names.
|
||||||
std::vector<std::string> getScraperList();
|
std::vector<std::string> getScraperList();
|
||||||
|
|
||||||
// returns true if the scraper configured in the settings is still valid
|
// Returns true if the scraper configured in the settings is still valid.
|
||||||
bool isValidConfiguredScraper();
|
bool isValidConfiguredScraper();
|
||||||
|
|
||||||
typedef void (*generate_scraper_requests_func)(const ScraperSearchParams& params, std::queue< std::unique_ptr<ScraperRequest> >& requests, std::vector<ScraperSearchResult>& results);
|
typedef void (*generate_scraper_requests_func)(const ScraperSearchParams& params,
|
||||||
|
std::queue< std::unique_ptr<ScraperRequest> >& requests,
|
||||||
|
std::vector<ScraperSearchResult>& results);
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Meta data asset downloading stuff.
|
// Meta data asset downloading stuff.
|
||||||
class MDResolveHandle : public AsyncHandle
|
class MDResolveHandle : public AsyncHandle
|
||||||
{
|
{
|
||||||
|
@ -129,7 +139,8 @@ public:
|
||||||
MDResolveHandle(const ScraperSearchResult& result, const ScraperSearchParams& search);
|
MDResolveHandle(const ScraperSearchResult& result, const ScraperSearchParams& search);
|
||||||
|
|
||||||
void update() override;
|
void update() override;
|
||||||
inline const ScraperSearchResult& getResult() const { assert(mStatus == ASYNC_DONE); return mResult; }
|
inline const ScraperSearchResult& getResult() const
|
||||||
|
{ assert(mStatus == ASYNC_DONE); return mResult; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ScraperSearchResult mResult;
|
ScraperSearchResult mResult;
|
||||||
|
@ -141,7 +152,8 @@ private:
|
||||||
class ImageDownloadHandle : public AsyncHandle
|
class ImageDownloadHandle : public AsyncHandle
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ImageDownloadHandle(const std::string& url, const std::string& path, int maxWidth, int maxHeight);
|
ImageDownloadHandle(const std::string& url, const std::string& path,
|
||||||
|
int maxWidth, int maxHeight);
|
||||||
|
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
|
@ -152,19 +164,24 @@ private:
|
||||||
int mMaxHeight;
|
int mMaxHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
//About the same as "~/.emulationstation/downloaded_images/[system_name]/[game_name].[url's extension]".
|
// About the same as:
|
||||||
//Will create the "downloaded_images" and "subdirectory" directories if they do not exist.
|
// "~/.emulationstation/downloaded_images/[system_name]/[game_name].[url's extension]".
|
||||||
std::string getSaveAsPath(const ScraperSearchParams& params, const std::string& suffix, const std::string& url);
|
// Will create the "downloaded_images" and "subdirectory" directories if they do not exist.
|
||||||
|
std::string getSaveAsPath(const ScraperSearchParams& params, const std::string& suffix,
|
||||||
|
const std::string& url);
|
||||||
|
|
||||||
//Will resize according to Settings::getInt("ScraperResizeWidth") and Settings::getInt("ScraperResizeHeight").
|
// Will resize according to Settings::getInt("ScraperResizeWidth") and
|
||||||
std::unique_ptr<ImageDownloadHandle> downloadImageAsync(const std::string& url, const std::string& saveAs);
|
// Settings::getInt("ScraperResizeHeight").
|
||||||
|
std::unique_ptr<ImageDownloadHandle> downloadImageAsync(const std::string& url,
|
||||||
|
const std::string& saveAs);
|
||||||
|
|
||||||
// Resolves all metadata assets that need to be downloaded.
|
// Resolves all metadata assets that need to be downloaded.
|
||||||
std::unique_ptr<MDResolveHandle> resolveMetaDataAssets(const ScraperSearchResult& result, const ScraperSearchParams& search);
|
std::unique_ptr<MDResolveHandle> resolveMetaDataAssets(const ScraperSearchResult& result,
|
||||||
|
const ScraperSearchParams& search);
|
||||||
|
|
||||||
//You can pass 0 for maxWidth or maxHeight to automatically keep the aspect ratio.
|
// You can pass 0 for maxWidth or maxHeight to automatically keep the aspect ratio.
|
||||||
//Will overwrite the image at [path] with the new resized one.
|
// It will overwrite the image at [path] with the new resized one.
|
||||||
//Returns true if successful, false otherwise.
|
// Returns true if successful, false otherwise.
|
||||||
bool resizeImage(const std::string& path, int maxWidth, int maxHeight);
|
bool resizeImage(const std::string& path, int maxWidth, int maxHeight);
|
||||||
|
|
||||||
#endif // ES_APP_SCRAPERS_SCRAPER_H
|
#endif // ES_APP_SCRAPERS_SCRAPER_H
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
//
|
||||||
|
// ScreenScraper.cpp
|
||||||
|
//
|
||||||
|
// Functions specifically for scraping from screenscraper.fr
|
||||||
|
// Called from Scraper.
|
||||||
|
//
|
||||||
|
|
||||||
#include "scrapers/ScreenScraper.h"
|
#include "scrapers/ScreenScraper.h"
|
||||||
|
|
||||||
#include "utils/TimeUtil.h"
|
#include "utils/TimeUtil.h"
|
||||||
|
@ -12,17 +19,15 @@
|
||||||
|
|
||||||
using namespace PlatformIds;
|
using namespace PlatformIds;
|
||||||
|
|
||||||
/**
|
// List of systems and their IDs from:
|
||||||
List of systems and their IDs from
|
// https://www.screenscraper.fr/api/systemesListe.php?devid=xxx&devpassword=yyy&softname=zzz&output=XML
|
||||||
https://www.screenscraper.fr/api/systemesListe.php?devid=xxx&devpassword=yyy&softname=zzz&output=XML
|
const std::map<PlatformId, unsigned short> screenscraper_platformid_map {
|
||||||
**/
|
|
||||||
const std::map<PlatformId, unsigned short> screenscraper_platformid_map{
|
|
||||||
{ THREEDO, 29 },
|
{ THREEDO, 29 },
|
||||||
{ AMIGA, 64 },
|
{ AMIGA, 64 },
|
||||||
{ AMSTRAD_CPC, 65 },
|
{ AMSTRAD_CPC, 65 },
|
||||||
{ APPLE_II, 86 },
|
{ APPLE_II, 86 },
|
||||||
{ ARCADE, 75 },
|
{ ARCADE, 75 },
|
||||||
{ ATARI_800, 26 }, // Use ATARI_2600 as an alias for atari 800
|
{ ATARI_800, 26 }, // Use ATARI_2600 as an alias for atari 800.
|
||||||
{ ATARI_2600, 26 },
|
{ ATARI_2600, 26 },
|
||||||
{ ATARI_5200, 40 },
|
{ ATARI_5200, 40 },
|
||||||
{ ATARI_7800, 41 },
|
{ ATARI_7800, 41 },
|
||||||
|
@ -30,7 +35,7 @@ const std::map<PlatformId, unsigned short> screenscraper_platformid_map{
|
||||||
{ ATARI_JAGUAR_CD, 171 },
|
{ ATARI_JAGUAR_CD, 171 },
|
||||||
{ ATARI_LYNX, 28 },
|
{ ATARI_LYNX, 28 },
|
||||||
{ ATARI_ST, 42},
|
{ ATARI_ST, 42},
|
||||||
// missing Atari XE ?
|
// Missing Atari XE ?
|
||||||
{ COLECOVISION, 48 },
|
{ COLECOVISION, 48 },
|
||||||
{ COMMODORE_64, 66 },
|
{ COMMODORE_64, 66 },
|
||||||
{ INTELLIVISION, 115 },
|
{ INTELLIVISION, 115 },
|
||||||
|
@ -72,7 +77,7 @@ const std::map<PlatformId, unsigned short> screenscraper_platformid_map{
|
||||||
{ PLAYSTATION, 57 },
|
{ PLAYSTATION, 57 },
|
||||||
{ PLAYSTATION_2, 58 },
|
{ PLAYSTATION_2, 58 },
|
||||||
{ PLAYSTATION_3, 59 },
|
{ PLAYSTATION_3, 59 },
|
||||||
// missing Sony Playstation 4 ?
|
// Missing Sony Playstation 4 ?
|
||||||
{ PLAYSTATION_VITA, 62 },
|
{ PLAYSTATION_VITA, 62 },
|
||||||
{ PLAYSTATION_PORTABLE, 61 },
|
{ PLAYSTATION_PORTABLE, 61 },
|
||||||
{ SUPER_NINTENDO, 4 },
|
{ SUPER_NINTENDO, 4 },
|
||||||
|
@ -88,12 +93,11 @@ const std::map<PlatformId, unsigned short> screenscraper_platformid_map{
|
||||||
{ TANDY, 144 }
|
{ TANDY, 144 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Helper XML parsing method, finding a node-by-name recursively.
|
// Helper XML parsing method, finding a node-by-name recursively.
|
||||||
pugi::xml_node find_node_by_name_re(const pugi::xml_node& node, const std::vector<std::string> node_names) {
|
pugi::xml_node find_node_by_name_re(const pugi::xml_node& node,
|
||||||
|
const std::vector<std::string> node_names) {
|
||||||
|
|
||||||
for (const std::string& _val : node_names)
|
for (const std::string& _val : node_names) {
|
||||||
{
|
|
||||||
pugi::xpath_query query_node_name((static_cast<std::string>("//") + _val).c_str());
|
pugi::xpath_query query_node_name((static_cast<std::string>("//") + _val).c_str());
|
||||||
pugi::xpath_node_set results = node.select_nodes(query_node_name);
|
pugi::xpath_node_set results = node.select_nodes(query_node_name);
|
||||||
|
|
||||||
|
@ -104,26 +108,25 @@ pugi::xml_node find_node_by_name_re(const pugi::xml_node& node, const std::vecto
|
||||||
return pugi::xml_node();
|
return pugi::xml_node();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help XML parsing method, finding an direct child XML node starting from the parent and filtering by an attribute value list.
|
// Help XML parsing method, finding an direct child XML node starting from the parent and
|
||||||
pugi::xml_node find_child_by_attribute_list(const pugi::xml_node& node_parent, const std::string& node_name, const std::string& attribute_name, const std::vector<std::string> attribute_values)
|
// filtering by an attribute value list.
|
||||||
|
pugi::xml_node find_child_by_attribute_list(const pugi::xml_node& node_parent,
|
||||||
|
const std::string& node_name, const std::string& attribute_name,
|
||||||
|
const std::vector<std::string> attribute_values)
|
||||||
{
|
{
|
||||||
for (auto _val : attribute_values)
|
for (auto _val : attribute_values) {
|
||||||
{
|
for (pugi::xml_node node : node_parent.children(node_name.c_str())) {
|
||||||
for (pugi::xml_node node : node_parent.children(node_name.c_str()))
|
|
||||||
{
|
|
||||||
|
|
||||||
if (strcmp(node.attribute(attribute_name.c_str()).value(), _val.c_str()) == 0)
|
if (strcmp(node.attribute(attribute_name.c_str()).value(), _val.c_str()) == 0)
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pugi::xml_node(NULL);
|
return pugi::xml_node(NULL);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
|
void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
|
||||||
std::queue< std::unique_ptr<ScraperRequest> >& requests,
|
std::queue< std::unique_ptr<ScraperRequest> >& requests,
|
||||||
std::vector<ScraperSearchResult>& results)
|
std::vector<ScraperSearchResult>& results)
|
||||||
{
|
{
|
||||||
std::string path;
|
std::string path;
|
||||||
|
|
||||||
|
@ -133,22 +136,23 @@ void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
|
||||||
auto& platforms = params.system->getPlatformIds();
|
auto& platforms = params.system->getPlatformIds();
|
||||||
std::vector<unsigned short> p_ids;
|
std::vector<unsigned short> p_ids;
|
||||||
|
|
||||||
// Get the IDs of each platform from the ScreenScraper list
|
// Get the IDs of each platform from the ScreenScraper list.
|
||||||
for (auto platformIt = platforms.cbegin(); platformIt != platforms.cend(); platformIt++)
|
for (auto platformIt = platforms.cbegin(); platformIt != platforms.cend(); platformIt++) {
|
||||||
{
|
|
||||||
auto mapIt = screenscraper_platformid_map.find(*platformIt);
|
auto mapIt = screenscraper_platformid_map.find(*platformIt);
|
||||||
|
|
||||||
if (mapIt != screenscraper_platformid_map.cend())
|
if (mapIt != screenscraper_platformid_map.cend()) {
|
||||||
{
|
|
||||||
p_ids.push_back(mapIt->second);
|
p_ids.push_back(mapIt->second);
|
||||||
}else{
|
}
|
||||||
LOG(LogWarning) << "ScreenScraper: no support for platform " << getPlatformName(*platformIt);
|
else {
|
||||||
// Add the scrape request without a platform/system ID
|
LOG(LogWarning) << "ScreenScraper: no support for platform " <<
|
||||||
requests.push(std::unique_ptr<ScraperRequest>(new ScreenScraperRequest(requests, results, path)));
|
getPlatformName(*platformIt);
|
||||||
|
// Add the scrape request without a platform/system ID.
|
||||||
|
requests.push(std::unique_ptr<ScraperRequest>
|
||||||
|
(new ScreenScraperRequest(requests, results, path)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the platform IDs and remove duplicates
|
// Sort the platform IDs and remove duplicates.
|
||||||
std::sort(p_ids.begin(), p_ids.end());
|
std::sort(p_ids.begin(), p_ids.end());
|
||||||
auto last = std::unique(p_ids.begin(), p_ids.end());
|
auto last = std::unique(p_ids.begin(), p_ids.end());
|
||||||
p_ids.erase(last, p_ids.end());
|
p_ids.erase(last, p_ids.end());
|
||||||
|
@ -157,22 +161,24 @@ void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
|
||||||
{
|
{
|
||||||
path += "&systemeid=";
|
path += "&systemeid=";
|
||||||
path += HttpReq::urlEncode(std::to_string(*platform));
|
path += HttpReq::urlEncode(std::to_string(*platform));
|
||||||
requests.push(std::unique_ptr<ScraperRequest>(new ScreenScraperRequest(requests, results, path)));
|
requests.push(std::unique_ptr<ScraperRequest>
|
||||||
|
(new ScreenScraperRequest(requests, results, path)));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenScraperRequest::process(const std::unique_ptr<HttpReq>& req, std::vector<ScraperSearchResult>& results)
|
void ScreenScraperRequest::process(const std::unique_ptr<HttpReq>& req,
|
||||||
|
std::vector<ScraperSearchResult>& results)
|
||||||
{
|
{
|
||||||
assert(req->status() == HttpReq::REQ_SUCCESS);
|
assert(req->status() == HttpReq::REQ_SUCCESS);
|
||||||
|
|
||||||
pugi::xml_document doc;
|
pugi::xml_document doc;
|
||||||
pugi::xml_parse_result parseResult = doc.load_string(req->getContent().c_str());
|
pugi::xml_parse_result parseResult = doc.load_string(req->getContent().c_str());
|
||||||
|
|
||||||
if (!parseResult)
|
if (!parseResult) {
|
||||||
{
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "ScreenScraperRequest - Error parsing XML." << std::endl << parseResult.description() << "";
|
ss << "ScreenScraperRequest - Error parsing XML." << std::endl <<
|
||||||
|
parseResult.description() << "";
|
||||||
|
|
||||||
std::string err = ss.str();
|
std::string err = ss.str();
|
||||||
setError(err);
|
setError(err);
|
||||||
|
@ -182,17 +188,15 @@ void ScreenScraperRequest::process(const std::unique_ptr<HttpReq>& req, std::vec
|
||||||
}
|
}
|
||||||
|
|
||||||
processGame(doc, results);
|
processGame(doc, results);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
|
||||||
void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc, std::vector<ScraperSearchResult>& out_results)
|
std::vector<ScraperSearchResult>& out_results)
|
||||||
{
|
{
|
||||||
pugi::xml_node data = xmldoc.child("Data");
|
pugi::xml_node data = xmldoc.child("Data");
|
||||||
pugi::xml_node game = data.child("jeu");
|
pugi::xml_node game = data.child("jeu");
|
||||||
|
|
||||||
if (game)
|
if (game) {
|
||||||
{
|
|
||||||
ScraperSearchResult result;
|
ScraperSearchResult result;
|
||||||
ScreenScraperRequest::ScreenScraperConfig ssConfig;
|
ScreenScraperRequest::ScreenScraperConfig ssConfig;
|
||||||
|
|
||||||
|
@ -200,114 +204,116 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc, std::ve
|
||||||
std::string language = Utils::String::toLower(ssConfig.language).c_str();
|
std::string language = Utils::String::toLower(ssConfig.language).c_str();
|
||||||
|
|
||||||
// Name fallback: US, WOR(LD). ( Xpath: Data/jeu[0]/noms/nom[*] ).
|
// Name fallback: US, WOR(LD). ( Xpath: Data/jeu[0]/noms/nom[*] ).
|
||||||
result.mdl.set("name", find_child_by_attribute_list(game.child("noms"), "nom", "region", { region, "wor", "us" , "ss", "eu", "jp" }).text().get());
|
result.mdl.set("name", find_child_by_attribute_list(game.child("noms"),
|
||||||
|
"nom", "region", { region, "wor", "us" , "ss", "eu", "jp" }).text().get());
|
||||||
|
|
||||||
// Description fallback language: EN, WOR(LD)
|
// Description fallback language: EN, WOR(LD).
|
||||||
std::string description = find_child_by_attribute_list(game.child("synopsis"), "synopsis", "langue", { language, "en", "wor" }).text().get();
|
std::string description = find_child_by_attribute_list(game.child("synopsis"),
|
||||||
|
"synopsis", "langue", { language, "en", "wor" }).text().get();
|
||||||
|
|
||||||
if (!description.empty()) {
|
if (!description.empty())
|
||||||
result.mdl.set("desc", Utils::String::replace(description, " ", " "));
|
result.mdl.set("desc", Utils::String::replace(description, " ", " "));
|
||||||
}
|
|
||||||
|
|
||||||
// Genre fallback language: EN. ( Xpath: Data/jeu[0]/genres/genre[*] )
|
// Genre fallback language: EN. ( Xpath: Data/jeu[0]/genres/genre[*] ).
|
||||||
result.mdl.set("genre", find_child_by_attribute_list(game.child("genres"), "genre", "langue", { language, "en" }).text().get());
|
result.mdl.set("genre", find_child_by_attribute_list(game.child("genres"),
|
||||||
|
"genre", "langue", { language, "en" }).text().get());
|
||||||
LOG(LogDebug) << "Genre: " << result.mdl.get("genre");
|
LOG(LogDebug) << "Genre: " << result.mdl.get("genre");
|
||||||
|
|
||||||
// Get the date proper. The API returns multiple 'date' children nodes to the 'dates' main child of 'jeu'.
|
// Get the date proper. The API returns multiple 'date' children nodes to the 'dates'
|
||||||
// Date fallback: WOR(LD), US, SS, JP, EU
|
// main child of 'jeu'.
|
||||||
std::string _date = find_child_by_attribute_list(game.child("dates"), "date", "region", { region, "wor", "us", "ss", "jp", "eu" }).text().get();
|
// Date fallback: WOR(LD), US, SS, JP, EU.
|
||||||
|
std::string _date = find_child_by_attribute_list(game.child("dates"), "date", "region",
|
||||||
|
{ region, "wor", "us", "ss", "jp", "eu" }).text().get();
|
||||||
LOG(LogDebug) << "Release Date (unparsed): " << _date;
|
LOG(LogDebug) << "Release Date (unparsed): " << _date;
|
||||||
|
|
||||||
// Date can be YYYY-MM-DD or just YYYY.
|
// Date can be YYYY-MM-DD or just YYYY.
|
||||||
if (_date.length() > 4)
|
if (_date.length() > 4) {
|
||||||
{
|
result.mdl.set("releasedate", Utils::Time::DateTime(
|
||||||
result.mdl.set("releasedate", Utils::Time::DateTime(Utils::Time::stringToTime(_date, "%Y-%m-%d")));
|
Utils::Time::stringToTime(_date, "%Y-%m-%d")));
|
||||||
} else if (_date.length() > 0)
|
}
|
||||||
{
|
else if (_date.length() > 0) {
|
||||||
result.mdl.set("releasedate", Utils::Time::DateTime(Utils::Time::stringToTime(_date, "%Y")));
|
result.mdl.set("releasedate", Utils::Time::DateTime(
|
||||||
|
Utils::Time::stringToTime(_date, "%Y")));
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG(LogDebug) << "Release Date (parsed): " << result.mdl.get("releasedate");
|
LOG(LogDebug) << "Release Date (parsed): " << result.mdl.get("releasedate");
|
||||||
|
|
||||||
/// Developer for the game( Xpath: Data/jeu[0]/developpeur )
|
/// Developer for the game( Xpath: Data/jeu[0]/developpeur ).
|
||||||
std::string developer = game.child("developpeur").text().get();
|
std::string developer = game.child("developpeur").text().get();
|
||||||
if (!developer.empty())
|
if (!developer.empty())
|
||||||
result.mdl.set("developer", Utils::String::replace(developer, " ", " "));
|
result.mdl.set("developer", Utils::String::replace(developer, " ", " "));
|
||||||
|
|
||||||
// Publisher for the game ( Xpath: Data/jeu[0]/editeur )
|
// Publisher for the game ( Xpath: Data/jeu[0]/editeur ).
|
||||||
std::string publisher = game.child("editeur").text().get();
|
std::string publisher = game.child("editeur").text().get();
|
||||||
if (!publisher.empty())
|
if (!publisher.empty())
|
||||||
result.mdl.set("publisher", Utils::String::replace(publisher, " ", " "));
|
result.mdl.set("publisher", Utils::String::replace(publisher, " ", " "));
|
||||||
|
|
||||||
// Players
|
// Players.
|
||||||
result.mdl.set("players", game.child("joueurs").text().get());
|
result.mdl.set("players", game.child("joueurs").text().get());
|
||||||
|
|
||||||
// TODO: Validate rating
|
// TODO: Validate rating.
|
||||||
if (Settings::getInstance()->getBool("ScrapeRatings") && game.child("note"))
|
if (Settings::getInstance()->getBool("ScrapeRatings") && game.child("note")) {
|
||||||
{
|
|
||||||
float ratingVal = (game.child("note").text().as_int() / 20.0f);
|
float ratingVal = (game.child("note").text().as_int() / 20.0f);
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << ratingVal;
|
ss << ratingVal;
|
||||||
result.mdl.set("rating", ss.str());
|
result.mdl.set("rating", ss.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Media super-node
|
// Media super-node.
|
||||||
pugi::xml_node media_list = game.child("medias");
|
pugi::xml_node media_list = game.child("medias");
|
||||||
|
|
||||||
if (media_list)
|
if (media_list) {
|
||||||
{
|
|
||||||
pugi::xml_node art = pugi::xml_node(NULL);
|
pugi::xml_node art = pugi::xml_node(NULL);
|
||||||
|
|
||||||
// Do an XPath query for media[type='$media_type'], then filter by region
|
// Do an XPath query for media[type='$media_type'], then filter by region.
|
||||||
// We need to do this because any child of 'medias' has the form
|
// We need to do this because any child of 'medias' has the form
|
||||||
// <media type="..." region="..." format="...">
|
// <media type="..." region="..." format="...">
|
||||||
// and we need to find the right media for the region.
|
// and we need to find the right media for the region.
|
||||||
pugi::xpath_node_set results = media_list.select_nodes((static_cast<std::string>("media[@type='") + ssConfig.media_name + "']").c_str());
|
pugi::xpath_node_set results = media_list.select_nodes((static_cast<std::string>
|
||||||
|
("media[@type='") + ssConfig.media_name + "']").c_str());
|
||||||
|
|
||||||
if (results.size())
|
if (results.size()) {
|
||||||
{
|
// Region fallback: WOR(LD), US, CUS(TOM?), JP, EU.
|
||||||
// Region fallback: WOR(LD), US, CUS(TOM?), JP, EU
|
for (auto _region : std::vector<std::string>{ region,
|
||||||
for (auto _region : std::vector<std::string>{ region, "wor", "us", "cus", "jp", "eu" })
|
"wor", "us", "cus", "jp", "eu" }) {
|
||||||
{
|
|
||||||
if (art)
|
if (art)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
for (auto node : results)
|
for (auto node : results) {
|
||||||
{
|
if (node.node().attribute("region").value() == _region) {
|
||||||
if (node.node().attribute("region").value() == _region)
|
|
||||||
{
|
|
||||||
art = node.node();
|
art = node.node();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // results
|
} // Results.
|
||||||
|
|
||||||
if (art)
|
if (art) {
|
||||||
{
|
// Sending a 'softname' containing space will make the image URLs returned
|
||||||
// Sending a 'softname' containing space will make the image URLs returned by the API also contain the space.
|
// by the API also contain the space. Escape any spaces in the URL here
|
||||||
// Escape any spaces in the URL here
|
|
||||||
result.imageUrl = Utils::String::replace(art.text().get(), " ", "%20");
|
result.imageUrl = Utils::String::replace(art.text().get(), " ", "%20");
|
||||||
|
|
||||||
// Get the media type returned by ScreenScraper
|
// Get the media type returned by ScreenScraper.
|
||||||
std::string media_type = art.attribute("format").value();
|
std::string media_type = art.attribute("format").value();
|
||||||
if (!media_type.empty())
|
if (!media_type.empty())
|
||||||
result.imageType = "." + media_type;
|
result.imageType = "." + media_type;
|
||||||
|
|
||||||
// Ask for the same image, but with a smaller size, for the thumbnail displayed during scraping
|
// Ask for the same image, but with a smaller size, for the thumbnail
|
||||||
|
// displayed during scraping.
|
||||||
result.thumbnailUrl = result.imageUrl + "&maxheight=250";
|
result.thumbnailUrl = result.imageUrl + "&maxheight=250";
|
||||||
}else{
|
}
|
||||||
|
else {
|
||||||
LOG(LogDebug) << "Failed to find media XML node with name=" << ssConfig.media_name;
|
LOG(LogDebug) << "Failed to find media XML node with name=" << ssConfig.media_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out_results.push_back(result);
|
out_results.push_back(result);
|
||||||
} // game
|
} // Game.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currently not used in this module
|
// Currently not used in this module.
|
||||||
void ScreenScraperRequest::processList(const pugi::xml_document& xmldoc, std::vector<ScraperSearchResult>& results)
|
void ScreenScraperRequest::processList(const pugi::xml_document& xmldoc,
|
||||||
|
std::vector<ScraperSearchResult>& results)
|
||||||
{
|
{
|
||||||
assert(mRequestQueue != nullptr);
|
assert(mRequestQueue != nullptr);
|
||||||
|
|
||||||
|
@ -321,26 +327,26 @@ void ScreenScraperRequest::processList(const pugi::xml_document& xmldoc, std::ve
|
||||||
|
|
||||||
ScreenScraperRequest::ScreenScraperConfig ssConfig;
|
ScreenScraperRequest::ScreenScraperConfig ssConfig;
|
||||||
|
|
||||||
// limit the number of results per platform, not in total.
|
// Limit the number of results per platform, not in total.
|
||||||
// otherwise if the first platform returns >= 7 games
|
// Otherwise if the first platform returns >= 7 games
|
||||||
// but the second platform contains the relevant game,
|
// but the second platform contains the relevant game,
|
||||||
// the relevant result would not be shown.
|
// the relevant result would not be shown.
|
||||||
for (int i = 0; game && i < MAX_SCRAPER_RESULTS; i++)
|
for (int i = 0; game && i < MAX_SCRAPER_RESULTS; i++) {
|
||||||
{
|
|
||||||
std::string id = game.child("id").text().get();
|
std::string id = game.child("id").text().get();
|
||||||
std::string name = game.child("nom").text().get();
|
std::string name = game.child("nom").text().get();
|
||||||
std::string platformId = game.child("systemeid").text().get();
|
std::string platformId = game.child("systemeid").text().get();
|
||||||
std::string path = ssConfig.getGameSearchUrl(name) + "&systemeid=" + platformId + "&gameid=" + id;
|
std::string path = ssConfig.getGameSearchUrl(name) + "&systemeid=" +
|
||||||
|
platformId + "&gameid=" + id;
|
||||||
|
|
||||||
mRequestQueue->push(std::unique_ptr<ScraperRequest>(new ScreenScraperRequest(results, path)));
|
mRequestQueue->push(std::unique_ptr<ScraperRequest>
|
||||||
|
(new ScreenScraperRequest(results, path)));
|
||||||
|
|
||||||
game = game.next_sibling("jeu");
|
game = game.next_sibling("jeu");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScreenScraperRequest::ScreenScraperConfig::getGameSearchUrl(const std::string gameName) const
|
std::string ScreenScraperRequest::ScreenScraperConfig::getGameSearchUrl(
|
||||||
|
const std::string gameName) const
|
||||||
{
|
{
|
||||||
return API_URL_BASE
|
return API_URL_BASE
|
||||||
+ "/jeuInfos.php?devid=" + Utils::String::scramble(API_DEV_U, API_DEV_KEY)
|
+ "/jeuInfos.php?devid=" + Utils::String::scramble(API_DEV_U, API_DEV_KEY)
|
||||||
|
@ -348,5 +354,4 @@ std::string ScreenScraperRequest::ScreenScraperConfig::getGameSearchUrl(const st
|
||||||
+ "&softname=" + HttpReq::urlEncode(API_SOFT_NAME)
|
+ "&softname=" + HttpReq::urlEncode(API_SOFT_NAME)
|
||||||
+ "&output=xml"
|
+ "&output=xml"
|
||||||
+ "&romnom=" + HttpReq::urlEncode(gameName);
|
+ "&romnom=" + HttpReq::urlEncode(gameName);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
//
|
||||||
|
// ScreenScraper.h
|
||||||
|
//
|
||||||
|
// Functions specifically for scraping from screenscraper.fr
|
||||||
|
// Called from Scraper.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_APP_SCRAPERS_SCREEN_SCRAPER_H
|
#ifndef ES_APP_SCRAPERS_SCREEN_SCRAPER_H
|
||||||
#define ES_APP_SCRAPERS_SCREEN_SCRAPER_H
|
#define ES_APP_SCRAPERS_SCREEN_SCRAPER_H
|
||||||
|
@ -7,67 +14,76 @@
|
||||||
|
|
||||||
namespace pugi { class xml_document; }
|
namespace pugi { class xml_document; }
|
||||||
|
|
||||||
|
void screenscraper_generate_scraper_requests(
|
||||||
void screenscraper_generate_scraper_requests(const ScraperSearchParams& params, std::queue< std::unique_ptr<ScraperRequest> >& requests,
|
const ScraperSearchParams& params,
|
||||||
std::vector<ScraperSearchResult>& results);
|
std::queue< std::unique_ptr<ScraperRequest> >& requests,
|
||||||
|
std::vector<ScraperSearchResult>& results);
|
||||||
|
|
||||||
class ScreenScraperRequest : public ScraperHttpRequest
|
class ScreenScraperRequest : public ScraperHttpRequest
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// ctor for a GetGameList request
|
// ctor for a GetGameList request.
|
||||||
ScreenScraperRequest(std::queue< std::unique_ptr<ScraperRequest> >& requestsWrite, std::vector<ScraperSearchResult>& resultsWrite, const std::string& url) : ScraperHttpRequest(resultsWrite, url), mRequestQueue(&requestsWrite) {}
|
ScreenScraperRequest(std::queue< std::unique_ptr<ScraperRequest> >& requestsWrite,
|
||||||
// ctor for a GetGame request
|
std::vector<ScraperSearchResult>& resultsWrite,
|
||||||
ScreenScraperRequest(std::vector<ScraperSearchResult>& resultsWrite, const std::string& url) : ScraperHttpRequest(resultsWrite, url), mRequestQueue(nullptr) {}
|
const std::string& url) : ScraperHttpRequest(resultsWrite, url),
|
||||||
|
mRequestQueue(&requestsWrite) {}
|
||||||
|
|
||||||
// Settings for the scraper
|
// ctor for a GetGame request.
|
||||||
|
ScreenScraperRequest(std::vector<ScraperSearchResult>& resultsWrite,
|
||||||
|
const std::string& url) : ScraperHttpRequest(resultsWrite, url),
|
||||||
|
mRequestQueue(nullptr) {}
|
||||||
|
|
||||||
|
// Settings for the scraper.
|
||||||
static const struct ScreenScraperConfig {
|
static const struct ScreenScraperConfig {
|
||||||
std::string getGameSearchUrl(const std::string gameName) const;
|
std::string getGameSearchUrl(const std::string gameName) const;
|
||||||
|
|
||||||
// Access to the API
|
// Access to the API.
|
||||||
const std::string API_DEV_U = { 91, 32, 7, 17 };
|
const std::string API_DEV_U =
|
||||||
const std::string API_DEV_P = { 108, 28, 54, 55, 83, 43, 91, 44, 30, 22, 41, 12, 0, 108, 38, 29 };
|
{ 91, 32, 7, 17 };
|
||||||
const std::string API_DEV_KEY = { 54, 73, 115, 100, 101, 67, 111, 107, 79, 66, 68, 66, 67, 56, 118, 77, 54, 88, 101, 54 };
|
const std::string API_DEV_P =
|
||||||
|
{ 108, 28, 54, 55, 83, 43, 91, 44, 30, 22, 41, 12, 0, 108, 38, 29 };
|
||||||
|
const std::string API_DEV_KEY =
|
||||||
|
{ 54, 73, 115, 100, 101, 67, 111, 107, 79, 66, 68, 66, 67, 56, 118, 77, 54, 88, 101, 54 };
|
||||||
const std::string API_URL_BASE = "https://www.screenscraper.fr/api2";
|
const std::string API_URL_BASE = "https://www.screenscraper.fr/api2";
|
||||||
const std::string API_SOFT_NAME = "Emulationstation-DE " + static_cast<std::string>(PROGRAM_VERSION_STRING);
|
const std::string API_SOFT_NAME = "Emulationstation-DE " +
|
||||||
|
static_cast<std::string>(PROGRAM_VERSION_STRING);
|
||||||
|
|
||||||
/** Which type of image artwork we need. Possible values (not a comprehensive list):
|
// Which type of image artwork we need. Possible values (not a comprehensive list):
|
||||||
- ss: in-game screenshot
|
// - ss: in-game screenshot
|
||||||
- box-3D: 3D boxart
|
// - box-3D: 3D boxart
|
||||||
- box-2D: 2D boxart (default)
|
// - box-2D: 2D boxart (default)
|
||||||
- screenmarque : marquee
|
// - screenmarque : marquee
|
||||||
- sstitle: in-game start screenshot
|
// - sstitle: in-game start screenshot
|
||||||
- steamgrid: Steam artwork
|
// - steamgrid: Steam artwork
|
||||||
- wheel: spine
|
// - wheel: spine
|
||||||
- support-2D: media showing the 2d boxart on the cart
|
// - support-2D: media showing the 2d boxart on the cart
|
||||||
- support-3D: media showing the 3d boxart on the cart
|
// - support-3D: media showing the 3d boxart on the cart
|
||||||
|
//
|
||||||
Note that no all games contain values for these, so we default to "box-2D" since it's the most common.
|
// Note that not all games contain values for all these, so we default to "box-2D"
|
||||||
**/
|
// since it's the most common.
|
||||||
|
//
|
||||||
std::string media_name = "box-2D";
|
std::string media_name = "box-2D";
|
||||||
|
|
||||||
// Which Region to use when selecting the artwork
|
// Which Region to use when selecting the artwork.
|
||||||
// Applies to: artwork, name of the game, date of release
|
// Applies to: artwork, name of the game, date of release.
|
||||||
std::string region = "US";
|
std::string region = "US";
|
||||||
|
|
||||||
// Which Language to use when selecting the textual information
|
// Which Language to use when selecting the textual information.
|
||||||
// Applies to: description, genre
|
// Applies to: description, genre.
|
||||||
std::string language = "EN";
|
std::string language = "EN";
|
||||||
|
|
||||||
ScreenScraperConfig() {};
|
ScreenScraperConfig() {};
|
||||||
} configuration;
|
} configuration;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void process(const std::unique_ptr<HttpReq>& req, std::vector<ScraperSearchResult>& results) override;
|
void process(const std::unique_ptr<HttpReq>& req,
|
||||||
|
std::vector<ScraperSearchResult>& results) override;
|
||||||
|
|
||||||
void processList(const pugi::xml_document& xmldoc, std::vector<ScraperSearchResult>& results);
|
void processList(const pugi::xml_document& xmldoc, std::vector<ScraperSearchResult>& results);
|
||||||
void processGame(const pugi::xml_document& xmldoc, std::vector<ScraperSearchResult>& results);
|
void processGame(const pugi::xml_document& xmldoc, std::vector<ScraperSearchResult>& results);
|
||||||
bool isGameRequest() { return !mRequestQueue; }
|
bool isGameRequest() { return !mRequestQueue; }
|
||||||
|
|
||||||
std::queue< std::unique_ptr<ScraperRequest> >* mRequestQueue;
|
std::queue< std::unique_ptr<ScraperRequest> >* mRequestQueue;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif // ES_APP_SCRAPERS_SCREEN_SCRAPER_H
|
#endif // ES_APP_SCRAPERS_SCREEN_SCRAPER_H
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
|
//
|
||||||
|
// AsyncHandle.h
|
||||||
|
//
|
||||||
|
// Asynchronous operations used by ScraperSearchComponent and Scraper.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_CORE_ASYNC_HANDLE_H
|
#ifndef ES_CORE_ASYNC_HANDLE_H
|
||||||
#define ES_CORE_ASYNC_HANDLE_H
|
#define ES_CORE_ASYNC_HANDLE_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
enum AsyncHandleStatus
|
enum AsyncHandleStatus {
|
||||||
{
|
|
||||||
ASYNC_IN_PROGRESS,
|
ASYNC_IN_PROGRESS,
|
||||||
ASYNC_ERROR,
|
ASYNC_ERROR,
|
||||||
ASYNC_DONE
|
ASYNC_DONE
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle for some asynchronous operation.
|
// Handle for some asynchronous operations.
|
||||||
class AsyncHandle
|
class AsyncHandle
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -23,11 +28,11 @@ public:
|
||||||
// Update and return the latest status.
|
// Update and return the latest status.
|
||||||
inline AsyncHandleStatus status() { update(); return mStatus; }
|
inline AsyncHandleStatus status() { update(); return mStatus; }
|
||||||
|
|
||||||
// User-friendly string of our current status. Will return error message if status() == SEARCH_ERROR.
|
// User-friendly string of our current status.
|
||||||
|
// Will return error message if status() == SEARCH_ERROR.
|
||||||
inline std::string getStatusString()
|
inline std::string getStatusString()
|
||||||
{
|
{
|
||||||
switch(mStatus)
|
switch (mStatus) {
|
||||||
{
|
|
||||||
case ASYNC_IN_PROGRESS:
|
case ASYNC_IN_PROGRESS:
|
||||||
return "in progress";
|
return "in progress";
|
||||||
case ASYNC_ERROR:
|
case ASYNC_ERROR:
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
//
|
||||||
|
// HttpReq.cpp
|
||||||
|
//
|
||||||
|
// HTTP request functions.
|
||||||
|
// Used by Scraper, GamesDBJSONScraper, GamesDBJSONScraperResources and
|
||||||
|
// ScreenScraper to download game information and media files.
|
||||||
|
//
|
||||||
|
|
||||||
#include "HttpReq.h"
|
#include "HttpReq.h"
|
||||||
|
|
||||||
#include "utils/FileSystemUtil.h"
|
#include "utils/FileSystemUtil.h"
|
||||||
|
@ -10,17 +18,15 @@ std::map<CURL*, HttpReq*> HttpReq::s_requests;
|
||||||
|
|
||||||
std::string HttpReq::urlEncode(const std::string &s)
|
std::string HttpReq::urlEncode(const std::string &s)
|
||||||
{
|
{
|
||||||
const std::string unreserved = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~";
|
const std::string unreserved =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~";
|
||||||
|
|
||||||
std::string escaped="";
|
std::string escaped="";
|
||||||
for(size_t i=0; i<s.length(); i++)
|
for (size_t i=0; i<s.length(); i++) {
|
||||||
{
|
if (unreserved.find_first_of(s[i]) != std::string::npos) {
|
||||||
if (unreserved.find_first_of(s[i]) != std::string::npos)
|
|
||||||
{
|
|
||||||
escaped.push_back(s[i]);
|
escaped.push_back(s[i]);
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
escaped.append("%");
|
escaped.append("%");
|
||||||
char buf[3];
|
char buf[3];
|
||||||
sprintf(buf, "%.2X", (unsigned char)s[i]);
|
sprintf(buf, "%.2X", (unsigned char)s[i]);
|
||||||
|
@ -32,81 +38,75 @@ std::string HttpReq::urlEncode(const std::string &s)
|
||||||
|
|
||||||
bool HttpReq::isUrl(const std::string& str)
|
bool HttpReq::isUrl(const std::string& str)
|
||||||
{
|
{
|
||||||
//the worst guess
|
// The worst guess.
|
||||||
return (!str.empty() && !Utils::FileSystem::exists(str) &&
|
return (!str.empty() && !Utils::FileSystem::exists(str) &&
|
||||||
(str.find("http://") != std::string::npos || str.find("https://") != std::string::npos || str.find("www.") != std::string::npos));
|
(str.find("http://") != std::string::npos || str.find("https://") !=
|
||||||
|
std::string::npos || str.find("www.") != std::string::npos));
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpReq::HttpReq(const std::string& url)
|
HttpReq::HttpReq(const std::string& url)
|
||||||
: mStatus(REQ_IN_PROGRESS), mHandle(NULL)
|
: mStatus(REQ_IN_PROGRESS), mHandle(NULL)
|
||||||
{
|
{
|
||||||
mHandle = curl_easy_init();
|
mHandle = curl_easy_init();
|
||||||
|
|
||||||
if(mHandle == NULL)
|
if (mHandle == NULL) {
|
||||||
{
|
|
||||||
mStatus = REQ_IO_ERROR;
|
mStatus = REQ_IO_ERROR;
|
||||||
onError("curl_easy_init failed");
|
onError("curl_easy_init failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//set the url
|
// Set the url.
|
||||||
CURLcode err = curl_easy_setopt(mHandle, CURLOPT_URL, url.c_str());
|
CURLcode err = curl_easy_setopt(mHandle, CURLOPT_URL, url.c_str());
|
||||||
if(err != CURLE_OK)
|
if (err != CURLE_OK) {
|
||||||
{
|
|
||||||
mStatus = REQ_IO_ERROR;
|
mStatus = REQ_IO_ERROR;
|
||||||
onError(curl_easy_strerror(err));
|
onError(curl_easy_strerror(err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//set curl to handle redirects
|
// Set curl to handle redirects.
|
||||||
err = curl_easy_setopt(mHandle, CURLOPT_FOLLOWLOCATION, 1L);
|
err = curl_easy_setopt(mHandle, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
if(err != CURLE_OK)
|
if (err != CURLE_OK) {
|
||||||
{
|
|
||||||
mStatus = REQ_IO_ERROR;
|
mStatus = REQ_IO_ERROR;
|
||||||
onError(curl_easy_strerror(err));
|
onError(curl_easy_strerror(err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//set curl max redirects
|
// Set curl max redirects.
|
||||||
err = curl_easy_setopt(mHandle, CURLOPT_MAXREDIRS, 2L);
|
err = curl_easy_setopt(mHandle, CURLOPT_MAXREDIRS, 2L);
|
||||||
if(err != CURLE_OK)
|
if (err != CURLE_OK) {
|
||||||
{
|
|
||||||
mStatus = REQ_IO_ERROR;
|
mStatus = REQ_IO_ERROR;
|
||||||
onError(curl_easy_strerror(err));
|
onError(curl_easy_strerror(err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//set curl restrict redirect protocols
|
// Set curl restrict redirect protocols.
|
||||||
err = curl_easy_setopt(mHandle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
err = curl_easy_setopt(mHandle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
||||||
if(err != CURLE_OK)
|
if (err != CURLE_OK) {
|
||||||
{
|
|
||||||
mStatus = REQ_IO_ERROR;
|
mStatus = REQ_IO_ERROR;
|
||||||
onError(curl_easy_strerror(err));
|
onError(curl_easy_strerror(err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//tell curl how to write the data
|
// Tell curl how to write the data.
|
||||||
err = curl_easy_setopt(mHandle, CURLOPT_WRITEFUNCTION, &HttpReq::write_content);
|
err = curl_easy_setopt(mHandle, CURLOPT_WRITEFUNCTION, &HttpReq::write_content);
|
||||||
if(err != CURLE_OK)
|
if (err != CURLE_OK) {
|
||||||
{
|
|
||||||
mStatus = REQ_IO_ERROR;
|
mStatus = REQ_IO_ERROR;
|
||||||
onError(curl_easy_strerror(err));
|
onError(curl_easy_strerror(err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//give curl a pointer to this HttpReq so we know where to write the data *to* in our write function
|
// Give curl a pointer to this HttpReq so we know where to write the
|
||||||
|
// data *to* in our write function.
|
||||||
err = curl_easy_setopt(mHandle, CURLOPT_WRITEDATA, this);
|
err = curl_easy_setopt(mHandle, CURLOPT_WRITEDATA, this);
|
||||||
if(err != CURLE_OK)
|
if (err != CURLE_OK) {
|
||||||
{
|
|
||||||
mStatus = REQ_IO_ERROR;
|
mStatus = REQ_IO_ERROR;
|
||||||
onError(curl_easy_strerror(err));
|
onError(curl_easy_strerror(err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//add the handle to our multi
|
// Add the handle to our multi.
|
||||||
CURLMcode merr = curl_multi_add_handle(s_multi_handle, mHandle);
|
CURLMcode merr = curl_multi_add_handle(s_multi_handle, mHandle);
|
||||||
if(merr != CURLM_OK)
|
if (merr != CURLM_OK) {
|
||||||
{
|
|
||||||
mStatus = REQ_IO_ERROR;
|
mStatus = REQ_IO_ERROR;
|
||||||
onError(curl_multi_strerror(merr));
|
onError(curl_multi_strerror(merr));
|
||||||
return;
|
return;
|
||||||
|
@ -117,14 +117,14 @@ HttpReq::HttpReq(const std::string& url)
|
||||||
|
|
||||||
HttpReq::~HttpReq()
|
HttpReq::~HttpReq()
|
||||||
{
|
{
|
||||||
if(mHandle)
|
if (mHandle) {
|
||||||
{
|
|
||||||
s_requests.erase(mHandle);
|
s_requests.erase(mHandle);
|
||||||
|
|
||||||
CURLMcode merr = curl_multi_remove_handle(s_multi_handle, mHandle);
|
CURLMcode merr = curl_multi_remove_handle(s_multi_handle, mHandle);
|
||||||
|
|
||||||
if(merr != CURLM_OK)
|
if (merr != CURLM_OK)
|
||||||
LOG(LogError) << "Error removing curl_easy handle from curl_multi: " << curl_multi_strerror(merr);
|
LOG(LogError) << "Error removing curl_easy handle from curl_multi: " <<
|
||||||
|
curl_multi_strerror(merr);
|
||||||
|
|
||||||
curl_easy_cleanup(mHandle);
|
curl_easy_cleanup(mHandle);
|
||||||
}
|
}
|
||||||
|
@ -132,12 +132,10 @@ HttpReq::~HttpReq()
|
||||||
|
|
||||||
HttpReq::Status HttpReq::status()
|
HttpReq::Status HttpReq::status()
|
||||||
{
|
{
|
||||||
if(mStatus == REQ_IN_PROGRESS)
|
if (mStatus == REQ_IN_PROGRESS) {
|
||||||
{
|
|
||||||
int handle_count;
|
int handle_count;
|
||||||
CURLMcode merr = curl_multi_perform(s_multi_handle, &handle_count);
|
CURLMcode merr = curl_multi_perform(s_multi_handle, &handle_count);
|
||||||
if(merr != CURLM_OK && merr != CURLM_CALL_MULTI_PERFORM)
|
if (merr != CURLM_OK && merr != CURLM_CALL_MULTI_PERFORM) {
|
||||||
{
|
|
||||||
mStatus = REQ_IO_ERROR;
|
mStatus = REQ_IO_ERROR;
|
||||||
onError(curl_multi_strerror(merr));
|
onError(curl_multi_strerror(merr));
|
||||||
return mStatus;
|
return mStatus;
|
||||||
|
@ -145,22 +143,19 @@ HttpReq::Status HttpReq::status()
|
||||||
|
|
||||||
int msgs_left;
|
int msgs_left;
|
||||||
CURLMsg* msg;
|
CURLMsg* msg;
|
||||||
while((msg = curl_multi_info_read(s_multi_handle, &msgs_left)) != nullptr)
|
while ((msg = curl_multi_info_read(s_multi_handle, &msgs_left)) != nullptr) {
|
||||||
{
|
if (msg->msg == CURLMSG_DONE) {
|
||||||
if(msg->msg == CURLMSG_DONE)
|
|
||||||
{
|
|
||||||
HttpReq* req = s_requests[msg->easy_handle];
|
HttpReq* req = s_requests[msg->easy_handle];
|
||||||
|
|
||||||
if(req == NULL)
|
if (req == NULL) {
|
||||||
{
|
|
||||||
LOG(LogError) << "Cannot find easy handle!";
|
LOG(LogError) << "Cannot find easy handle!";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(msg->data.result == CURLE_OK)
|
if (msg->data.result == CURLE_OK) {
|
||||||
{
|
|
||||||
req->mStatus = REQ_SUCCESS;
|
req->mStatus = REQ_SUCCESS;
|
||||||
}else{
|
}
|
||||||
|
else {
|
||||||
req->mStatus = REQ_IO_ERROR;
|
req->mStatus = REQ_IO_ERROR;
|
||||||
req->onError(curl_easy_strerror(msg->data.result));
|
req->onError(curl_easy_strerror(msg->data.result));
|
||||||
}
|
}
|
||||||
|
@ -187,9 +182,9 @@ std::string HttpReq::getErrorMsg()
|
||||||
return mErrorMsg;
|
return mErrorMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
//used as a curl callback
|
// Used as a curl callback.
|
||||||
//size = size of an element, nmemb = number of elements
|
// size = size of an element, nmemb = number of elements.
|
||||||
//return value is number of elements successfully read
|
// Return value is number of elements successfully read.
|
||||||
size_t HttpReq::write_content(void* buff, size_t size, size_t nmemb, void* req_ptr)
|
size_t HttpReq::write_content(void* buff, size_t size, size_t nmemb, void* req_ptr)
|
||||||
{
|
{
|
||||||
std::stringstream& ss = ((HttpReq*)req_ptr)->mContent;
|
std::stringstream& ss = ((HttpReq*)req_ptr)->mContent;
|
||||||
|
@ -198,8 +193,8 @@ size_t HttpReq::write_content(void* buff, size_t size, size_t nmemb, void* req_p
|
||||||
return nmemb;
|
return nmemb;
|
||||||
}
|
}
|
||||||
|
|
||||||
//used as a curl callback
|
// Used as a curl callback.
|
||||||
/*int HttpReq::update_progress(void* req_ptr, double dlTotal, double dlNow, double ulTotal, double ulNow)
|
//int HttpReq::update_progress(void* req_ptr, double dlTotal,
|
||||||
{
|
// double dlNow, double ulTotal, double ulNow)
|
||||||
|
//{
|
||||||
}*/
|
//}
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
//
|
||||||
|
// HttpReq.h
|
||||||
|
//
|
||||||
|
// HTTP request functions.
|
||||||
|
// Used by Scraper, GamesDBJSONScraper, GamesDBJSONScraperResources and
|
||||||
|
// ScreenScraper to download game information and media files.
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_CORE_HTTP_REQ_H
|
#ifndef ES_CORE_HTTP_REQ_H
|
||||||
#define ES_CORE_HTTP_REQ_H
|
#define ES_CORE_HTTP_REQ_H
|
||||||
|
@ -6,22 +14,27 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
/* Usage:
|
// Usage:
|
||||||
* HttpReq myRequest("www.google.com", "/index.html");
|
// HttpReq myRequest("www.duckduckgo.com", "/index.html");
|
||||||
* //for blocking behavior: while(myRequest.status() == HttpReq::REQ_IN_PROGRESS);
|
//
|
||||||
* //for non-blocking behavior: check if(myRequest.status() != HttpReq::REQ_IN_PROGRESS) in some sort of update method
|
// For blocking behavior:
|
||||||
*
|
// while (myRequest.status() == HttpReq::REQ_IN_PROGRESS);
|
||||||
* //once one of those completes, the request is ready
|
//
|
||||||
* if(myRequest.status() != REQ_SUCCESS)
|
// For non-blocking behavior:
|
||||||
* {
|
// Check 'if(myRequest.status() != HttpReq::REQ_IN_PROGRESS)' in some sort of update method.
|
||||||
* //an error occured
|
//
|
||||||
* LOG(LogError) << "HTTP request error - " << myRequest.getErrorMessage();
|
// Once one of those calls complete, the request is ready.
|
||||||
* return;
|
//
|
||||||
* }
|
// Do something like this to capture errors:
|
||||||
*
|
// if(myRequest.status() != REQ_SUCCESS)
|
||||||
* std::string content = myRequest.getContent();
|
// {
|
||||||
* //process contents...
|
// // An error occured.
|
||||||
*/
|
// LOG(LogError) << "HTTP request error - " << myRequest.getErrorMessage();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This is how to read the returned content:
|
||||||
|
// std::string content = myRequest.getContent();
|
||||||
|
|
||||||
class HttpReq
|
class HttpReq
|
||||||
{
|
{
|
||||||
|
@ -30,31 +43,31 @@ public:
|
||||||
|
|
||||||
~HttpReq();
|
~HttpReq();
|
||||||
|
|
||||||
enum Status
|
enum Status {
|
||||||
{
|
REQ_IN_PROGRESS, // Request is in progress.
|
||||||
REQ_IN_PROGRESS, //request is in progress
|
REQ_SUCCESS, // Request completed successfully, get it with getContent().
|
||||||
REQ_SUCCESS, //request completed successfully, get it with getContent()
|
|
||||||
|
|
||||||
REQ_IO_ERROR, //some error happened, get it with getErrorMsg()
|
REQ_IO_ERROR, // Some error happened, get it with getErrorMsg().
|
||||||
REQ_BAD_STATUS_CODE, //some invalid HTTP response status code happened (non-200)
|
REQ_BAD_STATUS_CODE, // Some invalid HTTP response status code happened (non-200).
|
||||||
REQ_INVALID_RESPONSE //the HTTP response was invalid
|
REQ_INVALID_RESPONSE // The HTTP response was invalid.
|
||||||
};
|
};
|
||||||
|
|
||||||
Status status(); //process any received data and return the status afterwards
|
Status status(); // Process any received data and return the status afterwards.
|
||||||
|
|
||||||
std::string getErrorMsg();
|
std::string getErrorMsg();
|
||||||
|
|
||||||
std::string getContent() const; // mStatus must be REQ_SUCCESS
|
std::string getContent() const; // mStatus must be REQ_SUCCESS.
|
||||||
|
|
||||||
static std::string urlEncode(const std::string &s);
|
static std::string urlEncode(const std::string &s);
|
||||||
static bool isUrl(const std::string& s);
|
static bool isUrl(const std::string& s);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static size_t write_content(void* buff, size_t size, size_t nmemb, void* req_ptr);
|
static size_t write_content(void* buff, size_t size, size_t nmemb, void* req_ptr);
|
||||||
//static int update_progress(void* req_ptr, double dlTotal, double dlNow, double ulTotal, double ulNow);
|
//static int update_progress(void* req_ptr, double dlTotal, double dlNow,
|
||||||
|
// double ulTotal, double ulNow);
|
||||||
|
|
||||||
//god dammit libcurl why can't you have some way to check the status of an individual handle
|
// God dammit libcurl why can't you have some way to check the status of an
|
||||||
//why do I have to handle ALL messages at once
|
// individual handle why do I have to handle ALL messages at once.
|
||||||
static std::map<CURL*, HttpReq*> s_requests;
|
static std::map<CURL*, HttpReq*> s_requests;
|
||||||
|
|
||||||
static CURLM* s_multi_handle;
|
static CURLM* s_multi_handle;
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
//
|
//
|
||||||
// Settings.cpp
|
// Settings.cpp
|
||||||
//
|
//
|
||||||
// Functions to read from and write to the configuration file es_settings.cfg
|
// Functions to read from and write to the configuration file es_settings.cfg.
|
||||||
// are included here. The default values for the application settings are
|
// The default values for the application settings are defined here as well.
|
||||||
// defined here as well.
|
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
//
|
//
|
||||||
// Settings.h
|
// Settings.h
|
||||||
//
|
//
|
||||||
// Functions to read from and write to the configuration file es_settings.cfg
|
// Functions to read from and write to the configuration file es_settings.cfg.
|
||||||
// are included here. The default values for the application settings are
|
// The default values for the application settings are defined here as well.
|
||||||
// defined here as well.
|
|
||||||
//
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
Loading…
Reference in a new issue