Added semi-automatic scraping mode, fixed some scraping bugs and improved folder metadata editing.

This commit is contained in:
Leon Styhre 2020-06-13 16:47:12 +02:00
parent b01bccc8d6
commit 8492160a80
11 changed files with 103 additions and 53 deletions

View file

@ -38,10 +38,7 @@ const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls +
MetaDataDecl folderDecls[] = {
{"name", MD_STRING, "", false, "name", "enter game name", true},
{"sortname", MD_STRING, "", false, "sortname", "enter game sort name", false},
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true},
{"rating", MD_RATING, "0", false, "rating", "enter rating", true},
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date", true},
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer", true},
{"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher", true},
{"genre", MD_STRING, "unknown", false, "genre", "enter game genre", true},

View file

@ -72,6 +72,8 @@ public:
inline MetaDataListType getType() const { return mType; }
inline const std::vector<MetaDataDecl>& getMDD() const { return getMDDByType(getType()); }
inline const std::vector<MetaDataDecl>& getMDD(MetaDataListType type) const
{ return getMDDByType(type); }
private:
MetaDataListType mType;

View file

@ -126,14 +126,27 @@ GuiGamelistOptions::GuiGamelistOptions(
mMenu.addRow(row);
}
if (UIModeController::getInstance()->isUIModeFull() && !fromPlaceholder &&
!(mSystem->isCollection() && file->getType() == FOLDER)) {
row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow,
"EDIT THIS GAME'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(makeArrow(mWindow), false);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this));
mMenu.addRow(row);
if (file->getType() == FOLDER) {
if (UIModeController::getInstance()->isUIModeFull() && !fromPlaceholder &&
!(mSystem->isCollection() && file->getType() == FOLDER)) {
row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow,
"EDIT THIS FOLDER'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(makeArrow(mWindow), false);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this));
mMenu.addRow(row);
}
}
else {
if (UIModeController::getInstance()->isUIModeFull() && !fromPlaceholder &&
!(mSystem->isCollection() && file->getType() == FOLDER)) {
row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow,
"EDIT THIS GAME'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(makeArrow(mWindow), false);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this));
mMenu.addRow(row);
}
}
// Buttons. Logic to apply or cancel settings are handled by the destructor.
@ -234,10 +247,20 @@ void GuiGamelistOptions::openMetaDataEd()
};
}
mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, file->metadata.getMDD(), p,
Utils::FileSystem::getFileName(file->getPath()), std::bind(
&IGameListView::onFileChanged, ViewController::get()->getGameListView(
file->getSystem()).get(), file, FILE_METADATA_CHANGED), deleteBtnFunc));
if (file->getType() == FOLDER) {
mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata,
file->metadata.getMDD(FOLDER_METADATA), p,
Utils::FileSystem::getFileName(file->getPath()), std::bind(
&IGameListView::onFileChanged, ViewController::get()->getGameListView(
file->getSystem()).get(), file, FILE_METADATA_CHANGED), deleteBtnFunc));
}
else {
mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata,
file->metadata.getMDD(GAME_METADATA), p,
Utils::FileSystem::getFileName(file->getPath()), std::bind(
&IGameListView::onFileChanged, ViewController::get()->getGameListView(
file->getSystem()).get(), file, FILE_METADATA_CHANGED), deleteBtnFunc));
}
}
void GuiGamelistOptions::jumpToLetter()

View file

@ -163,7 +163,7 @@ GuiMetaDataEd::GuiMetaDataEd(
defaultLaunchString, ed, updateVal, multiLine] {
mWindow->pushGui(new GuiComplexTextEditPopup(mWindow, getHelpStyle(),
title, staticTextString, defaultLaunchString, ed->getValue(),
updateVal, multiLine, "SAVE"));
updateVal, multiLine, "APPLY"));
});
break;
}
@ -190,7 +190,7 @@ GuiMetaDataEd::GuiMetaDataEd(
auto updateVal = [ed](const std::string& newVal) { ed->setValue(newVal); };
row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] {
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), title,
ed->getValue(), updateVal, multiLine, "SAVE"));
ed->getValue(), updateVal, multiLine, "APPLY"));
});
break;
}
@ -204,9 +204,11 @@ GuiMetaDataEd::GuiMetaDataEd(
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 (mScraperParams.game->getType() != FOLDER) {
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 metadata",
[&] { save(); delete this; }));
@ -290,6 +292,15 @@ void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
MetaDataList* metadata = nullptr;
metadata = new MetaDataList(*mMetaData);
// Check if any values were manually changed before starting the scraping.
// If so, it's these values we should compare against when scraping, not
// the values previously saved for the game.
for (unsigned int i = 0; i < mEditors.size(); i++) {
const std::string& key = mMetaDataDecl.at(i).key;
if (metadata->get(key) != mEditors[i]->getValue())
metadata->set(key, mEditors[i]->getValue());
}
mMetadataUpdated = GuiScraperSearch::saveMetadata(result, *metadata);
// Update the list with the scraped metadata values.

View file

@ -202,13 +202,21 @@ void GuiScraperMenu::openOtherSettings()
s->addSaveFunc([scrape_overwrite] { Settings::getInstance()->setBool("ScraperOverwriteData",
scrape_overwrite->getState()); });
// Automatic scraping.
// Interactive scraping.
auto scraper_interactive = std::make_shared<SwitchComponent>(mWindow);
scraper_interactive->setState(Settings::getInstance()->getBool("ScraperInteractive"));
s->addWithLabel("INTERACTIVE MODE", scraper_interactive);
s->addSaveFunc([scraper_interactive] { Settings::getInstance()->setBool("ScraperInteractive",
scraper_interactive->getState()); });
// Semi-automatic scraping.
auto scraper_semiautomatic = std::make_shared<SwitchComponent>(mWindow);
scraper_semiautomatic->setState(Settings::getInstance()->getBool("ScraperSemiautomatic"));
s->addWithLabel("AUTO-APPROVE SINGLE GAME MATCHES", scraper_semiautomatic);
s->addSaveFunc([scraper_semiautomatic] {
Settings::getInstance()->setBool("ScraperSemiautomatic",
scraper_semiautomatic->getState()); });
mWindow->pushGui(s);
}

View file

@ -55,9 +55,15 @@ GuiScraperMulti::GuiScraperMulti(
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER);
mGrid.setEntry(mSubtitle, Vector2i(0, 2), false, true);
mSearchComp = std::make_shared<GuiScraperSearch>(mWindow,
approveResults ? GuiScraperSearch::ALWAYS_ACCEPT_MATCHING_CRC
: GuiScraperSearch::ALWAYS_ACCEPT_FIRST_RESULT);
if (approveResults && !Settings::getInstance()->getBool("ScraperSemiautomatic"))
mSearchComp = std::make_shared<GuiScraperSearch>(mWindow,
GuiScraperSearch::NEVER_AUTO_ACCEPT);
else if (approveResults && Settings::getInstance()->getBool("ScraperSemiautomatic"))
mSearchComp = std::make_shared<GuiScraperSearch>(mWindow,
GuiScraperSearch::ACCEPT_SINGLE_MATCHES);
else if (!approveResults)
mSearchComp = std::make_shared<GuiScraperSearch>(mWindow,
GuiScraperSearch::ALWAYS_ACCEPT_FIRST_RESULT);
mSearchComp->setAcceptCallback(std::bind(&GuiScraperMulti::acceptResult,
this, std::placeholders::_1));
mSearchComp->setSkipCallback(std::bind(&GuiScraperMulti::skip, this));

View file

@ -299,22 +299,31 @@ void GuiScraperSearch::onSearchDone(const std::vector<ScraperSearchResult>& resu
}
else {
mFoundGame = true;
ComponentListRow row;
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.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); });
mResultList->addRow(row);
if (!(mSearchType == ACCEPT_SINGLE_MATCHES && results.size() == 1)) {
ComponentListRow row;
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.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); });
mResultList->addRow(row);
}
mGrid.resetCursor();
}
mGrid.resetCursor();
}
mBlockAccept = false;
updateInfoPane();
// If there is no scraping result or if there is no game media to download
// as a thumbnail, then proceed directly.
// If there is no thumbnail to download and we're in semi-automatic mode, proceed to return
// the results or we'll get stuck forever waiting for a thumbnail to be downloaded.
if (mSearchType == ACCEPT_SINGLE_MATCHES && results.size() == 1 &&
mScraperResults.front().ThumbnailImageUrl == "")
returnResult(mScraperResults.front());
// For automatic mode, if there's no thumbnail to download or no matching games found,
// proceed directly or we'll get stuck forever.
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) {
if (mScraperResults.size() == 0 || (mScraperResults.size() > 0 &&
mScraperResults.front().ThumbnailImageUrl == "")) {
@ -324,7 +333,6 @@ void GuiScraperSearch::onSearchDone(const std::vector<ScraperSearchResult>& resu
returnResult(mScraperResults.front());
}
}
}
void GuiScraperSearch::onSearchError(const std::string& error)
@ -562,14 +570,11 @@ void GuiScraperSearch::updateThumbnail()
mThumbnailReq.reset();
// When the thumbnail has been downloaded and we are in non-interactive
// mode, we proceed to automatically download the rest of the media files.
// The reason to always complete the thumbnail download first is that it looks
// a lot more consistent in the GUI. And since the thumbnail is being cached
// anyway, this hardly takes any more time. Maybe rather the opposite as the
// image used for the thumbnail (cover or screenshot) would have had to be
// requested from the server again.
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT &&
// When the thumbnail has been downloaded and we are in automatic mode, or if
// we are in semi-automatic mode with a single matching game result, we proceed
// to immediately download the rest of the media files.
if ((mSearchType == ALWAYS_ACCEPT_FIRST_RESULT ||
(mSearchType == ACCEPT_SINGLE_MATCHES && mScraperResults.size() == 1)) &&
mScraperResults.front().thumbnailDownloadStatus == COMPLETED) {
if (mScraperResults.size() == 0)
mSkipCallback();
@ -645,6 +650,10 @@ bool GuiScraperSearch::saveMetadata(
if (result.mdl.get(key) == metadata.get(key))
continue;
// Make sure to set releasedate to the proper default value.
if (key == "releasedate" && metadata.get(key) == "19700101T010000")
metadata.set(key, mMetaDataDecl.at(i).defaultValue);
// Overwrite all the other values if the flag to overwrite data has been set.
if (Settings::getInstance()->getBool("ScraperOverwriteData")) {
metadata.set(key, result.mdl.get(key));

View file

@ -32,7 +32,7 @@ class GuiScraperSearch : public GuiComponent
public:
enum SearchType {
ALWAYS_ACCEPT_FIRST_RESULT,
ALWAYS_ACCEPT_MATCHING_CRC,
ACCEPT_SINGLE_MATCHES,
NEVER_AUTO_ACCEPT
};

View file

@ -398,13 +398,6 @@ bool ViewController::input(InputConfig* config, Input input)
// to play when we've closed the menu.
if (mSystemListView->isAnimationPlaying(0))
mSystemListView->finishAnimation(0);
// for (unsigned int i = 0; i > mGameListViews.size(); i++)
// mGameListViews[i].get()->finishAnimation(0);
for (auto it = mGameListViews.begin(); it != mGameListViews.end(); it++) {
std::string teststring = it->second->getCursor()->getName();
std::string teststring2;
}
mWindow->pushGui(new GuiMenu(mWindow));
return true;

View file

@ -327,8 +327,8 @@ void InputManager::loadDefaultKBConfig()
cfg->mapInput("b", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_BACKSPACE, 1, true));
cfg->mapInput("x", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_DELETE, 1, true));
cfg->mapInput("y", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_INSERT, 1, true));
cfg->mapInput("start", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F1, 1, true));
cfg->mapInput("select", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F2, 1, true));
cfg->mapInput("start", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_ESCAPE, 1, true));
cfg->mapInput("select", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F1, 1, true));
cfg->mapInput("leftshoulder", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PAGEUP, 1, true));
cfg->mapInput("rightshoulder", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PAGEDOWN, 1, true));

View file

@ -137,6 +137,7 @@ void Settings::setDefaults()
// mBoolMap["ScraperGenerateMiximages"] = false;
// mBoolMap["ScraperGenerateThumbnails"] = false;
mBoolMap["ScraperInteractive"] = true;
mBoolMap["ScraperSemiautomatic"] = true;
mBoolMap["ScraperOverwriteData"] = false;
mBoolMap["ScrapeMetadata"] = true;
mBoolMap["ScrapeGameNames"] = true;