Large code cleanup and code documentation update, mostly related to the scraper. Only cosmetic changes in this commit.

This commit is contained in:
Leon Styhre 2020-05-26 18:34:33 +02:00
parent da946279de
commit fd7da08bf9
30 changed files with 1153 additions and 935 deletions

View file

@ -1,3 +1,9 @@
//
// AsyncReqComponent.cpp
//
// Deprecated, not in use any longer?
//
#include "components/AsyncReqComponent.h"
#include "renderers/Renderer.h"

View file

@ -1,3 +1,9 @@
//
// AsyncReqComponent.h
//
// Deprecated, not in use any longer?
//
#pragma once
#ifndef ES_APP_COMPONENTS_ASYNC_REQ_COMPONENT_H
#define ES_APP_COMPONENTS_ASYNC_REQ_COMPONENT_H

View file

@ -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/ComponentList.h"
@ -14,32 +26,39 @@
#include "Log.h"
#include "Window.h"
ScraperSearchComponent::ScraperSearchComponent(Window* window, SearchType type) : GuiComponent(window),
mGrid(window, Vector2i(4, 3)), mBusyAnim(window),
mSearchType(type)
ScraperSearchComponent::ScraperSearchComponent(
Window* window,
SearchType type)
: GuiComponent(window),
mGrid(window, Vector2i(4, 3)),
mBusyAnim(window),
mSearchType(type)
{
addChild(&mGrid);
mBlockAccept = false;
// 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);
// 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);
// selected result name
mResultName = std::make_shared<TextComponent>(mWindow, "Result name", Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
// Selected result name.
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);
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);
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->setAutoScroll(true);
// metadata
auto font = Font::get(FONT_SIZE_SMALL); // this gets replaced in onSizeChanged() so its just a placeholder
// Metadata.
auto font = Font::get(FONT_SIZE_SMALL); // Placeholder, gets replaced in onSizeChanged().
const unsigned int mdColor = 0x777777FF;
const unsigned int mdLblColor = 0x666666FF;
mMD_Rating = std::make_shared<RatingComponent>(mWindow);
@ -50,17 +69,23 @@ ScraperSearchComponent::ScraperSearchComponent(Window* window, SearchType type)
mMD_Genre = 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>(mWindow, "RELEASED:", font, mdLblColor), mMD_ReleaseDate));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>(mWindow, "DEVELOPER:", font, mdLblColor), mMD_Developer));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>(mWindow, "PUBLISHER:", font, mdLblColor), mMD_Publisher));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>(mWindow, "GENRE:", font, mdLblColor), mMD_Genre));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>(mWindow, "PLAYERS:", font, mdLblColor), mMD_Players));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>
(mWindow, "RATING:", font, mdLblColor), mMD_Rating, false));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>
(mWindow, "RELEASED:", font, mdLblColor), mMD_ReleaseDate));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>
(mWindow, "DEVELOPER:", font, mdLblColor), mMD_Developer));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>
(mWindow, "PUBLISHER:", font, mdLblColor), mMD_Publisher));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>
(mWindow, "GENRE:", font, mdLblColor), mMD_Genre));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>
(mWindow, "PLAYERS:", font, mdLblColor), mMD_Players));
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;
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->second, Vector2i(1, i), false, it->resize);
i += 2;
@ -68,9 +93,10 @@ ScraperSearchComponent::ScraperSearchComponent(Window* window, SearchType type)
mGrid.setEntry(mMD_Grid, Vector2i(2, 1), false, false);
// result list
// Result list.
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();
}
@ -79,46 +105,49 @@ void ScraperSearchComponent::onSizeChanged()
{
mGrid.setSize(mSize);
if(mSize.x() == 0 || mSize.y() == 0)
if (mSize.x() == 0 || mSize.y() == 0)
return;
// column widths
if(mSearchType == ALWAYS_ACCEPT_FIRST_RESULT)
mGrid.setColWidthPerc(0, 0.02f); // looks better when this is higher in auto mode
// Column widths.
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT)
mGrid.setColWidthPerc(0, 0.02f); // Looks better when this is higher in auto mode.
else
mGrid.setColWidthPerc(0, 0.01f);
mGrid.setColWidthPerc(1, 0.25f);
mGrid.setColWidthPerc(2, 0.25f);
// row heights
if(mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) // show name
mGrid.setRowHeightPerc(0, (mResultName->getFont()->getHeight() * 1.6f) / mGrid.getSize().y()); // result name
// Row heights.
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) // Show name.
mGrid.setRowHeightPerc(0, (mResultName->getFont()->getHeight() * 1.6f) /
mGrid.getSize().y()); // Result name.
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);
}else{
else
mGrid.setRowHeightPerc(1, 0.505f);
}
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
// we also pad a little so it doesn't rub up against the metadata labels
// Limit thumbnail size using setMaxHeight - we do this instead of letting mGrid
// call setSize because it maintains the aspect ratio.
// We also pad a little so it doesn't rub up against the metadata labels.
mResultThumbnail->setMaxSize(mGrid.getColWidth(1) * boxartCellScale, mGrid.getRowHeight(1));
// metadata
// Metadata.
resizeMetadata();
if(mSearchType != ALWAYS_ACCEPT_FIRST_RESULT)
mDescContainer->setSize(mGrid.getColWidth(1)*boxartCellScale + mGrid.getColWidth(2), mResultDesc->getFont()->getHeight() * 3);
if (mSearchType != ALWAYS_ACCEPT_FIRST_RESULT)
mDescContainer->setSize(mGrid.getColWidth(1)*boxartCellScale +
mGrid.getColWidth(2), mResultDesc->getFont()->getHeight() * 3);
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();
@ -128,28 +157,25 @@ void ScraperSearchComponent::onSizeChanged()
void ScraperSearchComponent::resizeMetadata()
{
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);
auto fontLbl = Font::get(fontHeight, FONT_PATH_REGULAR);
auto fontComp = Font::get(fontHeight, FONT_PATH_LIGHT);
// update label fonts
// Update label fonts.
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->setSize(0, 0);
if(it->first->getSize().x() > maxLblWidth)
if (it->first->getSize().x() > maxLblWidth)
maxLblWidth = it->first->getSize().x() + 6;
}
for(unsigned int i = 0; i < mMD_Pairs.size(); i++)
{
mMD_Grid->setRowHeightPerc(i*2, (fontLbl->getLetterHeight() + 2) / mMD_Grid->getSize().y());
}
for (unsigned int i = 0; i < mMD_Pairs.size(); i++)
mMD_Grid->setRowHeightPerc(i*2, (fontLbl->getLetterHeight() + 2) /
mMD_Grid->getSize().y());
// update component fonts
// Update component fonts.
mMD_ReleaseDate->setFont(fontComp);
mMD_Developer->setFont(fontComp);
mMD_Publisher->setFont(fontComp);
@ -158,44 +184,52 @@ void ScraperSearchComponent::resizeMetadata()
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_Grid->onSizeChanged();
// make result font follow label font
// Make result font follow label font.
mResultDesc->setFont(Font::get(fontHeight, FONT_PATH_REGULAR));
}
}
void ScraperSearchComponent::updateViewStyle()
{
// unlink description and result list and result name
// Unlink description, result list and result name.
mGrid.removeEntry(mResultName);
mGrid.removeEntry(mResultDesc);
mGrid.removeEntry(mResultList);
// add them back depending on search type
if(mSearchType == ALWAYS_ACCEPT_FIRST_RESULT)
{
// show name
mGrid.setEntry(mResultName, Vector2i(1, 0), false, true, Vector2i(2, 1), GridFlags::BORDER_TOP);
// Add them back depending on search type.
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) {
// Show name.
mGrid.setEntry(mResultName, Vector2i(1, 0), false, true, Vector2i(2, 1),
GridFlags::BORDER_TOP);
// 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);
// 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);
// show description on the right
mGrid.setEntry(mDescContainer, Vector2i(3, 0), false, false, Vector2i(1, 3), GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
mResultDesc->setSize(mDescContainer->getSize().x(), 0); // make desc text wrap at edge of container
}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 description on the right.
mGrid.setEntry(mDescContainer, Vector2i(3, 0), false, false, Vector2i(1, 3),
GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
// Make description text wrap at edge of container.
mResultDesc->setSize(mDescContainer->getSize().x(), 0);
}
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
mGrid.setEntry(mResultList, Vector2i(3, 0), true, true, Vector2i(1, 3), GridFlags::BORDER_LEFT | GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
// 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);
// show description under image/info
mGrid.setEntry(mDescContainer, Vector2i(1, 2), false, false, Vector2i(2, 1), GridFlags::BORDER_BOTTOM);
mResultDesc->setSize(mDescContainer->getSize().x(), 0); // make desc text wrap at edge of container
// Show description under image/info.
mGrid.setEntry(mDescContainer, Vector2i(1, 2), false, false, Vector2i(2, 1),
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);
unsigned int color = 0x777777FF;
if(results.empty())
{
// Check if the scraper used is still valid
if (!isValidConfiguredScraper())
{
mWindow->pushGui(new GuiMsgBox(mWindow, Utils::String::toUpper("Configured scraper is no longer available.\nPlease change the scraping source in the settings."),
if (results.empty()) {
// Check if the scraper used is still valid.
if (!isValidConfiguredScraper()) {
mWindow->pushGui(new GuiMsgBox(mWindow, Utils::String::toUpper("Configured scraper "
"is no longer available.\nPlease change the scraping source in the settings."),
"FINISH", mSkipCallback));
}
else
{
else {
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);
mResultList->addRow(row);
mGrid.resetCursor();
}
}else{
}
else {
ComponentListRow row;
for(size_t i = 0; i < results.size(); i++)
{
for (size_t i = 0; i < results.size(); i++) {
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)); });
mResultList->addRow(row);
}
@ -263,14 +297,13 @@ void ScraperSearchComponent::onSearchDone(const std::vector<ScraperSearchResult>
mBlockAccept = false;
updateInfoPane();
if(mSearchType == ALWAYS_ACCEPT_FIRST_RESULT)
{
if(mScraperResults.size() == 0)
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) {
if (mScraperResults.size() == 0)
mSkipCallback();
else
returnResult(mScraperResults.front());
}else if(mSearchType == ALWAYS_ACCEPT_MATCHING_CRC)
{
}
else if (mSearchType == ALWAYS_ACCEPT_MATCHING_CRC) {
// TODO
}
}
@ -286,7 +319,7 @@ void ScraperSearchComponent::onSearchError(const std::string& error)
int ScraperSearchComponent::getSelectedIndex()
{
if(!mScraperResults.size() || mGrid.getSelectedComponent() != mResultList)
if (!mScraperResults.size() || mGrid.getSelectedComponent() != mResultList)
return -1;
return mResultList->getCursorId();
@ -295,13 +328,10 @@ int ScraperSearchComponent::getSelectedIndex()
void ScraperSearchComponent::updateInfoPane()
{
int i = getSelectedIndex();
if(mSearchType == ALWAYS_ACCEPT_FIRST_RESULT && mScraperResults.size())
{
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT && mScraperResults.size())
i = 0;
}
if(i != -1 && (int)mScraperResults.size() > i)
{
if (i != -1 && (int)mScraperResults.size() > i) {
ScraperSearchResult& res = mScraperResults.at(i);
mResultName->setText(Utils::String::toUpper(res.mdl.get("name")));
mResultDesc->setText(Utils::String::toUpper(res.mdl.get("desc")));
@ -309,14 +339,12 @@ void ScraperSearchComponent::updateInfoPane()
mResultThumbnail->setImage("");
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));
}else{
else
mThumbnailReq.reset();
}
// metadata
// Metadata.
mMD_Rating->setValue(Utils::String::toUpper(res.mdl.get("rating")));
mMD_ReleaseDate->setValue(Utils::String::toUpper(res.mdl.get("releasedate")));
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_Players->setText(Utils::String::toUpper(res.mdl.get("players")));
mGrid.onSizeChanged();
}else{
}
else {
mResultName->setText("");
mResultDesc->setText("");
mResultThumbnail->setImage("");
// metadata
// Metadata.
mMD_Rating->setValue("");
mMD_ReleaseDate->setValue("");
mMD_Developer->setText("");
@ -341,9 +370,8 @@ void ScraperSearchComponent::updateInfoPane()
bool ScraperSearchComponent::input(InputConfig* config, Input input)
{
if(config->isMappedTo("a", input) && input.value != 0)
{
if(mBlockAccept)
if (config->isMappedTo("a", input) && input.value != 0) {
if (mBlockAccept)
return true;
}
@ -356,8 +384,7 @@ void ScraperSearchComponent::render(const Transform4x4f& parentTrans)
renderChildren(trans);
if(mBlockAccept)
{
if (mBlockAccept) {
Renderer::setMatrix(trans);
Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), 0x00000011, 0x00000011);
@ -369,9 +396,8 @@ void ScraperSearchComponent::returnResult(ScraperSearchResult result)
{
mBlockAccept = true;
// resolve metadata image before returning
if(!result.imageUrl.empty())
{
// Resolve metadata image before returning.
if (!result.imageUrl.empty()) {
mMDResolveHandle = resolveMetaDataAssets(result, mLastSearch);
return;
}
@ -383,46 +409,36 @@ void ScraperSearchComponent::update(int deltaTime)
{
GuiComponent::update(deltaTime);
if(mBlockAccept)
{
if (mBlockAccept)
mBusyAnim.update(deltaTime);
}
if(mThumbnailReq && mThumbnailReq->status() != HttpReq::REQ_IN_PROGRESS)
{
if (mThumbnailReq && mThumbnailReq->status() != HttpReq::REQ_IN_PROGRESS)
updateThumbnail();
}
if(mSearchHandle && mSearchHandle->status() != ASYNC_IN_PROGRESS)
{
if (mSearchHandle && mSearchHandle->status() != ASYNC_IN_PROGRESS) {
auto status = mSearchHandle->status();
auto results = mSearchHandle->getResults();
auto statusString = mSearchHandle->getStatusString();
// we reset here because onSearchDone in auto mode can call mSkipCallback() which can call
// another search() which will set our mSearchHandle to something important
// We reset here because onSearchDone in auto mode can call mSkipCallback() which
// can call another search() which will set our mSearchHandle to something important.
mSearchHandle.reset();
if(status == ASYNC_DONE)
{
if (status == ASYNC_DONE)
onSearchDone(results);
}else if(status == ASYNC_ERROR)
{
else if (status == ASYNC_ERROR)
onSearchError(statusString);
}
}
if(mMDResolveHandle && mMDResolveHandle->status() != ASYNC_IN_PROGRESS)
{
if(mMDResolveHandle->status() == ASYNC_DONE)
{
if (mMDResolveHandle && mMDResolveHandle->status() != ASYNC_IN_PROGRESS) {
if (mMDResolveHandle->status() == ASYNC_DONE) {
ScraperSearchResult result = mMDResolveHandle->getResult();
mMDResolveHandle.reset();
// this might end in us being deleted, depending on mAcceptCallback - so make sure this is the last thing we do in update()
// This might end in us being deleted, depending on mAcceptCallback -
// so make sure this is the last thing we do in update().
returnResult(result);
}else if(mMDResolveHandle->status() == ASYNC_ERROR)
{
}
else if (mMDResolveHandle->status() == ASYNC_ERROR) {
onSearchError(mMDResolveHandle->getStatusString());
mMDResolveHandle.reset();
}
@ -431,12 +447,12 @@ void ScraperSearchComponent::update(int deltaTime)
void ScraperSearchComponent::updateThumbnail()
{
if(mThumbnailReq && mThumbnailReq->status() == HttpReq::REQ_SUCCESS)
{
if (mThumbnailReq && mThumbnailReq->status() == HttpReq::REQ_SUCCESS) {
std::string content = mThumbnailReq->getContent();
mResultThumbnail->setImage(content.data(), content.length());
mGrid.onSizeChanged(); // a hack to fix the thumbnail position since its size changed
}else{
mGrid.onSizeChanged(); // A hack to fix the thumbnail position since its size changed.
}
else {
LOG(LogWarning) << "thumbnail req failed: " << mThumbnailReq->getErrorMsg();
mResultThumbnail->setImage("");
}
@ -446,15 +462,14 @@ void ScraperSearchComponent::updateThumbnail()
void ScraperSearchComponent::openInputScreen(ScraperSearchParams& params)
{
auto searchForFunc = [&](const std::string& name)
{
auto searchForFunc = [&](const std::string& name) {
params.nameOverride = name;
search(params);
};
stop();
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,
searchForFunc, false, "SEARCH"));
}
@ -462,7 +477,7 @@ void ScraperSearchComponent::openInputScreen(ScraperSearchParams& params)
std::vector<HelpPrompt> ScraperSearchComponent::getHelpPrompts()
{
std::vector<HelpPrompt> prompts = mGrid.getHelpPrompts();
if(getSelectedIndex() != -1)
if (getSelectedIndex() != -1)
prompts.push_back(HelpPrompt("a", "accept result"));
return prompts;

View file

@ -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
#ifndef 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
{
public:
enum SearchType
{
enum SearchType {
ALWAYS_ACCEPT_FIRST_RESULT,
ALWAYS_ACCEPT_MATCHING_CRC,
NEVER_AUTO_ACCEPT
@ -31,10 +42,14 @@ public:
void stop();
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).
inline void setAcceptCallback(const std::function<void(const ScraperSearchResult&)>& acceptCallback) { mAcceptCallback = acceptCallback; }
inline void setSkipCallback(const std::function<void()>& skipCallback) { mSkipCallback = skipCallback; };
inline void setCancelCallback(const std::function<void()>& cancelCallback) { mCancelCallback = cancelCallback; }
// Metadata assets will be resolved before calling the accept callback
// (e.g. result.mdl's "image" is automatically downloaded and properly set).
inline void setAcceptCallback(const std::function<void(const ScraperSearchResult&)>&
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;
void update(int deltaTime) override;
@ -56,7 +71,7 @@ private:
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);
ComponentGrid mGrid;
@ -75,14 +90,15 @@ private:
std::shared_ptr<TextComponent> mMD_Genre;
std::shared_ptr<TextComponent> mMD_Players;
// label-component pair
struct MetaDataPair
{
// Label-component pair.
struct MetaDataPair {
std::shared_ptr<TextComponent> first;
std::shared_ptr<GuiComponent> second;
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;

View file

@ -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 "components/ButtonComponent.h"
@ -7,48 +15,57 @@
#include "PowerSaver.h"
#include "SystemData.h"
GuiGameScraper::GuiGameScraper(Window* window, ScraperSearchParams params, std::function<void(const ScraperSearchResult&)> doneFunc) : GuiComponent(window),
mGrid(window, Vector2i(1, 7)),
mBox(window, ":/frame.png"),
mSearchParams(params),
mClose(false)
GuiGameScraper::GuiGameScraper(
Window* window,
ScraperSearchParams params,
std::function<void(const ScraperSearchResult&)> doneFunc)
: GuiComponent(window),
mGrid(window, Vector2i(1, 7)),
mBox(window, ":/frame.png"),
mSearchParams(params),
mClose(false)
{
PowerSaver::pause();
addChild(&mBox);
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())),
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER);
mGameName = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(
Utils::FileSystem::getFileName(mSearchParams.game->getPath())),
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER);
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),
0x888888FF, ALIGN_CENTER);
mSystemName = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(
mSearchParams.system->getFullName()), Font::get(FONT_SIZE_SMALL),
0x888888FF, ALIGN_CENTER);
mGrid.setEntry(mSystemName, Vector2i(0, 3), false, true);
// row 4 is a spacer
// Row 4 is a spacer.
// ScraperSearchComponent
mSearch = std::make_shared<ScraperSearchComponent>(window, ScraperSearchComponent::NEVER_AUTO_ACCEPT);
// ScraperSearchComponent.
mSearch = std::make_shared<ScraperSearchComponent>(window,
ScraperSearchComponent::NEVER_AUTO_ACCEPT);
mGrid.setEntry(mSearch, Vector2i(0, 5), true);
// buttons
// Buttons
std::vector< std::shared_ptr<ButtonComponent> > buttons;
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "INPUT", "search", [&] {
mSearch->openInputScreen(mSearchParams);
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);
mGrid.setEntry(mButtonGrid, Vector2i(0, 6), true, false);
// 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:
// 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',
// the following would happen when the metadata resolver is done:
// GuiGameScraper::update()
// GuiComponent::update()
// it = mChildren.cbegin();
@ -56,9 +73,9 @@ GuiGameScraper::GuiGameScraper(Window* window, ScraperSearchParams params, std::
// it++;
// mSearchComponent::update()
// 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()
// GuiComponent::update()
// it = mChildren.cbegin();
@ -66,17 +83,19 @@ GuiGameScraper::GuiGameScraper(Window* window, ScraperSearchParams params, std::
// it++;
// mSearchComponent::update()
// acceptCallback -> close() -> mClose = true
// it++; // ok
// it++; // OK.
// if(mClose)
// 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; });
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();
mSearch->search(params); // start the search
mSearch->search(params); // Start the search.
}
void GuiGameScraper::onSizeChanged()
@ -84,18 +103,19 @@ void GuiGameScraper::onSizeChanged()
mBox.fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32));
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(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(6, mButtonGrid->getSize().y() / mSize.y(), false); // buttons
mGrid.setRowHeightPerc(6, mButtonGrid->getSize().y() / mSize.y(), false); // Buttons.
mGrid.setSize(mSize);
}
bool GuiGameScraper::input(InputConfig* config, Input input)
{
if(config->isMappedTo("b", input) && input.value)
{
if(config->isMappedTo("b", input) && input.value) {
PowerSaver::resume();
delete this;
return true;

View file

@ -1,3 +1,11 @@
//
// GuiGameScraper.h
//
// Single game scraping user interface.
// This interface is triggered from GuiMetaDataEd.
// ScraperSearchComponent is called from here.
//
#pragma once
#ifndef ES_APP_GUIS_GUI_GAME_SCRAPER_H
#define ES_APP_GUIS_GUI_GAME_SCRAPER_H
@ -9,7 +17,10 @@
class GuiGameScraper : public GuiComponent
{
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;

View file

@ -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 "components/OptionListComponent.h"
#include "views/UIModeController.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();
}
@ -13,15 +26,15 @@ void GuiGamelistFilter::initializeMenu()
{
addChild(&mMenu);
// get filters from system
// Get filters from system.
mFilterIndex = mSystem->getIndex();
ComponentListRow row;
// show filtered menu
// Show filtered menu.
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));
mMenu.addRow(row);
row.elements.clear();
@ -30,13 +43,15 @@ void GuiGamelistFilter::initializeMenu()
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()
{
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;
optionList->selectNone();
}
@ -57,23 +72,22 @@ void GuiGamelistFilter::addFiltersToMenu()
if (UIModeController::getInstance()->isUIModeKid())
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
std::map<std::string, int>* allKeys = (*it).allIndexKeys; // all possible filters for this type
std::string menuLabel = (*it).menuLabel; // text to show in menu
FilterIndexType type = (*it).type; // Type of filter.
// All possible filters for this type.
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;
// add filters (with first one selected)
// Add filters (with first one selected).
ComponentListRow row;
// add genres
// Add genres.
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));
}
if (allKeys->size() > 0)
mMenu.addWithLabel(menuLabel, optionList);
@ -84,27 +98,24 @@ void GuiGamelistFilter::addFiltersToMenu()
void GuiGamelistFilter::applyFilters()
{
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::vector<std::string> filters = optionList->getSelectedObjects();
mFilterIndex->setFilter(it->first, &filters);
}
delete this;
}
bool GuiGamelistFilter::input(InputConfig* config, Input input)
{
bool consumed = GuiComponent::input(config, input);
if(consumed)
if (consumed)
return true;
if(config->isMappedTo("b", input) && input.value != 0)
{
if (config->isMappedTo("b", input) && input.value != 0)
applyFilters();
}
return false;
}

View file

@ -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
#ifndef 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:
GuiGamelistFilter(Window* window, SystemData* system);
~GuiGamelistFilter();
bool input(InputConfig* config, Input input) override;

View file

@ -4,6 +4,9 @@
// Gamelist options menu for the 'Jump to...' quick selector,
// 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"

View file

@ -4,6 +4,9 @@
// Gamelist options menu for the 'Jump to...' quick selector,
// 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
#ifndef ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H

View file

@ -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 "components/ButtonComponent.h"
@ -20,25 +29,35 @@
#include "SystemData.h"
#include "Window.h"
GuiMetaDataEd::GuiMetaDataEd(Window* window, 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),
GuiMetaDataEd::GuiMetaDataEd(
Window* window,
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"),
mGrid(window, Vector2i(1, 3)),
mBackground(window, ":/frame.png"),
mGrid(window, Vector2i(1, 3)),
mMetaDataDecl(mdd),
mMetaData(md),
mSavedCallback(saveCallback), mDeleteFunc(deleteFunc)
mMetaDataDecl(mdd),
mMetaData(md),
mSavedCallback(saveCallback),
mDeleteFunc(deleteFunc)
{
addChild(&mBackground);
addChild(&mGrid);
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);
mSubtitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(Utils::FileSystem::getFileName(scraperParams.game->getPath())),
Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER);
mTitle = std::make_shared<TextComponent>(mWindow, "EDIT METADATA",
Font::get(FONT_SIZE_LARGE), 0x555555FF, 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(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);
mGrid.setEntry(mList, Vector2i(0, 1), true, true);
// populate list
for(auto iter = mdd.cbegin(); iter != mdd.cend(); iter++)
{
// Populate list.
for (auto iter = mdd.cbegin(); iter != mdd.cend(); iter++) {
std::shared_ptr<GuiComponent> ed;
// don't add statistics
if(iter->isStatistic)
// Don't add statistics.
if (iter->isStatistic)
continue;
// Don't show the launch string override entry if this option has been disabled in the settings
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);
// Don't show the launch string override entry if this option has been disabled.
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);
assert(ed);
ed->setValue(mMetaData->get(iter->key));
mEditors.push_back(ed);
@ -67,22 +86,20 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
continue;
}
// create ed and add it (and any related components) to mMenu
// ed's value will be set below
// Create ed and add it (and any related components) to mMenu.
// ed's value will be set below.
ComponentListRow row;
auto lbl = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(iter->displayName), Font::get(FONT_SIZE_SMALL), 0x777777FF);
row.addElement(lbl, true); // label
auto lbl = std::make_shared<TextComponent>(mWindow,
Utils::String::toUpper(iter->displayName), Font::get(FONT_SIZE_SMALL), 0x777777FF);
row.addElement(lbl, true); // Label.
switch(iter->type)
{
case MD_BOOL:
{
switch (iter->type) {
case MD_BOOL: {
ed = std::make_shared<SwitchComponent>(window);
row.addElement(ed, false, true);
break;
}
case MD_RATING:
{
case MD_RATING: {
ed = std::make_shared<RatingComponent>(window);
const float height = lbl->getSize().y() * 0.71f;
ed->setSize(0, height);
@ -92,13 +109,12 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0);
row.addElement(spacer, false);
// 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);
// 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);
break;
}
case MD_DATE:
{
case MD_DATE: {
ed = std::make_shared<DateTimeEditComponent>(window);
row.addElement(ed, false);
@ -106,20 +122,20 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0);
row.addElement(spacer, false);
// 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);
// 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);
break;
}
case MD_TIME:
{
ed = std::make_shared<DateTimeEditComponent>(window, DateTimeEditComponent::DISP_RELATIVE_TO_NOW);
case MD_TIME: {
ed = std::make_shared<DateTimeEditComponent>(window,
DateTimeEditComponent::DISP_RELATIVE_TO_NOW);
row.addElement(ed, false);
break;
}
case MD_LAUNCHSTRING:
{
ed = std::make_shared<TextComponent>(window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
case MD_LAUNCHSTRING: {
ed = std::make_shared<TextComponent>(window, "",
Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
row.addElement(ed, true);
auto spacer = std::make_shared<GuiComponent>(mWindow);
@ -133,21 +149,26 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
bool multiLine = false;
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 defaultLaunchString = scraperParams.system->getSystemEnvData()->mLaunchCommand;
std::string defaultLaunchString = scraperParams.system->
getSystemEnvData()->mLaunchCommand;
row.makeAcceptInputHandler([this, title, staticTextString, defaultLaunchString, ed, updateVal, multiLine] {
mWindow->pushGui(new GuiComplexTextEditPopup(mWindow, title, staticTextString, defaultLaunchString, ed->getValue(), updateVal, multiLine));
row.makeAcceptInputHandler([this, title, staticTextString,
defaultLaunchString, ed, updateVal, multiLine] {
mWindow->pushGui(new GuiComplexTextEditPopup(mWindow, title,
staticTextString, defaultLaunchString, ed->getValue(),
updateVal, multiLine));
});
break;
}
case MD_MULTILINE_STRING:
default:
{
// MD_STRING
ed = std::make_shared<TextComponent>(window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
default: {
// MD_STRING.
ed = std::make_shared<TextComponent>(window, "", Font::get(FONT_SIZE_SMALL,
FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
row.addElement(ed, true);
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;
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] {
mWindow->pushGui(new GuiTextEditPopup(mWindow, title, ed->getValue(), updateVal, multiLine));
mWindow->pushGui(new GuiTextEditPopup(mWindow, title, ed->getValue(),
updateVal, multiLine));
});
break;
}
@ -177,26 +201,33 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
std::vector< std::shared_ptr<ButtonComponent> > buttons;
if(!scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SCRAPE", "scrape", std::bind(&GuiMetaDataEd::fetch, this)));
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, "SAVE", "save", [&] { save(); delete this; }));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "cancel", [&] { delete this; }));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SAVE", "save",
[&] { 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 deleteBtnFunc = [this, deleteFileAndSelf] { mWindow->pushGui(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));
auto deleteBtnFunc = [this, deleteFileAndSelf] { mWindow->pushGui(
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);
mGrid.setEntry(mButtons, Vector2i(0, 2), true, false);
// resize + center
float width = (float)Math::min(Renderer::getScreenHeight(), (int)(Renderer::getScreenWidth() * 0.90f));
// Resize + center.
float width = (float)Math::min(Renderer::getScreenHeight(),
(int)(Renderer::getScreenWidth() * 0.90f));
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()
@ -209,7 +240,8 @@ void GuiMetaDataEd::onSizeChanged()
const float subtitleHeight = mSubtitle->getFont()->getLetterHeight();
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());
mHeaderGrid->setRowHeightPerc(1, titleHeight / mHeaderGrid->getSize().y());
@ -219,24 +251,23 @@ void GuiMetaDataEd::onSizeChanged()
void GuiMetaDataEd::save()
{
// remove game from index
// Remove game from index.
mScraperParams.system->getIndex()->removeFromIndex(mScraperParams.game);
for(unsigned int i = 0; i < mEditors.size(); i++)
{
if(mMetaDataDecl.at(i).isStatistic)
for (unsigned int i = 0; i < mEditors.size(); i++) {
if (mMetaDataDecl.at(i).isStatistic)
continue;
mMetaData->set(mMetaDataDecl.at(i).key, mEditors.at(i)->getValue());
}
// enter game in index
// Enter game in index.
mScraperParams.system->getIndex()->addToIndex(mScraperParams.game);
if(mSavedCallback)
if (mSavedCallback)
mSavedCallback();
// update respective Collection Entries
// Update respective Collection Entries.
CollectionSystemManager::get()->refreshCollectionSystems(mScraperParams.game);
mScraperParams.system->onMetaDataSavePoint();
@ -244,15 +275,15 @@ void GuiMetaDataEd::save()
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);
}
void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
{
for(unsigned int i = 0; i < mEditors.size(); i++)
{
if(mMetaDataDecl.at(i).isStatistic)
for (unsigned int i = 0; i < mEditors.size(); i++) {
if (mMetaDataDecl.at(i).isStatistic)
continue;
const std::string& key = mMetaDataDecl.at(i).key;
@ -262,52 +293,50 @@ void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
void GuiMetaDataEd::close(bool closeAllWindows)
{
// find out if the user made any changes
// Find out if the user made any changes.
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;
if(mMetaData->get(key) != mEditors.at(i)->getValue())
{
if (mMetaData->get(key) != mEditors.at(i)->getValue()) {
dirty = true;
break;
}
}
std::function<void()> closeFunc;
if(!closeAllWindows)
{
if (!closeAllWindows) {
closeFunc = [this] { delete this; };
}else{
}
else {
Window* window = mWindow;
closeFunc = [window, this] {
while(window->peekGui() != ViewController::get())
while (window->peekGui() != ViewController::get())
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,
"SAVE CHANGES?",
"YES", [this, closeFunc] { save(); closeFunc(); },
"NO", closeFunc
));
}else{
}
else {
closeFunc();
}
}
bool GuiMetaDataEd::input(InputConfig* config, Input input)
{
if(GuiComponent::input(config, input))
if (GuiComponent::input(config, input))
return true;
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);
return true;
}

View file

@ -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
#ifndef 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
{
public:
GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector<MetaDataDecl>& mdd, ScraperSearchParams params,
const std::string& header, std::function<void()> savedCallback, std::function<void()> deleteFunc);
GuiMetaDataEd(
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;
void onSizeChanged() override;

View file

@ -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 "components/ButtonComponent.h"
@ -11,9 +20,14 @@
#include "SystemData.h"
#include "Window.h"
GuiScraperMulti::GuiScraperMulti(Window* window, const std::queue<ScraperSearchParams>& searches, bool approveResults) :
GuiComponent(window), mBackground(window, ":/frame.png"), mGrid(window, Vector2i(1, 5)),
mSearchQueue(searches)
GuiScraperMulti::GuiScraperMulti(
Window* window,
const std::queue<ScraperSearchParams>& searches,
bool approveResults)
: GuiComponent(window),
mBackground(window, ":/frame.png"),
mGrid(window, Vector2i(1, 5)),
mSearchQueue(searches)
{
assert(mSearchQueue.size());
@ -28,27 +42,32 @@ GuiScraperMulti::GuiScraperMulti(Window* window, const std::queue<ScraperSearchP
mTotalSuccessful = 0;
mTotalSkipped = 0;
// set up grid
mTitle = std::make_shared<TextComponent>(mWindow, "SCRAPING IN PROGRESS", Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER);
// Set up grid.
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);
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);
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);
mSearchComp = std::make_shared<ScraperSearchComponent>(mWindow,
approveResults ? ScraperSearchComponent::ALWAYS_ACCEPT_MATCHING_CRC : ScraperSearchComponent::ALWAYS_ACCEPT_FIRST_RESULT);
mSearchComp->setAcceptCallback(std::bind(&GuiScraperMulti::acceptResult, this, std::placeholders::_1));
approveResults ? ScraperSearchComponent::ALWAYS_ACCEPT_MATCHING_CRC
: ScraperSearchComponent::ALWAYS_ACCEPT_FIRST_RESULT);
mSearchComp->setAcceptCallback(std::bind(&GuiScraperMulti::acceptResult,
this, std::placeholders::_1));
mSearchComp->setSkipCallback(std::bind(&GuiScraperMulti::skip, 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;
if(approveResults)
{
if (approveResults) {
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "INPUT", "search", [&] {
mSearchComp->openInputScreen(mSearchQueue.front());
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);
mGrid.setEntry(mButtonGrid, Vector2i(0, 4), true, false);
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();
}
GuiScraperMulti::~GuiScraperMulti()
{
// view type probably changed (basic -> detailed)
for(auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); it++)
// View type probably changed (basic -> detailed).
for (auto it = SystemData::sSystemVector.cbegin();
it !=SystemData::sSystemVector.cend(); it++)
ViewController::get()->reloadGameListView(*it, false);
}
@ -91,19 +113,20 @@ void GuiScraperMulti::onSizeChanged()
void GuiScraperMulti::doNextSearch()
{
if(mSearchQueue.empty())
{
if (mSearchQueue.empty()) {
finish();
return;
}
// update title
// Update title.
std::stringstream ss;
mSystem->setText(Utils::String::toUpper(mSearchQueue.front().system->getFullName()));
// update subtitle
ss.str(""); // clear
ss << "GAME " << (mCurrentGame + 1) << " OF " << mTotalGames << " - " << Utils::String::toUpper(Utils::FileSystem::getFileName(mSearchQueue.front().game->getPath()));
// Update subtitle.
ss.str(""); // Clear.
ss << "GAME " << (mCurrentGame + 1) << " OF " << mTotalGames << " - " <<
Utils::String::toUpper(Utils::FileSystem::getFileName(
mSearchQueue.front().game->getPath()));
mSubtitle->setText(ss.str());
mSearchComp->search(mSearchQueue.front());
@ -133,18 +156,20 @@ void GuiScraperMulti::skip()
void GuiScraperMulti::finish()
{
std::stringstream ss;
if(mTotalSuccessful == 0)
{
if (mTotalSuccessful == 0) {
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)
ss << "\n" << mTotalSkipped << " GAME" << ((mTotalSkipped > 1) ? "S" : "") << " SKIPPED.";
if (mTotalSkipped > 0)
ss << "\n" << mTotalSkipped << " GAME" << ((mTotalSkipped > 1) ? "S" : "") <<
" SKIPPED.";
}
mWindow->pushGui(new GuiMsgBox(mWindow, ss.str(),
"OK", [&] { delete this; }));
mWindow->pushGui(new GuiMsgBox(mWindow, ss.str(), "OK", [&] {
delete this; }));
mIsProcessing = false;
PowerSaver::resume();

View file

@ -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
#ifndef 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
{
public:
GuiScraperMulti(Window* window, const std::queue<ScraperSearchParams>& searches, bool approveResults);
GuiScraperMulti(
Window* window,
const std::queue<ScraperSearchParams>& searches,
bool approveResults);
virtual ~GuiScraperMulti();
void onSizeChanged() override;

View file

@ -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 "components/OptionListComponent.h"
@ -13,19 +20,22 @@ GuiScraperStart::GuiScraperStart(Window* window) : GuiComponent(window),
{
addChild(&mMenu);
// add filters (with first one selected)
mFilters = std::make_shared< OptionListComponent<GameFilterFunc> >(mWindow, "SCRAPE THESE GAMES", false);
// Add filters (with first one selected).
mFilters = std::make_shared< OptionListComponent<GameFilterFunc>
>(mWindow, "SCRAPE THESE GAMES", false);
mFilters->add("All Games",
[](SystemData*, FileData*) -> bool { return true; }, false);
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);
//add systems (all with a platformid specified selected)
mSystems = std::make_shared< OptionListComponent<SystemData*> >(mWindow, "SCRAPE THESE SYSTEMS", true);
for(auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); it++)
{
if(!(*it)->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
// Add systems (all systems with an existing platform ID are listed).
mSystems = std::make_shared< OptionListComponent<SystemData*>
>(mWindow, "SCRAPE THESE SYSTEMS", true);
for (auto it = SystemData::sSystemVector.cbegin();
it != SystemData::sSystemVector.cend(); it++) {
if (!(*it)->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
mSystems->add((*it)->getFullName(), *it, !(*it)->getPlatformIds().empty());
}
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("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()
{
std::vector<SystemData*> sys = mSystems->getSelectedObjects();
for(auto it = sys.cbegin(); it != sys.cend(); it++)
{
if((*it)->getPlatformIds().empty())
{
for (auto it = sys.cbegin(); it != sys.cend(); it++) {
if ((*it)->getPlatformIds().empty()) {
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),
"NO", nullptr));
return;
@ -60,29 +71,28 @@ void GuiScraperStart::pressedStart()
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,
"NO GAMES FIT THAT CRITERIA."));
}else{
}
else {
GuiScraperMulti* gsm = new GuiScraperMulti(mWindow, searches, mApproveResults->getState());
mWindow->pushGui(gsm);
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;
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);
for(auto game = games.cbegin(); game != games.cend(); game++)
{
if(selector((*sys), (*game)))
{
for (auto game = games.cbegin(); game != games.cend(); game++) {
if (selector((*sys), (*game))) {
ScraperSearchParams search;
search.game = *game;
search.system = *sys;
@ -91,31 +101,27 @@ std::queue<ScraperSearchParams> GuiScraperStart::getSearches(std::vector<SystemD
}
}
}
return queue;
}
bool GuiScraperStart::input(InputConfig* config, Input input)
{
bool consumed = GuiComponent::input(config, input);
if(consumed)
if (consumed)
return true;
if(input.value != 0 && config->isMappedTo("b", input))
{
if (input.value != 0 && config->isMappedTo("b", input)) {
delete this;
return true;
}
if(config->isMappedTo("start", input) && input.value != 0)
{
// close everything
if (config->isMappedTo("start", input) && input.value != 0) {
// Close everything.
Window* window = mWindow;
while(window->peekGui() && window->peekGui() != ViewController::get())
while (window->peekGui() && window->peekGui() != ViewController::get())
delete window->peekGui();
}
return false;
}

View file

@ -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
#ifndef 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;
//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).
//Generates a list of "searches" that will be carried out by GuiScraperLog.
// The starting point for a multi-game scrape.
// Allows the user to set various parameters (filters, which systems to scrape and
// whether to use manual mode). Generates a list of "searches" that will be carried
// out by GuiScraperLog.
class GuiScraperStart : public GuiComponent
{
public:
@ -28,7 +36,8 @@ public:
private:
void pressedStart();
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<SystemData*> > mSystems;

View file

@ -7,6 +7,8 @@
// Improved and extended by the RetroPie community.
// Desktop Edition fork by Leon Styhre.
//
// The line length limit is 100 characters.
//
// main.cpp
//
// Main program loop. Interprets command-line arguments, checks for the

View file

@ -1,3 +1,10 @@
//
// GamesDBJSONScraper.cpp
//
// Functions specifically for scraping from thegamesdb.net
// Called from Scraper.
//
#include <exception>
#include <map>
@ -12,15 +19,14 @@
#include "utils/TimeUtil.h"
#include <pugixml/src/pugixml.hpp>
/* 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:
#ifndef RAPIDJSON_ASSERT
#define RAPIDJSON_ASSERT(x) \
if (!(x)) { \
throw std::runtime_error("rapidjson internal assertion failure: " #x); \
}
#endif // RAPIDJSON_ASSERT
*/
// 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:
//ifndef RAPIDJSON_ASSERT
//#define RAPIDJSON_ASSERT(x) \
// if (!(x)) { \
// throw std::runtime_error("rapidjson internal assertion failure: " #x); \
// }
//#endif // RAPIDJSON_ASSERT
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
@ -28,12 +34,11 @@
using namespace PlatformIds;
using namespace rapidjson;
namespace
{
namespace {
TheGamesDBJSONRequestResources resources;
}
const std::map<PlatformId, std::string> gamesdb_new_platformid_map{
const std::map<PlatformId, std::string> gamesdb_new_platformid_map {
{ THREEDO, "25" },
{ AMIGA, "4911" },
{ AMSTRAD_CPC, "4914" },
@ -88,8 +93,8 @@ const std::map<PlatformId, std::string> gamesdb_new_platformid_map{
{ PLAYSTATION_VITA, "39" },
{ PLAYSTATION_PORTABLE, "13" },
{ SUPER_NINTENDO, "6" },
{ TURBOGRAFX_16, "34" }, // HuCards only
{ TURBOGRAFX_CD, "4955" }, // CD-ROMs only
{ TURBOGRAFX_16, "34" }, // HuCards only.
{ TURBOGRAFX_CD, "4955" }, // CD-ROMs only.
{ WONDERSWAN, "4925" },
{ WONDERSWAN_COLOR, "4926" },
{ ZX_SPECTRUM, "4913" },
@ -99,16 +104,17 @@ const std::map<PlatformId, std::string> gamesdb_new_platformid_map{
{ TANDY, "4941" },
};
void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params,
std::queue<std::unique_ptr<ScraperRequest>>& requests, std::vector<ScraperSearchResult>& results)
void thegamesdb_generate_json_scraper_requests(
const ScraperSearchParams& params,
std::queue<std::unique_ptr<ScraperRequest>>& requests,
std::vector<ScraperSearchResult>& results)
{
resources.prepare();
std::string path = "https://api.thegamesdb.net/v1";
bool usingGameID = false;
const std::string apiKey = std::string("apikey=") + resources.getApiKey();
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);
path += "/Games/ByGameID?" + apiKey +
"&fields=players,publishers,genres,overview,last_updated,rating,"
@ -116,42 +122,37 @@ void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params
"include=boxart&id=" +
HttpReq::urlEncode(gameID);
usingGameID = true;
} else
{
}
else {
if (cleanName.empty())
cleanName = params.game->getCleanName();
path += "/Games/ByGameName?" + apiKey +
"&fields=players,publishers,genres,overview,last_updated,rating,"
"platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates&"
"include=boxart&name=" +
HttpReq::urlEncode(cleanName);
path += "/Games/ByGameName?" + apiKey +
"&fields=players,publishers,genres,overview,last_updated,rating,"
"platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates&"
"include=boxart&name=" +
HttpReq::urlEncode(cleanName);
}
if (usingGameID)
{
// if we have the ID already, we don't need the GetGameList request
if (usingGameID) {
// Ff we have the ID already, we don't need the GetGameList request.
requests.push(std::unique_ptr<ScraperRequest>(new TheGamesDBJSONRequest(results, path)));
} else
{
}
else {
std::string platformQueryParam;
auto& platforms = params.system->getPlatformIds();
if (!platforms.empty())
{
if (!platforms.empty()) {
bool first = true;
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);
if (mapIt != gamesdb_new_platformid_map.cend())
{
if (mapIt != gamesdb_new_platformid_map.cend()) {
if (!first)
{
platformQueryParam += ",";
}
platformQueryParam += HttpReq::urlEncode(mapIt->second);
first = false;
} else
{
}
else {
LOG(LogWarning) << "TheGamesDB scraper warning - no support for platform "
<< getPlatformName(*platformIt);
}
@ -159,7 +160,8 @@ void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params
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)
{
if (!v.HasMember(key.c_str()) || !v[key.c_str()].IsString())
{
throw std::runtime_error("rapidjson internal assertion failure: missing or non string key:" + key);
if (!v.HasMember(key.c_str()) || !v[key.c_str()].IsString()) {
throw std::runtime_error(
"rapidjson internal assertion failure: missing or non string key:" + key);
}
return v[key.c_str()].GetString();
}
int getIntOrThrow(const Value& v, const std::string& key)
{
if (!v.HasMember(key.c_str()) || !v[key.c_str()].IsInt())
{
throw std::runtime_error("rapidjson internal assertion failure: missing or non int key:" + key);
if (!v.HasMember(key.c_str()) || !v[key.c_str()].IsInt()) {
throw std::runtime_error(
"rapidjson internal assertion failure: missing or non int key:" + key);
}
return v[key.c_str()].GetInt();
}
int getIntOrThrow(const Value& v)
{
if (!v.IsInt())
{
if (!v.IsInt()) {
throw std::runtime_error("rapidjson internal assertion failure: not an int");
}
return v.GetInt();
@ -196,18 +197,14 @@ int getIntOrThrow(const Value& v)
std::string getBoxartImage(const Value& v)
{
if (!v.IsArray() || v.Size() == 0)
{
return "";
}
for (int i = 0; i < (int)v.Size(); ++i)
{
for (int i = 0; i < (int)v.Size(); ++i) {
auto& im = v[i];
std::string type = getStringOrThrow(im, "type");
std::string side = getStringOrThrow(im, "side");
if (type == "boxart" && side == "front")
{
return getStringOrThrow(im, "filename");
}
}
return getStringOrThrow(v[0], "filename");
}
@ -215,22 +212,19 @@ std::string getBoxartImage(const Value& v)
std::string getDeveloperString(const Value& v)
{
if (!v.IsArray())
{
return "";
}
std::string out = "";
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]));
if (mapIt == resources.gamesdb_new_developers_map.cend())
{
continue;
}
if (!first)
{
out += ", ";
}
out += mapIt->second;
first = false;
}
@ -240,22 +234,19 @@ std::string getDeveloperString(const Value& v)
std::string getPublisherString(const Value& v)
{
if (!v.IsArray())
{
return "";
}
std::string out = "";
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]));
if (mapIt == resources.gamesdb_new_publishers_map.cend())
{
continue;
}
if (!first)
{
out += ", ";
}
out += mapIt->second;
first = false;
}
@ -265,22 +256,18 @@ std::string getPublisherString(const Value& v)
std::string getGenreString(const Value& v)
{
if (!v.IsArray())
{
return "";
}
std::string out = "";
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]));
if (mapIt == resources.gamesdb_new_genres_map.cend())
{
continue;
}
if (!first)
{
out += ", ";
}
out += mapIt->second;
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"));
if (game.HasMember("overview") && game["overview"].IsString())
{
result.mdl.set("desc", game["overview"].GetString());
}
if (game.HasMember("release_date") && game["release_date"].IsString())
{
result.mdl.set(
"releasedate", Utils::Time::DateTime(Utils::Time::stringToTime(game["release_date"].GetString(), "%Y-%m-%d")));
}
result.mdl.set("releasedate", Utils::Time::DateTime(Utils::Time::stringToTime(
game["release_date"].GetString(), "%Y-%m-%d")));
if (game.HasMember("developers") && game["developers"].IsArray())
{
result.mdl.set("developer", getDeveloperString(game["developers"]));
}
if (game.HasMember("publishers") && game["publishers"].IsArray())
{
result.mdl.set("publisher", getPublisherString(game["publishers"]));
}
if (game.HasMember("genres") && game["genres"].IsArray())
{
result.mdl.set("genre", getGenreString(game["genres"]));
}
if (game.HasMember("players") && game["players"].IsInt())
{
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"));
if (boxart["data"].HasMember(id.c_str()))
{
if (boxart["data"].HasMember(id.c_str())) {
std::string image = getBoxartImage(boxart["data"][id.c_str()]);
result.thumbnailUrl = baseImageUrlThumb + "/" + image;
result.imageUrl = baseImageUrlLarge + "/" + image;
@ -338,32 +314,32 @@ void processGame(const Value& game, const Value& boxart, std::vector<ScraperSear
}
} // 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);
Document doc;
doc.Parse(req->getContent().c_str());
if (doc.HasParseError())
{
if (doc.HasParseError()) {
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);
LOG(LogError) << err;
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";
LOG(LogWarning) << warn;
return;
}
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";
LOG(LogWarning) << warn;
return;
@ -371,8 +347,7 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req, std::ve
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";
LOG(LogWarning) << warn;
return;
@ -380,16 +355,12 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req, std::ve
resources.ensureResources();
for (int i = 0; i < (int)games.Size(); ++i)
{
for (int i = 0; i < (int)games.Size(); ++i) {
auto& v = games[i];
try
{
try {
processGame(v, boxart, results);
}
catch (std::runtime_error& e)
{
catch (std::runtime_error& e) {
LOG(LogError) << "Error while processing game: " << e.what();
}
}

View file

@ -1,34 +1,49 @@
//
// GamesDBJSONScraper.h
//
// Functions specifically for scraping from thegamesdb.net
// Called from Scraper.
//
#pragma once
#ifndef ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_H
#define ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_H
#include "scrapers/Scraper.h"
namespace pugi
{
namespace pugi {
class xml_document;
}
void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params,
std::queue<std::unique_ptr<ScraperRequest>>& requests, std::vector<ScraperSearchResult>& results);
void thegamesdb_generate_json_scraper_requests(
const ScraperSearchParams& params,
std::queue<std::unique_ptr<ScraperRequest>>& requests,
std::vector<ScraperSearchResult>& results);
class TheGamesDBJSONRequest : public ScraperHttpRequest
{
public:
// ctor for a GetGameList request
TheGamesDBJSONRequest(std::queue<std::unique_ptr<ScraperRequest>>& requestsWrite,
std::vector<ScraperSearchResult>& resultsWrite, const std::string& url)
: ScraperHttpRequest(resultsWrite, url), mRequestQueue(&requestsWrite)
// ctor for a GetGameList request.
TheGamesDBJSONRequest(
std::queue<std::unique_ptr<ScraperRequest>>& requestsWrite,
std::vector<ScraperSearchResult>& resultsWrite,
const std::string& url)
: ScraperHttpRequest(resultsWrite, url),
mRequestQueue(&requestsWrite)
{
}
// ctor for a GetGame request
TheGamesDBJSONRequest(std::vector<ScraperSearchResult>& resultsWrite, const std::string& url)
: ScraperHttpRequest(resultsWrite, url), mRequestQueue(nullptr)
TheGamesDBJSONRequest(
std::vector<ScraperSearchResult>& resultsWrite,
const std::string& url)
: ScraperHttpRequest(resultsWrite, url),
mRequestQueue(nullptr)
{
}
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; }
std::queue<std::unique_ptr<ScraperRequest>>* mRequestQueue;

View file

@ -1,3 +1,10 @@
//
// GamesDBJSONScraperResources.cpp
//
// Functions specifically for scraping from thegamesdb.net
// Called from GamesDBJSONScraper.
//
#include <chrono>
#include <fstream>
#include <memory>
@ -8,17 +15,14 @@
#include "scrapers/GamesDBJSONScraperResources.h"
#include "utils/FileSystemUtil.h"
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
using namespace rapidjson;
namespace
{
constexpr char GamesDBAPIKey[] = "445fcbc3f32bb2474bc27016b99eb963d318ee3a608212c543b9a79de1041600";
namespace {
constexpr char GamesDBAPIKey[] =
"445fcbc3f32bb2474bc27016b99eb963d318ee3a608212c543b9a79de1041600";
constexpr int MAX_WAIT_MS = 90000;
constexpr int POLL_TIME_MS = 500;
@ -46,73 +50,57 @@ void ensureScrapersResourcesDir()
} // namespace
std::string getScrapersResouceDir()
{
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; }
void TheGamesDBJSONRequestResources::prepare()
{
if (checkLoaded())
{
return;
}
if (loadResource(gamesdb_new_developers_map, "developers", genFilePath(DEVELOPERS_JSON_FILE)) &&
!gamesdb_developers_resource_request)
{
if (loadResource(gamesdb_new_developers_map, "developers",
genFilePath(DEVELOPERS_JSON_FILE)) && !gamesdb_developers_resource_request)
gamesdb_developers_resource_request = fetchResource(DEVELOPERS_ENDPOINT);
}
if (loadResource(gamesdb_new_publishers_map, "publishers", genFilePath(PUBLISHERS_JSON_FILE)) &&
!gamesdb_publishers_resource_request)
{
if (loadResource(gamesdb_new_publishers_map, "publishers",
genFilePath(PUBLISHERS_JSON_FILE)) && !gamesdb_publishers_resource_request)
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);
}
}
void TheGamesDBJSONRequestResources::ensureResources()
{
if (checkLoaded())
{
return;
}
for (int i = 0; i < MAX_WAIT_ITER; ++i) {
for (int i = 0; i < MAX_WAIT_ITER; ++i)
{
if (gamesdb_developers_resource_request &&
saveResource(gamesdb_developers_resource_request.get(), gamesdb_new_developers_map, "developers",
genFilePath(DEVELOPERS_JSON_FILE)))
{
saveResource(gamesdb_developers_resource_request.get(),
gamesdb_new_developers_map, "developers", genFilePath(DEVELOPERS_JSON_FILE)))
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;
}
std::this_thread::sleep_for(std::chrono::milliseconds(POLL_TIME_MS));
}
LOG(LogError) << "Timed out while waiting for resources\n";
@ -120,26 +108,28 @@ void TheGamesDBJSONRequestResources::ensureResources()
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,
const std::string& resource_name, const std::string& file_name)
bool TheGamesDBJSONRequestResources::saveResource(
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";
return true;
}
if (req->status() == HttpReq::REQ_IN_PROGRESS)
{
return false; // Not ready: wait some more
if (req->status() == HttpReq::REQ_IN_PROGRESS) {
return false; // Not ready: wait some more.
}
if (req->status() != HttpReq::REQ_SUCCESS)
{
LOG(LogError) << "Resource request for " << file_name << " failed:\n\t" << req->getErrorMsg();
return true; // Request failed, resetting request.
if (req->status() != HttpReq::REQ_SUCCESS) {
LOG(LogError) << "Resource request for " << file_name <<
" failed:\n\t" << req->getErrorMsg();
return true; // Request failed, resetting request..
}
ensureScrapersResourcesDir();
@ -160,47 +150,42 @@ std::unique_ptr<HttpReq> TheGamesDBJSONRequestResources::fetchResource(const std
return std::unique_ptr<HttpReq>(new HttpReq(path));
}
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);
if (!fin.good())
{
return 1;
}
std::stringstream buffer;
buffer << fin.rdbuf();
Document doc;
doc.Parse(buffer.str().c_str());
if (doc.HasParseError())
{
std::string err = std::string("TheGamesDBJSONRequest - Error parsing JSON for resource file ") + file_name +
":\n\t" + GetParseError_En(doc.GetParseError());
if (doc.HasParseError()) {
std::string err = std::string("TheGamesDBJSONRequest - "
"Error parsing JSON for resource file ") + file_name +
":\n\t" + GetParseError_En(doc.GetParseError());
LOG(LogError) << err;
return 1;
}
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";
LOG(LogError) << err;
return 1;
}
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;
if (!entry.IsObject() || !entry.HasMember("id") || !entry["id"].IsInt() || !entry.HasMember("name") ||
!entry["name"].IsString())
{
if (!entry.IsObject() || !entry.HasMember("id") || !entry["id"].IsInt() ||
!entry.HasMember("name") || !entry["name"].IsString())
continue;
}
resource[entry["id"].GetInt()] = entry["name"].GetString();
}
return resource.empty();

View file

@ -1,3 +1,10 @@
//
// GamesDBJSONScraperResources.h
//
// Functions specifically for scraping from thegamesdb.net
// Called from GamesDBJSONScraper.
//
#pragma once
#ifndef 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"
struct TheGamesDBJSONRequestResources
{
struct TheGamesDBJSONRequestResources {
TheGamesDBJSONRequestResources() = default;
void prepare();
@ -25,12 +30,17 @@ struct TheGamesDBJSONRequestResources
private:
bool checkLoaded();
bool saveResource(HttpReq* req, 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);
bool saveResource(
HttpReq* req,
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(
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_publishers_resource_request;
@ -39,4 +49,4 @@ struct TheGamesDBJSONRequestResources
std::string getScrapersResouceDir();
#endif // ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_H
#endif // ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_RESOURCES_H

View file

@ -1,3 +1,11 @@
//
// Scraper.cpp
//
// Main scraper logic.
// Called from ScraperSearchComponent.
// Calls either GamesDBJSONScraper or ScreenScraper.
//
#include "scrapers/Scraper.h"
#include "FileData.h"
@ -19,15 +27,11 @@ std::unique_ptr<ScraperSearchHandle> startScraperSearch(const ScraperSearchParam
const std::string& name = Settings::getInstance()->getString("Scraper");
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())
{
LOG(LogWarning) << "Configured scraper (" << name << ") unavailable, scraping aborted.";
}
else
{
scraper_request_funcs.at(name)(params, handle->mRequestQueue, handle->mResults);
}
return handle;
}
@ -36,9 +40,7 @@ std::vector<std::string> getScraperList()
{
std::vector<std::string> list;
for(auto it = scraper_request_funcs.cbegin(); it != scraper_request_funcs.cend(); it++)
{
list.push_back(it->first);
}
return list;
}
@ -49,7 +51,7 @@ bool isValidConfiguredScraper()
return scraper_request_funcs.find(name) != scraper_request_funcs.end();
}
// ScraperSearchHandle
// ScraperSearchHandle.
ScraperSearchHandle::ScraperSearchHandle()
{
setStatus(ASYNC_IN_PROGRESS);
@ -62,51 +64,45 @@ void ScraperSearchHandle::update()
if(!mRequestQueue.empty())
{
// a request can add more requests to the queue while running,
// so be careful with references into the queue
// A request can add more requests to the queue while running,
// so be careful with references into the queue.
auto& req = *(mRequestQueue.front());
AsyncHandleStatus status = req.status();
if(status == ASYNC_ERROR)
{
// propegate error
if(status == ASYNC_ERROR) {
// Propagate error.
setError(req.getStatusString());
// empty our queue
// Empty our queue.
while(!mRequestQueue.empty())
mRequestQueue.pop();
return;
}
// finished this one, see if we have any more
// Finished this one, see if we have any more.
if(status == ASYNC_DONE)
{
mRequestQueue.pop();
}
// status == ASYNC_IN_PROGRESS
// Status == ASYNC_IN_PROGRESS.
}
// we finished without any errors!
if(mRequestQueue.empty())
{
// We finished without any errors!
if(mRequestQueue.empty()) {
setStatus(ASYNC_DONE);
return;
}
}
// ScraperRequest
ScraperRequest::ScraperRequest(std::vector<ScraperSearchResult>& resultsWrite) : mResults(resultsWrite)
// ScraperRequest.
ScraperRequest::ScraperRequest(std::vector<ScraperSearchResult>& resultsWrite)
: mResults(resultsWrite)
{
}
// ScraperHttpRequest
ScraperHttpRequest::ScraperHttpRequest(std::vector<ScraperSearchResult>& resultsWrite, const std::string& url)
: ScraperRequest(resultsWrite)
// ScraperHttpRequest.
ScraperHttpRequest::ScraperHttpRequest(std::vector<ScraperSearchResult>&
resultsWrite, const std::string& url) : ScraperRequest(resultsWrite)
{
setStatus(ASYNC_IN_PROGRESS);
mReq = std::unique_ptr<HttpReq>(new HttpReq(url));
@ -117,41 +113,43 @@ void ScraperHttpRequest::update()
HttpReq::Status status = mReq->status();
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);
return;
}
// not ready yet
// Not ready yet.
if(status == HttpReq::REQ_IN_PROGRESS)
return;
// everything else is some sort of error
LOG(LogError) << "ScraperHttpRequest network error (status: " << status << ") - " << mReq->getErrorMsg();
// Everything else is some sort of error.
LOG(LogError) << "ScraperHttpRequest network error (status: " << status<< ") - "
<< 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));
}
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;
// 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.
if (!result.imageType.empty())
{
if (!result.imageType.empty()) {
ext = result.imageType;
}else{
}
else {
size_t dot = result.imageUrl.find_last_of('.');
if (dot != std::string::npos)
@ -160,8 +158,8 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result, const Scrape
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.imageUrl = "";
}));
@ -174,14 +172,12 @@ void MDResolveHandle::update()
return;
auto it = mFuncs.cbegin();
while(it != mFuncs.cend())
{
if(it->first->status() == ASYNC_ERROR)
{
while(it != mFuncs.cend()) {
if(it->first->status() == ASYNC_ERROR) {
setError(it->first->getStatusString());
return;
}else if(it->first->status() == ASYNC_DONE)
{
}
else if(it->first->status() == ASYNC_DONE) {
it->second();
it = mFuncs.erase(it);
continue;
@ -193,14 +189,17 @@ void MDResolveHandle::update()
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,
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) :
mSavePath(path), mMaxWidth(maxWidth), mMaxHeight(maxHeight), mReq(new HttpReq(url))
ImageDownloadHandle::ImageDownloadHandle(const std::string& 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)
return;
if(mReq->status() != HttpReq::REQ_SUCCESS)
{
if(mReq->status() != HttpReq::REQ_SUCCESS) {
std::stringstream ss;
ss << "Network error: " << mReq->getErrorMsg();
setError(ss.str());
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);
if(stream.bad())
{
if(stream.bad()) {
setError("Failed to open image path to write. Permission error? Disk full?");
return;
}
@ -228,15 +225,13 @@ void ImageDownloadHandle::update()
const std::string& content = mReq->getContent();
stream.write(content.data(), content.length());
stream.close();
if(stream.bad())
{
if(stream.bad()) {
setError("Failed to save image. Disk full?");
return;
}
// resize it
if(!resizeImage(mSavePath, mMaxWidth, mMaxHeight))
{
// Resize it.
if(!resizeImage(mSavePath, mMaxWidth, mMaxHeight)) {
setError("Error saving resized image. Out of memory? Disk full?");
return;
}
@ -244,31 +239,30 @@ void ImageDownloadHandle::update()
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)
{
// nothing to do
// Nothing to do.
if(maxWidth == 0 && maxHeight == 0)
return true;
FREE_IMAGE_FORMAT format = FIF_UNKNOWN;
FIBITMAP* image = NULL;
//detect the filetype
// Detect the filetype.
format = FreeImage_GetFileType(path.c_str(), 0);
if(format == FIF_UNKNOWN)
format = FreeImage_GetFIFFromFilename(path.c_str());
if(format == FIF_UNKNOWN)
{
if(format == FIF_UNKNOWN) {
LOG(LogError) << "Error - could not detect filetype for image \"" << path << "\"!";
return false;
}
//make sure we can read this filetype first, then load it
if(FreeImage_FIFSupportsReading(format))
{
// Make sure we can read this filetype first, then load it.
if(FreeImage_FIFSupportsReading(format)) {
image = FreeImage_Load(format, path.c_str());
}else{
}
else {
LOG(LogError) << "Error - file format reading not supported for image \"" << path << "\"!";
return false;
}
@ -277,18 +271,14 @@ bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
float height = (float)FreeImage_GetHeight(image);
if(maxWidth == 0)
{
maxWidth = (int)((maxHeight / height) * width);
}else if(maxHeight == 0)
{
else if(maxHeight == 0)
maxHeight = (int)((maxWidth / width) * height);
}
FIBITMAP* imageRescaled = FreeImage_Rescale(image, maxWidth, maxHeight, FILTER_BILINEAR);
FreeImage_Unload(image);
if(imageRescaled == NULL)
{
if(imageRescaled == NULL) {
LOG(LogError) << "Could not resize image! (not enough memory? invalid bitdepth?)";
return false;
}
@ -302,7 +292,8 @@ bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
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 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))
Utils::FileSystem::createDirectory(path);
path += name + extension;
return path;
}

View file

@ -1,3 +1,11 @@
//
// Scraper.h
//
// Main scraper logic.
// Called from ScraperSearchComponent.
// Calls either GamesDBJSONScraper or ScreenScraper.
//
#pragma once
#ifndef ES_APP_SCRAPERS_SCRAPER_H
#define ES_APP_SCRAPERS_SCRAPER_H
@ -16,31 +24,29 @@
class FileData;
class SystemData;
struct ScraperSearchParams
{
struct ScraperSearchParams {
SystemData* system;
FileData* game;
std::string nameOverride;
};
struct ScraperSearchResult
{
struct ScraperSearchResult {
ScraperSearchResult() : mdl(GAME_METADATA) {};
MetaDataList mdl;
std::string imageUrl;
std::string thumbnailUrl;
// Needed to pre-set the image type
// Needed to pre-set the image type.
std::string imageType;
};
// 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.
// 1. Do some HTTP request(s) -> process it -> return the results
// 2. Do some local filesystem queries (an offline scraper) -> 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.
// The first way needs to be asynchronous while it's waiting for the HTTP request to return.
// The second doesn't.
@ -51,25 +57,26 @@ struct ScraperSearchResult
// ... process search ...
// 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,
// and I'm not sure of the performance of threads on the Pi (single-core ARM).
// We could do this if we used threads. Right now ES doesn't because I'm pretty sure I'll
// 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.
// 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.
// So, I did it the "long" way.
// ScraperSearchHandle - one logical search, e.g. "search for mario"
// ScraperRequest - encapsulates some sort of asynchronous request that will ultimately return some results
// ScraperHttpRequest - implementation of ScraperRequest that waits on an HttpReq, then processes it with some processing function.
// ScraperSearchHandle - one logical search, e.g. "search for mario".
// ScraperRequest - encapsulates some sort of asynchronous request that will ultimately
// 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
{
public:
ScraperRequest(std::vector<ScraperSearchResult>& resultsWrite);
// returns "true" once we're done
// Returns "true" once we're done.
virtual void update() = 0;
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
{
public:
@ -85,43 +92,46 @@ public:
virtual void update() override;
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:
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
{
public:
ScraperSearchHandle();
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:
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::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);
// returns a list of valid scraper names
// Returns a list of valid scraper names.
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();
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.
class MDResolveHandle : public AsyncHandle
{
@ -129,7 +139,8 @@ public:
MDResolveHandle(const ScraperSearchResult& result, const ScraperSearchParams& search);
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:
ScraperSearchResult mResult;
@ -141,7 +152,8 @@ private:
class ImageDownloadHandle : public AsyncHandle
{
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;
@ -152,19 +164,24 @@ private:
int mMaxHeight;
};
//About the same as "~/.emulationstation/downloaded_images/[system_name]/[game_name].[url's extension]".
//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);
// About the same as:
// "~/.emulationstation/downloaded_images/[system_name]/[game_name].[url's extension]".
// 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").
std::unique_ptr<ImageDownloadHandle> downloadImageAsync(const std::string& url, const std::string& saveAs);
// Will resize according to Settings::getInt("ScraperResizeWidth") and
// 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.
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.
//Will overwrite the image at [path] with the new resized one.
//Returns true if successful, false otherwise.
// You can pass 0 for maxWidth or maxHeight to automatically keep the aspect ratio.
// It will overwrite the image at [path] with the new resized one.
// Returns true if successful, false otherwise.
bool resizeImage(const std::string& path, int maxWidth, int maxHeight);
#endif // ES_APP_SCRAPERS_SCRAPER_H

View file

@ -1,3 +1,10 @@
//
// ScreenScraper.cpp
//
// Functions specifically for scraping from screenscraper.fr
// Called from Scraper.
//
#include "scrapers/ScreenScraper.h"
#include "utils/TimeUtil.h"
@ -12,17 +19,15 @@
using namespace PlatformIds;
/**
List of systems and their IDs from
https://www.screenscraper.fr/api/systemesListe.php?devid=xxx&devpassword=yyy&softname=zzz&output=XML
**/
const std::map<PlatformId, unsigned short> screenscraper_platformid_map{
// List of systems and their IDs from:
// https://www.screenscraper.fr/api/systemesListe.php?devid=xxx&devpassword=yyy&softname=zzz&output=XML
const std::map<PlatformId, unsigned short> screenscraper_platformid_map {
{ THREEDO, 29 },
{ AMIGA, 64 },
{ AMSTRAD_CPC, 65 },
{ APPLE_II, 86 },
{ 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_5200, 40 },
{ ATARI_7800, 41 },
@ -30,7 +35,7 @@ const std::map<PlatformId, unsigned short> screenscraper_platformid_map{
{ ATARI_JAGUAR_CD, 171 },
{ ATARI_LYNX, 28 },
{ ATARI_ST, 42},
// missing Atari XE ?
// Missing Atari XE ?
{ COLECOVISION, 48 },
{ COMMODORE_64, 66 },
{ INTELLIVISION, 115 },
@ -72,7 +77,7 @@ const std::map<PlatformId, unsigned short> screenscraper_platformid_map{
{ PLAYSTATION, 57 },
{ PLAYSTATION_2, 58 },
{ PLAYSTATION_3, 59 },
// missing Sony Playstation 4 ?
// Missing Sony Playstation 4 ?
{ PLAYSTATION_VITA, 62 },
{ PLAYSTATION_PORTABLE, 61 },
{ SUPER_NINTENDO, 4 },
@ -88,12 +93,11 @@ const std::map<PlatformId, unsigned short> screenscraper_platformid_map{
{ TANDY, 144 }
};
// 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_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();
}
// Help XML parsing method, finding an direct child XML node starting from the parent and 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)
// Help XML parsing method, finding an direct child XML node starting from the parent and
// 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 (pugi::xml_node node : node_parent.children(node_name.c_str()))
{
for (auto _val : attribute_values) {
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)
return node;
}
}
return pugi::xml_node(NULL);
}
void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
std::queue< std::unique_ptr<ScraperRequest> >& requests,
std::vector<ScraperSearchResult>& results)
std::queue< std::unique_ptr<ScraperRequest> >& requests,
std::vector<ScraperSearchResult>& results)
{
std::string path;
@ -133,22 +136,23 @@ void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
auto& platforms = params.system->getPlatformIds();
std::vector<unsigned short> p_ids;
// Get the IDs of each platform from the ScreenScraper list
for (auto platformIt = platforms.cbegin(); platformIt != platforms.cend(); platformIt++)
{
// Get the IDs of each platform from the ScreenScraper list.
for (auto platformIt = platforms.cbegin(); platformIt != platforms.cend(); 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);
}else{
LOG(LogWarning) << "ScreenScraper: no support for platform " << getPlatformName(*platformIt);
// Add the scrape request without a platform/system ID
requests.push(std::unique_ptr<ScraperRequest>(new ScreenScraperRequest(requests, results, path)));
}
else {
LOG(LogWarning) << "ScreenScraper: no support for platform " <<
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());
auto last = std::unique(p_ids.begin(), 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 += 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);
pugi::xml_document doc;
pugi::xml_parse_result parseResult = doc.load_string(req->getContent().c_str());
if (!parseResult)
{
if (!parseResult) {
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();
setError(err);
@ -182,17 +188,15 @@ void ScreenScraperRequest::process(const std::unique_ptr<HttpReq>& req, std::vec
}
processGame(doc, results);
}
void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc, std::vector<ScraperSearchResult>& out_results)
void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
std::vector<ScraperSearchResult>& out_results)
{
pugi::xml_node data = xmldoc.child("Data");
pugi::xml_node game = data.child("jeu");
if (game)
{
if (game) {
ScraperSearchResult result;
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();
// 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)
std::string description = find_child_by_attribute_list(game.child("synopsis"), "synopsis", "langue", { language, "en", "wor" }).text().get();
// Description fallback language: EN, WOR(LD).
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, "&nbsp;", " "));
}
// 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());
// 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());
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'.
// 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();
// Get the date proper. The API returns multiple 'date' children nodes to the 'dates'
// main child of 'jeu'.
// 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;
// Date can be YYYY-MM-DD or just YYYY.
if (_date.length() > 4)
{
result.mdl.set("releasedate", Utils::Time::DateTime(Utils::Time::stringToTime(_date, "%Y-%m-%d")));
} else if (_date.length() > 0)
{
result.mdl.set("releasedate", Utils::Time::DateTime(Utils::Time::stringToTime(_date, "%Y")));
if (_date.length() > 4) {
result.mdl.set("releasedate", Utils::Time::DateTime(
Utils::Time::stringToTime(_date, "%Y-%m-%d")));
}
else if (_date.length() > 0) {
result.mdl.set("releasedate", Utils::Time::DateTime(
Utils::Time::stringToTime(_date, "%Y")));
}
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();
if (!developer.empty())
result.mdl.set("developer", Utils::String::replace(developer, "&nbsp;", " "));
// 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();
if (!publisher.empty())
result.mdl.set("publisher", Utils::String::replace(publisher, "&nbsp;", " "));
// Players
// Players.
result.mdl.set("players", game.child("joueurs").text().get());
// TODO: Validate rating
if (Settings::getInstance()->getBool("ScrapeRatings") && game.child("note"))
{
// TODO: Validate rating.
if (Settings::getInstance()->getBool("ScrapeRatings") && game.child("note")) {
float ratingVal = (game.child("note").text().as_int() / 20.0f);
std::stringstream ss;
ss << ratingVal;
result.mdl.set("rating", ss.str());
}
// Media super-node
// Media super-node.
pugi::xml_node media_list = game.child("medias");
if (media_list)
{
if (media_list) {
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
// <media type="..." region="..." format="...">
// 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())
{
// Region fallback: WOR(LD), US, CUS(TOM?), JP, EU
for (auto _region : std::vector<std::string>{ region, "wor", "us", "cus", "jp", "eu" })
{
if (results.size()) {
// Region fallback: WOR(LD), US, CUS(TOM?), JP, EU.
for (auto _region : std::vector<std::string>{ region,
"wor", "us", "cus", "jp", "eu" }) {
if (art)
break;
for (auto node : results)
{
if (node.node().attribute("region").value() == _region)
{
for (auto node : results) {
if (node.node().attribute("region").value() == _region) {
art = node.node();
break;
}
}
}
} // results
} // Results.
if (art)
{
// Sending a 'softname' containing space will make the image URLs returned by the API also contain the space.
// Escape any spaces in the URL here
if (art) {
// Sending a 'softname' containing space will make the image URLs returned
// by the API also contain the space. Escape any spaces in the URL here
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();
if (!media_type.empty())
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";
}else{
}
else {
LOG(LogDebug) << "Failed to find media XML node with name=" << ssConfig.media_name;
}
}
out_results.push_back(result);
} // game
} // Game.
}
// Currently not used in this module
void ScreenScraperRequest::processList(const pugi::xml_document& xmldoc, std::vector<ScraperSearchResult>& results)
// Currently not used in this module.
void ScreenScraperRequest::processList(const pugi::xml_document& xmldoc,
std::vector<ScraperSearchResult>& results)
{
assert(mRequestQueue != nullptr);
@ -321,26 +327,26 @@ void ScreenScraperRequest::processList(const pugi::xml_document& xmldoc, std::ve
ScreenScraperRequest::ScreenScraperConfig ssConfig;
// limit the number of results per platform, not in total.
// otherwise if the first platform returns >= 7 games
// Limit the number of results per platform, not in total.
// Otherwise if the first platform returns >= 7 games
// but the second platform contains the relevant game,
// 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 name = game.child("nom").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");
}
}
std::string ScreenScraperRequest::ScreenScraperConfig::getGameSearchUrl(const std::string gameName) const
std::string ScreenScraperRequest::ScreenScraperConfig::getGameSearchUrl(
const std::string gameName) const
{
return API_URL_BASE
+ "/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)
+ "&output=xml"
+ "&romnom=" + HttpReq::urlEncode(gameName);
}

View file

@ -1,3 +1,10 @@
//
// ScreenScraper.h
//
// Functions specifically for scraping from screenscraper.fr
// Called from Scraper.
//
#pragma once
#ifndef ES_APP_SCRAPERS_SCREEN_SCRAPER_H
#define ES_APP_SCRAPERS_SCREEN_SCRAPER_H
@ -7,67 +14,76 @@
namespace pugi { class xml_document; }
void screenscraper_generate_scraper_requests(const ScraperSearchParams& params, std::queue< std::unique_ptr<ScraperRequest> >& requests,
std::vector<ScraperSearchResult>& results);
void screenscraper_generate_scraper_requests(
const ScraperSearchParams& params,
std::queue< std::unique_ptr<ScraperRequest> >& requests,
std::vector<ScraperSearchResult>& results);
class ScreenScraperRequest : public ScraperHttpRequest
{
public:
// 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) {}
// ctor for a GetGame request
ScreenScraperRequest(std::vector<ScraperSearchResult>& resultsWrite, const std::string& url) : ScraperHttpRequest(resultsWrite, url), mRequestQueue(nullptr) {}
// 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) {}
// 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 {
std::string getGameSearchUrl(const std::string gameName) const;
// Access to the API
const std::string API_DEV_U = { 91, 32, 7, 17 };
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 };
// Access to the API.
const std::string API_DEV_U =
{ 91, 32, 7, 17 };
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_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):
- ss: in-game screenshot
- box-3D: 3D boxart
- box-2D: 2D boxart (default)
- screenmarque : marquee
- sstitle: in-game start screenshot
- steamgrid: Steam artwork
- wheel: spine
- support-2D: media showing the 2d 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.
**/
// Which type of image artwork we need. Possible values (not a comprehensive list):
// - ss: in-game screenshot
// - box-3D: 3D boxart
// - box-2D: 2D boxart (default)
// - screenmarque : marquee
// - sstitle: in-game start screenshot
// - steamgrid: Steam artwork
// - wheel: spine
// - support-2D: media showing the 2d boxart on the cart
// - support-3D: media showing the 3d boxart on the cart
//
// 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";
// Which Region to use when selecting the artwork
// Applies to: artwork, name of the game, date of release
// Which Region to use when selecting the artwork.
// Applies to: artwork, name of the game, date of release.
std::string region = "US";
// Which Language to use when selecting the textual information
// Applies to: description, genre
// Which Language to use when selecting the textual information.
// Applies to: description, genre.
std::string language = "EN";
ScreenScraperConfig() {};
} configuration;
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 processGame(const pugi::xml_document& xmldoc, std::vector<ScraperSearchResult>& results);
bool isGameRequest() { return !mRequestQueue; }
std::queue< std::unique_ptr<ScraperRequest> >* mRequestQueue;
};
#endif // ES_APP_SCRAPERS_SCREEN_SCRAPER_H

View file

@ -1,17 +1,22 @@
//
// AsyncHandle.h
//
// Asynchronous operations used by ScraperSearchComponent and Scraper.
//
#pragma once
#ifndef ES_CORE_ASYNC_HANDLE_H
#define ES_CORE_ASYNC_HANDLE_H
#include <string>
enum AsyncHandleStatus
{
enum AsyncHandleStatus {
ASYNC_IN_PROGRESS,
ASYNC_ERROR,
ASYNC_DONE
};
// Handle for some asynchronous operation.
// Handle for some asynchronous operations.
class AsyncHandle
{
public:
@ -23,11 +28,11 @@ public:
// Update and return the latest status.
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()
{
switch(mStatus)
{
switch (mStatus) {
case ASYNC_IN_PROGRESS:
return "in progress";
case ASYNC_ERROR:

View file

@ -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 "utils/FileSystemUtil.h"
@ -10,17 +18,15 @@ std::map<CURL*, HttpReq*> HttpReq::s_requests;
std::string HttpReq::urlEncode(const std::string &s)
{
const std::string unreserved = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~";
const std::string unreserved =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~";
std::string escaped="";
for(size_t i=0; i<s.length(); i++)
{
if (unreserved.find_first_of(s[i]) != std::string::npos)
{
for (size_t i=0; i<s.length(); i++) {
if (unreserved.find_first_of(s[i]) != std::string::npos) {
escaped.push_back(s[i]);
}
else
{
else {
escaped.append("%");
char buf[3];
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)
{
//the worst guess
// The worst guess.
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)
: mStatus(REQ_IN_PROGRESS), mHandle(NULL)
: mStatus(REQ_IN_PROGRESS), mHandle(NULL)
{
mHandle = curl_easy_init();
if(mHandle == NULL)
{
if (mHandle == NULL) {
mStatus = REQ_IO_ERROR;
onError("curl_easy_init failed");
return;
}
//set the url
// Set the url.
CURLcode err = curl_easy_setopt(mHandle, CURLOPT_URL, url.c_str());
if(err != CURLE_OK)
{
if (err != CURLE_OK) {
mStatus = REQ_IO_ERROR;
onError(curl_easy_strerror(err));
return;
}
//set curl to handle redirects
// Set curl to handle redirects.
err = curl_easy_setopt(mHandle, CURLOPT_FOLLOWLOCATION, 1L);
if(err != CURLE_OK)
{
if (err != CURLE_OK) {
mStatus = REQ_IO_ERROR;
onError(curl_easy_strerror(err));
return;
}
//set curl max redirects
// Set curl max redirects.
err = curl_easy_setopt(mHandle, CURLOPT_MAXREDIRS, 2L);
if(err != CURLE_OK)
{
if (err != CURLE_OK) {
mStatus = REQ_IO_ERROR;
onError(curl_easy_strerror(err));
return;
}
//set curl restrict redirect protocols
// Set curl restrict redirect protocols.
err = curl_easy_setopt(mHandle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
if(err != CURLE_OK)
{
if (err != CURLE_OK) {
mStatus = REQ_IO_ERROR;
onError(curl_easy_strerror(err));
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);
if(err != CURLE_OK)
{
if (err != CURLE_OK) {
mStatus = REQ_IO_ERROR;
onError(curl_easy_strerror(err));
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);
if(err != CURLE_OK)
{
if (err != CURLE_OK) {
mStatus = REQ_IO_ERROR;
onError(curl_easy_strerror(err));
return;
}
//add the handle to our multi
// Add the handle to our multi.
CURLMcode merr = curl_multi_add_handle(s_multi_handle, mHandle);
if(merr != CURLM_OK)
{
if (merr != CURLM_OK) {
mStatus = REQ_IO_ERROR;
onError(curl_multi_strerror(merr));
return;
@ -117,14 +117,14 @@ HttpReq::HttpReq(const std::string& url)
HttpReq::~HttpReq()
{
if(mHandle)
{
if (mHandle) {
s_requests.erase(mHandle);
CURLMcode merr = curl_multi_remove_handle(s_multi_handle, mHandle);
if(merr != CURLM_OK)
LOG(LogError) << "Error removing curl_easy handle from curl_multi: " << curl_multi_strerror(merr);
if (merr != CURLM_OK)
LOG(LogError) << "Error removing curl_easy handle from curl_multi: " <<
curl_multi_strerror(merr);
curl_easy_cleanup(mHandle);
}
@ -132,12 +132,10 @@ HttpReq::~HttpReq()
HttpReq::Status HttpReq::status()
{
if(mStatus == REQ_IN_PROGRESS)
{
if (mStatus == REQ_IN_PROGRESS) {
int 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;
onError(curl_multi_strerror(merr));
return mStatus;
@ -145,22 +143,19 @@ HttpReq::Status HttpReq::status()
int msgs_left;
CURLMsg* msg;
while((msg = curl_multi_info_read(s_multi_handle, &msgs_left)) != nullptr)
{
if(msg->msg == CURLMSG_DONE)
{
while ((msg = curl_multi_info_read(s_multi_handle, &msgs_left)) != nullptr) {
if (msg->msg == CURLMSG_DONE) {
HttpReq* req = s_requests[msg->easy_handle];
if(req == NULL)
{
if (req == NULL) {
LOG(LogError) << "Cannot find easy handle!";
continue;
}
if(msg->data.result == CURLE_OK)
{
if (msg->data.result == CURLE_OK) {
req->mStatus = REQ_SUCCESS;
}else{
}
else {
req->mStatus = REQ_IO_ERROR;
req->onError(curl_easy_strerror(msg->data.result));
}
@ -187,9 +182,9 @@ std::string HttpReq::getErrorMsg()
return mErrorMsg;
}
//used as a curl callback
//size = size of an element, nmemb = number of elements
//return value is number of elements successfully read
// Used as a curl callback.
// size = size of an element, nmemb = number of elements.
// Return value is number of elements successfully read.
size_t HttpReq::write_content(void* buff, size_t size, size_t nmemb, void* req_ptr)
{
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;
}
//used as a curl callback
/*int HttpReq::update_progress(void* req_ptr, double dlTotal, double dlNow, double ulTotal, double ulNow)
{
}*/
// Used as a curl callback.
//int HttpReq::update_progress(void* req_ptr, double dlTotal,
// double dlNow, double ulTotal, double ulNow)
//{
//}

View file

@ -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
#ifndef ES_CORE_HTTP_REQ_H
#define ES_CORE_HTTP_REQ_H
@ -6,22 +14,27 @@
#include <map>
#include <sstream>
/* Usage:
* HttpReq myRequest("www.google.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
*
* //once one of those completes, the request is ready
* if(myRequest.status() != REQ_SUCCESS)
* {
* //an error occured
* LOG(LogError) << "HTTP request error - " << myRequest.getErrorMessage();
* return;
* }
*
* std::string content = myRequest.getContent();
* //process contents...
*/
// Usage:
// 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.
//
// Once one of those calls complete, the request is ready.
//
// Do something like this to capture errors:
// if(myRequest.status() != REQ_SUCCESS)
// {
// // 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
{
@ -30,31 +43,31 @@ public:
~HttpReq();
enum Status
{
REQ_IN_PROGRESS, //request is in progress
REQ_SUCCESS, //request completed successfully, get it with getContent()
enum Status {
REQ_IN_PROGRESS, // Request is in progress.
REQ_SUCCESS, // Request completed successfully, get it with getContent().
REQ_IO_ERROR, //some error happened, get it with getErrorMsg()
REQ_BAD_STATUS_CODE, //some invalid HTTP response status code happened (non-200)
REQ_INVALID_RESPONSE //the HTTP response was invalid
REQ_IO_ERROR, // Some error happened, get it with getErrorMsg().
REQ_BAD_STATUS_CODE, // Some invalid HTTP response status code happened (non-200).
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 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 bool isUrl(const std::string& s);
private:
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
//why do I have to handle ALL messages at once
// God dammit libcurl why can't you have some way to check the status of an
// individual handle why do I have to handle ALL messages at once.
static std::map<CURL*, HttpReq*> s_requests;
static CURLM* s_multi_handle;

View file

@ -1,9 +1,8 @@
//
// Settings.cpp
//
// Functions to read from and write to the configuration file es_settings.cfg
// are included here. The default values for the application settings are
// defined here as well.
// Functions to read from and write to the configuration file es_settings.cfg.
// The default values for the application settings are defined here as well.
//
#include "Settings.h"

View file

@ -1,9 +1,8 @@
//
// Settings.h
//
// Functions to read from and write to the configuration file es_settings.cfg
// are included here. The default values for the application settings are
// defined here as well.
// Functions to read from and write to the configuration file es_settings.cfg.
// The default values for the application settings are defined here as well.
//
#pragma once