mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-25 15:45:38 +00:00
Large code cleanup and code documentation update, mostly related to the scraper. Only cosmetic changes in this commit.
This commit is contained in:
parent
da946279de
commit
fd7da08bf9
|
@ -1,3 +1,9 @@
|
|||
//
|
||||
// AsyncReqComponent.cpp
|
||||
//
|
||||
// Deprecated, not in use any longer?
|
||||
//
|
||||
|
||||
#include "components/AsyncReqComponent.h"
|
||||
|
||||
#include "renderers/Renderer.h"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, " ", " "));
|
||||
}
|
||||
|
||||
// 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, " ", " "));
|
||||
|
||||
// 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, " ", " "));
|
||||
|
||||
// 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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
//{
|
||||
//}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue