Major update to scraper including support for new media handling logic, ability to download more media file types (screenshot, cover, marquee, 3D box) and an improved scraper GUI. As well a rewrite of the navigation sound code.

This commit is contained in:
Leon Styhre 2020-06-06 13:10:33 +02:00
parent d85ad49523
commit 90735d44e3
44 changed files with 1439 additions and 749 deletions

View file

@ -31,8 +31,8 @@ set(ES_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSlideshowScreensaverOptions.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.h
@ -89,8 +89,8 @@ set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSlideshowScreensaverOptions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.cpp

View file

@ -354,7 +354,6 @@ void CollectionSystemManager::updateCollectionSystem(FileData* file, CollectionS
}
else {
ViewController::get()->onFileChanged(rootFolder, FILE_SORTED);
std::string teststring1 = rootFolder->getPath();
// If it's a custom collection and the collections
// are grouped, update the parent instead.
if (sysData.decl.isCustom &&

View file

@ -39,8 +39,18 @@ FileData::FileData(
metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA)
{
// Metadata needs at least a name field (since that's what getName() will return).
if (metadata.get("name").empty())
metadata.set("name", getDisplayName());
if (metadata.get("name").empty()) {
if ((system->hasPlatformId(PlatformIds::ARCADE) ||
system->hasPlatformId(PlatformIds::NEOGEO)) &&
metadata.getType() != FOLDER_METADATA) {
// If it's a MAME or Neo Geo game, expand the game name accordingly.
metadata.set("name",
MameNames::getInstance()->getCleanName(getCleanName()));
}
else {
metadata.set("name", getDisplayName());
}
}
mSystemName = system->getName();
metadata.resetChangedFlag();
}
@ -88,7 +98,7 @@ const bool FileData::getFavorite()
return false;
}
const std::string FileData::getMediaDirectory() const
const std::string FileData::getMediaDirectory()
{
std::string mediaDirSetting = Settings::getInstance()->getString("MediaDirectory");
std::string mediaDirPath = "";
@ -111,12 +121,13 @@ const std::string FileData::getMediaDirectory() const
return mediaDirPath;
}
const std::string FileData::getThumbnailPath() const
const std::string FileData::getMediafilePath(std::string subdirectory, std::string mediatype) const
{
const char* extList[2] = { ".png", ".jpg" };
std::string tempPath = getMediaDirectory() + mSystemName + "/thumbnails/" + getDisplayName();
// Look for media in the media directory.
// Look for an image file in the media directory.
std::string tempPath = getMediaDirectory() + mSystemName + "/" +
subdirectory + "/" + getDisplayName();
for (int i = 0; i < 2; i++) {
std::string mediaPath = tempPath + extList[i];
if (Utils::FileSystem::exists(mediaPath))
@ -125,11 +136,10 @@ const std::string FileData::getThumbnailPath() const
// No media found in the media directory, so look
// for local art as well (if configured to do so).
if (Settings::getInstance()->getBool("LocalArt"))
{
if (Settings::getInstance()->getBool("LocalArt")) {
for (int i = 0; i < 2; i++) {
std::string localMediaPath = mEnvData->mStartPath + "/images/" +
getDisplayName() + "-thumbnail" + extList[i];
getDisplayName() + "-" + mediatype + extList[i];
if (Utils::FileSystem::exists(localMediaPath))
return localMediaPath;
}
@ -138,6 +148,52 @@ const std::string FileData::getThumbnailPath() const
return "";
}
const std::string FileData::getImagePath() const
{
// Look for a mix image (a combination of screenshot, 2D/3D box and marquee).
std::string image = getMediafilePath("miximages", "miximage");
if (image != "")
return image;
// If no mix image was found, try screenshot instead.
image = getMediafilePath("screenshots", "screenshot");
if (image != "")
return image;
// If no screenshot was found either, try cover.
return getMediafilePath("covers", "cover");
}
const std::string FileData::get3DBoxPath() const
{
return getMediafilePath("3dboxes", "3dbox");
}
const std::string FileData::getCoverPath() const
{
return getMediafilePath("covers", "cover");
}
const std::string FileData::getMarqueePath() const
{
return getMediafilePath("marquees", "marquee");
}
const std::string FileData::getMiximagePath() const
{
return getMediafilePath("miximages", "miximage");
}
const std::string FileData::getScreenshotPath() const
{
return getMediafilePath("screenshots", "screenshot");
}
const std::string FileData::getThumbnailPath() const
{
return getMediafilePath("thumbnails", "thumbnail");
}
const std::string FileData::getVideoPath() const
{
const char* extList[5] = { ".avi", ".mkv", ".mov", ".mp4", ".wmv" };
@ -155,7 +211,7 @@ const std::string FileData::getVideoPath() const
if (Settings::getInstance()->getBool("LocalArt"))
{
for (int i = 0; i < 5; i++) {
std::string localMediaPath = mEnvData->mStartPath + "/images/" + getDisplayName() +
std::string localMediaPath = mEnvData->mStartPath + "/videos/" + getDisplayName() +
"-video" + extList[i];
if (Utils::FileSystem::exists(localMediaPath))
return localMediaPath;
@ -165,68 +221,6 @@ const std::string FileData::getVideoPath() const
return "";
}
const std::string FileData::getMarqueePath() const
{
const char* extList[2] = { ".png", ".jpg" };
std::string tempPath = getMediaDirectory() + mSystemName + "/marquees/" + getDisplayName();
// Look for media in the media directory.
for (int i = 0; i < 2; i++) {
std::string mediaPath = tempPath + extList[i];
if (Utils::FileSystem::exists(mediaPath))
return mediaPath;
}
// No media found in the media directory, so look
// for local art as well (if configured to do so).
if (Settings::getInstance()->getBool("LocalArt"))
{
for (int i = 0; i < 2; i++) {
std::string localMediaPath = mEnvData->mStartPath + "/images/" + getDisplayName() +
"-marquee" + extList[i];
if (Utils::FileSystem::exists(localMediaPath))
return localMediaPath;
}
}
return "";
}
const std::string FileData::getImagePath() const
{
const char* extList[2] = { ".png", ".jpg" };
// Look for mix image (a combination of screenshot, 3D box and marquee) in the media directory.
std::string tempPath = getMediaDirectory() + mSystemName + "/miximages/" + getDisplayName();
for (int i = 0; i < 2; i++) {
std::string mediaPath = tempPath + extList[i];
if (Utils::FileSystem::exists(mediaPath))
return mediaPath;
}
// If no mix image exists, try normal screenshot.
tempPath = getMediaDirectory() + mSystemName + "/screenshots/" + getDisplayName();
for (int i = 0; i < 2; i++) {
std::string mediaPath = tempPath + extList[i];
if (Utils::FileSystem::exists(mediaPath))
return mediaPath;
}
// No media found in the media directory, so look
// for local art as well (if configured to do so).
if (Settings::getInstance()->getBool("LocalArt")) {
for (int i = 0; i < 2; i++) {
std::string localMediaPath = mEnvData->mStartPath + "/images/" +
getDisplayName() + "-image" + extList[i];
if (Utils::FileSystem::exists(localMediaPath))
return localMediaPath;
}
}
return "";
}
const std::vector<FileData*>& FileData::getChildrenListToDisplay()
{
@ -312,7 +306,6 @@ void FileData::removeChild(FileData* file)
// File somehow wasn't in our children.
assert(false);
}
void FileData::sort(ComparisonFunction& comparator, bool ascending)

View file

@ -57,11 +57,17 @@ public:
inline const std::vector<FileData*>& getChildren() const { return mChildren; }
inline SystemData* getSystem() const { return mSystem; }
inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; }
virtual const std::string getMediaDirectory() const;
static const std::string getMediaDirectory();
virtual const std::string getMediafilePath(
std::string subdirectory, std::string mediatype) const;
virtual const std::string getImagePath() const;
virtual const std::string get3DBoxPath() const;
virtual const std::string getCoverPath() const;
virtual const std::string getMarqueePath() const;
virtual const std::string getMiximagePath() const;
virtual const std::string getScreenshotPath() const;
virtual const std::string getThumbnailPath() const;
virtual const std::string getVideoPath() const;
virtual const std::string getMarqueePath() const;
virtual const std::string getImagePath() const;
const std::vector<FileData*>& getChildrenListToDisplay();
std::vector<FileData*> getFilesRecursive(unsigned int typeMask,

View file

@ -12,38 +12,40 @@
#include <pugixml/src/pugixml.hpp>
MetaDataDecl gameDecls[] = {
// key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd
{"name", MD_STRING, "", false, "name", "enter game name"},
{"sortname", MD_STRING, "", false, "sortname", "enter game sort name"},
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description"},
{"rating", MD_RATING, "0.000000", false, "rating", "enter rating"},
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"},
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer"},
{"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher"},
{"genre", MD_STRING, "unknown", false, "genre", "enter game genre"},
{"players", MD_INT, "1", false, "players", "enter number of players"},
{"favorite", MD_BOOL, "false", false, "favorite", "enter favorite off/on"},
{"completed", MD_BOOL, "false", false, "completed", "enter completed off/on"},
{"hidden", MD_BOOL, "false", false, "hidden", "enter hidden off/on"},
{"kidgame", MD_BOOL, "false", false, "kidgame", "enter kidgame off/on"},
{"launchstring", MD_LAUNCHSTRING, "", false, "launch string", "enter game launch string (emulator override)"},
{"playcount", MD_INT, "0", false, "play count", "enter number of times played"},
{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date"}
// key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd, shouldScrape
{"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},
{"players", MD_INT, "unknown", false, "players", "enter number of players", true},
{"favorite", MD_BOOL, "false", false, "favorite", "enter favorite off/on", false},
{"completed", MD_BOOL, "false", false, "completed", "enter completed off/on", false},
{"broken", MD_BOOL, "false", false, "broken/not working", "enter broken off/on", false},
{"hidden", MD_BOOL, "false", false, "hidden", "enter hidden off/on", false},
{"kidgame", MD_BOOL, "false", false, "kidgame", "enter kidgame off/on", false},
{"launchstring", MD_LAUNCHSTRING, "", false, "launch string", "enter game launch string "
"(emulator override)", false},
{"playcount", MD_INT, "0", false, "play count", "enter number of times played", false},
{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false}
};
const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls +
sizeof(gameDecls) / sizeof(gameDecls[0]));
MetaDataDecl folderDecls[] = {
{"name", MD_STRING, "", false, "name", "enter game name"},
{"sortname", MD_STRING, "", false, "sortname", "enter game sort name"},
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description"},
{"rating", MD_RATING, "0.000000", false, "rating", "enter rating"},
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"},
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer"},
{"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher"},
{"genre", MD_STRING, "unknown", false, "genre", "enter game genre"},
{"players", MD_INT, "1", false, "players", "enter number of players"}
{"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},
{"players", MD_INT, "unknown", false, "players", "enter number of players", true}
};
const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls +
sizeof(folderDecls) / sizeof(folderDecls[0]));
@ -78,7 +80,7 @@ MetaDataList MetaDataList::createFromXML(MetaDataListType type,
for (auto iter = mdd.cbegin(); iter != mdd.cend(); iter++) {
pugi::xml_node md = node.child(iter->key.c_str());
if (md) {
if (md && !md.text().empty()) {
// If it's a path, resolve relative paths.
std::string value = md.text().get();
if (iter->type == MD_PATH)

View file

@ -15,19 +15,19 @@
namespace pugi { class xml_node; }
enum MetaDataType {
// Generic types
// Generic types.
MD_STRING,
MD_INT,
MD_FLOAT,
MD_BOOL,
// Specialized types
// Specialized types.
MD_MULTILINE_STRING,
MD_LAUNCHSTRING,
MD_PATH,
MD_RATING,
MD_DATE,
MD_TIME // Used for lastplayed
MD_TIME // Used for lastplayed.
};
struct MetaDataDecl {
@ -40,6 +40,8 @@ struct MetaDataDecl {
std::string displayName;
// Phrase displayed in editors when prompted to enter value (currently only for strings).
std::string displayPrompt;
// If set to false, the scraper will not overwrite this metadata.
bool shouldScrape;
};
enum MetaDataListType {

View file

@ -38,7 +38,8 @@ SystemData::SystemData(
mEnvData(envData),
mThemeFolder(themeFolder),
mIsCollectionSystem(CollectionSystem),
mIsGameSystem(true)
mIsGameSystem(true),
mScrapeFlag(false)
{
mFilterIndex = new FileFilterIndex();

View file

@ -62,6 +62,8 @@ public:
unsigned int getGameCount() const;
unsigned int getDisplayedGameCount() const;
bool getScrapeFlag() { return mScrapeFlag; };
void setScrapeFlag(bool scrapeflag) { mScrapeFlag = scrapeflag; }
static void deleteSystems();
// Load the system config file at getConfigPath().
@ -100,6 +102,7 @@ public:
private:
bool mIsCollectionSystem;
bool mIsGameSystem;
bool mScrapeFlag; // Only used by scraper GUI to remember which systems to scrape.
std::string mName;
std::string mFullName;
SystemEnvironmentData* mEnvData;

View file

@ -4,7 +4,8 @@
// 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.
// to resolve scraping conflicts when run from GuiScraperMenu.
// The function to properly save scraped metadata is located here too.
//
// This component is called from GuiGameScraper for single-game scraping and
// from GuiScraperMulti for multi-game scraping.
@ -22,6 +23,9 @@
#include "guis/GuiTextEditPopup.h"
#include "resources/Font.h"
#include "utils/StringUtil.h"
#include "PlatformId.h"
#include "MameNames.h"
#include "SystemData.h"
#include "FileData.h"
#include "Log.h"
#include "Window.h"
@ -69,8 +73,10 @@ ScraperSearchComponent::ScraperSearchComponent(
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));
if (Settings::getInstance()->getBool("ScrapeRatings") &&
Settings::getInstance()->getString("Scraper") != "TheGamesDB")
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>
@ -144,7 +150,7 @@ void ScraperSearchComponent::onSizeChanged()
mGrid.getColWidth(2), mResultDesc->getFont()->getHeight() * 3);
else
mDescContainer->setSize(mGrid.getColWidth(3)*boxartCellScale,
mResultDesc->getFont()->getHeight() * 8);
mResultDesc->getFont()->getHeight() * 7);
// Make description text wrap at edge of container.
mResultDesc->setSize(mDescContainer->getSize().x(), 0);
@ -184,9 +190,12 @@ void ScraperSearchComponent::resizeMetadata()
mMD_Grid->setColWidthPerc(0, maxLblWidth / mMD_Grid->getSize().x());
// Rating is manually sized.
mMD_Rating->setSize(mMD_Grid->getColWidth(1), fontLbl->getHeight() * 0.65f);
mMD_Grid->onSizeChanged();
if (Settings::getInstance()->getBool("ScrapeRatings") &&
Settings::getInstance()->getString("Scraper") != "TheGamesDB") {
// Rating is manually sized.
mMD_Rating->setSize(mMD_Grid->getColWidth(1), fontLbl->getHeight() * 0.65f);
mMD_Grid->onSizeChanged();
}
// Make result font follow label font.
mResultDesc->setFont(Font::get(fontHeight, FONT_PATH_REGULAR));
@ -239,6 +248,7 @@ void ScraperSearchComponent::search(const ScraperSearchParams& params)
mResultList->clear();
mScraperResults.clear();
mMDRetrieveURLsHandle.reset();
mThumbnailReq.reset();
mMDResolveHandle.reset();
updateInfoPane();
@ -252,6 +262,7 @@ void ScraperSearchComponent::stop()
mThumbnailReq.reset();
mSearchHandle.reset();
mMDResolveHandle.reset();
mMDRetrieveURLsHandle.reset();
mBlockAccept = false;
}
@ -297,15 +308,18 @@ void ScraperSearchComponent::onSearchDone(const std::vector<ScraperSearchResult>
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 (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) {
if (mScraperResults.size() == 0)
mSkipCallback();
else
returnResult(mScraperResults.front());
}
else if (mSearchType == ALWAYS_ACCEPT_MATCHING_CRC) {
// TODO
if (mScraperResults.size() == 0 || (mScraperResults.size() > 0 &&
mScraperResults.front().ThumbnailImageUrl == "")) {
if (mScraperResults.size() == 0)
mSkipCallback();
else
returnResult(mScraperResults.front());
}
}
}
void ScraperSearchComponent::onSearchError(const std::string& error)
@ -338,14 +352,35 @@ void ScraperSearchComponent::updateInfoPane()
mDescContainer->reset();
mResultThumbnail->setImage("");
const std::string& thumb = res.thumbnailUrl.empty() ? res.imageUrl : res.thumbnailUrl;
if (!thumb.empty())
mThumbnailReq = std::unique_ptr<HttpReq>(new HttpReq(thumb));
else
mThumbnailReq.reset();
const std::string& thumb = res.screenshotUrl.empty() ? res.coverUrl : res.screenshotUrl;
mScraperResults[i].ThumbnailImageUrl = thumb;
// Cache the thumbnail image in mScraperResults so that we don't need to download
// it every time the list is scrolled back and forth.
if (mScraperResults[i].ThumbnailImageData.size() > 0) {
std::string content = mScraperResults[i].ThumbnailImageData;
mResultThumbnail->setImage(content.data(), content.length());
mGrid.onSizeChanged(); // A hack to fix the thumbnail position since its size changed.
}
// If it's not cached in mScraperResults it should mean that it's the first time
// we access the entry, and therefore we need to download the image.
else {
if (!thumb.empty()) {
// Make sure we don't attempt to download the same thumbnail twice.
if (!mThumbnailReq && mScraperResults[i].thumbnailDownloadStatus != IN_PROGRESS) {
mScraperResults[i].thumbnailDownloadStatus = IN_PROGRESS;
mThumbnailReq = std::unique_ptr<HttpReq>(new HttpReq(thumb));
}
}
else {
mThumbnailReq.reset();
}
}
// Metadata.
mMD_Rating->setValue(Utils::String::toUpper(res.mdl.get("rating")));
if (Settings::getInstance()->getBool("ScrapeRatings") &&
Settings::getInstance()->getString("Scraper") != "TheGamesDB")
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")));
mMD_Publisher->setText(Utils::String::toUpper(res.mdl.get("publisher")));
@ -359,7 +394,9 @@ void ScraperSearchComponent::updateInfoPane()
mResultThumbnail->setImage("");
// Metadata.
mMD_Rating->setValue("");
if (Settings::getInstance()->getBool("ScrapeRatings") &&
Settings::getInstance()->getString("Scraper") != "TheGamesDB")
mMD_Rating->setValue("");
mMD_ReleaseDate->setValue("");
mMD_Developer->setText("");
mMD_Publisher->setText("");
@ -397,7 +434,8 @@ void ScraperSearchComponent::returnResult(ScraperSearchResult result)
mBlockAccept = true;
// Resolve metadata image before returning.
if (!result.imageUrl.empty()) {
if (result.mediaFilesDownloadStatus != COMPLETED) {
result.mediaFilesDownloadStatus = IN_PROGRESS;
mMDResolveHandle = resolveMetaDataAssets(result, mLastSearch);
return;
}
@ -417,22 +455,69 @@ void ScraperSearchComponent::update(int deltaTime)
if (mSearchHandle && mSearchHandle->status() != ASYNC_IN_PROGRESS) {
auto status = mSearchHandle->status();
auto results = mSearchHandle->getResults();
mScraperResults = mSearchHandle->getResults();
auto statusString = mSearchHandle->getStatusString();
// We reset here because onSearchDone in auto mode can call mSkipCallback() which
// can call another search() which will set our mSearchHandle to something important.
mSearchHandle.reset();
if (status == ASYNC_DONE)
onSearchDone(results);
else if (status == ASYNC_ERROR)
if (status == ASYNC_DONE && mScraperResults.size() == 0)
onSearchDone(mScraperResults);
if (status == ASYNC_DONE && mScraperResults.size() > 0) {
if (mScraperResults.front().mediaURLFetch == COMPLETED) {
onSearchDone(mScraperResults);
}
else {
std::string gameIDs;
for (auto it = mScraperResults.cbegin(); it != mScraperResults.cend(); it++)
gameIDs += it->gameID + ',';
// Remove the last comma
gameIDs.pop_back();
mMDRetrieveURLsHandle = startMediaURLsFetch(gameIDs);
}
}
else if (status == ASYNC_ERROR) {
onSearchError(statusString);
}
}
if (mMDRetrieveURLsHandle && mMDRetrieveURLsHandle->status() != ASYNC_IN_PROGRESS) {
if (mMDRetrieveURLsHandle->status() == ASYNC_DONE) {
auto status_media = mMDRetrieveURLsHandle->status();
auto results_media = mMDRetrieveURLsHandle->getResults();
auto statusString_media = mMDRetrieveURLsHandle->getStatusString();
auto results_scrape = mScraperResults;
mMDRetrieveURLsHandle.reset();
mScraperResults.clear();
// Combine the intial scrape results with the media URL results.
for (auto it = results_media.cbegin(); it != results_media.cend(); it++) {
for (unsigned int i = 0; i < results_scrape.size(); i++) {
if (results_scrape[i].gameID == it->gameID) {
results_scrape[i].box3dUrl = it->box3dUrl;
results_scrape[i].coverUrl = it->coverUrl;
results_scrape[i].marqueeUrl = it->marqueeUrl;
results_scrape[i].screenshotUrl = it->screenshotUrl;
results_scrape[i].scraperRequestAllowance = it->scraperRequestAllowance;
results_scrape[i].mediaURLFetch = COMPLETED;
}
}
}
onSearchDone(results_scrape);
}
else if (mMDRetrieveURLsHandle->status() == ASYNC_ERROR) {
onSearchError(mMDRetrieveURLsHandle->getStatusString());
mMDRetrieveURLsHandle.reset();
}
}
if (mMDResolveHandle && mMDResolveHandle->status() != ASYNC_IN_PROGRESS) {
if (mMDResolveHandle->status() == ASYNC_DONE) {
ScraperSearchResult result = mMDResolveHandle->getResult();
result.mediaFilesDownloadStatus = COMPLETED;
mMDResolveHandle.reset();
// This might end in us being deleted, depending on mAcceptCallback -
// so make sure this is the last thing we do in update().
@ -448,6 +533,15 @@ void ScraperSearchComponent::update(int deltaTime)
void ScraperSearchComponent::updateThumbnail()
{
if (mThumbnailReq && mThumbnailReq->status() == HttpReq::REQ_SUCCESS) {
// Save thumbnail to mScraperResults cache and set the flag that the
// thumbnail download has been completed for this game.
for (auto i = 0; i < mScraperResults.size(); i++) {
if (mScraperResults[i].thumbnailDownloadStatus == IN_PROGRESS) {
mScraperResults[i].ThumbnailImageData = mThumbnailReq->getContent();
mScraperResults[i].thumbnailDownloadStatus = COMPLETED;
}
}
// Activate the thumbnail in the GUI.
std::string content = mThumbnailReq->getContent();
mResultThumbnail->setImage(content.data(), content.length());
mGrid.onSizeChanged(); // A hack to fix the thumbnail position since its size changed.
@ -458,6 +552,21 @@ void ScraperSearchComponent::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 &&
mScraperResults.front().thumbnailDownloadStatus == COMPLETED) {
if (mScraperResults.size() == 0)
mSkipCallback();
else
returnResult(mScraperResults.front());
}
}
void ScraperSearchComponent::openInputScreen(ScraperSearchParams& params)
@ -468,10 +577,78 @@ void ScraperSearchComponent::openInputScreen(ScraperSearchParams& params)
};
stop();
mWindow->pushGui(new GuiTextEditPopup(mWindow, "SEARCH FOR",
// 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"));
if (params.system->hasPlatformId(PlatformIds::ARCADE) ||
params.system->hasPlatformId(PlatformIds::NEOGEO)) {
mWindow->pushGui(new GuiTextEditPopup(mWindow, "SEARCH FOR",
// Initial value is last search if there was one, otherwise the clean path name.
// If it's a MAME or Neo Geo game, expand the game name accordingly.
params.nameOverride.empty() ?
MameNames::getInstance()->getCleanName(params.game->getCleanName()) :
params.nameOverride,
searchForFunc, false, "SEARCH"));
}
else {
mWindow->pushGui(new GuiTextEditPopup(mWindow, "SEARCH FOR",
// 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"));
}
}
bool ScraperSearchComponent::saveMetadata(
const ScraperSearchResult& result, MetaDataList& metadata)
{
bool mMetadataUpdated = false;
std::vector<MetaDataDecl> mMetaDataDecl = metadata.getMDD();
for (unsigned int i = 0; i < mMetaDataDecl.size(); i++) {
// Skip elements that are tagged not to be scraped.
if (!mMetaDataDecl.at(i).shouldScrape)
continue;
const std::string& key = mMetaDataDecl.at(i).key;
// Skip element if the setting to not scrape metadata has been set,
// unless its type is rating or name.
if (!Settings::getInstance()->getBool("ScrapeMetadata") &&
(key != "rating" && key != "name"))
continue;
// Skip saving of rating if the corresponding option has been set to false.
if (key == "rating" && !Settings::getInstance()->getBool("ScrapeRatings"))
continue;
// Skip saving of game name if the corresponding option has been set to false.
if (key == "name" && !Settings::getInstance()->getBool("ScrapeGameNames"))
continue;
// Skip elements that are empty.
if (result.mdl.get(key) == "")
continue;
// Skip elements that are the same as the default metadata value.
if (result.mdl.get(key) == mMetaDataDecl.at(i).defaultValue)
continue;
// Skip elements that are identical to the existing value.
if (result.mdl.get(key) == metadata.get(key))
continue;
// 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));
mMetadataUpdated = true;
}
// Else only update the value if it is set to the default metadata value.
else if (metadata.get(key) == mMetaDataDecl.at(i).defaultValue) {
metadata.set(key, result.mdl.get(key));
mMetadataUpdated = true;
}
}
return mMetadataUpdated;
}
std::vector<HelpPrompt> ScraperSearchComponent::getHelpPrompts()

View file

@ -4,7 +4,8 @@
// 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.
// to resolve scraping conflicts when run from GuiScraperMenu.
// The function to properly save scraped metadata is located here too.
//
// This component is called from GuiGameScraper for single-game scraping and
// from GuiScraperMulti for multi-game scraping.
@ -41,6 +42,7 @@ public:
void openInputScreen(ScraperSearchParams& from);
void stop();
inline SearchType getSearchType() const { return mSearchType; }
static bool saveMetadata(const ScraperSearchResult& result, MetaDataList& metadata);
// Metadata assets will be resolved before calling the accept callback
// (e.g. result.mdl's "image" is automatically downloaded and properly set).
@ -71,6 +73,10 @@ private:
int getSelectedIndex();
// For TheGamesDB, retrieve URLs for the additional metadata assets
// that need to be downloaded.
void retrieveMediaURLs(ScraperSearchResult result);
// Resolve any metadata assets that need to be downloaded and return.
void returnResult(ScraperSearchResult result);
@ -111,6 +117,7 @@ private:
bool mBlockAccept;
std::unique_ptr<ScraperSearchHandle> mSearchHandle;
std::unique_ptr<ScraperSearchHandle> mMDRetrieveURLsHandle;
std::unique_ptr<MDResolveHandle> mMDResolveHandle;
std::vector<ScraperSearchResult> mScraperResults;
std::unique_ptr<HttpReq> mThumbnailReq;

View file

@ -80,7 +80,8 @@ public:
inline void setLineSpacing(float lineSpacing) { mLineSpacing = lineSpacing; }
protected:
virtual void onScroll() { navigationsounds.playThemeNavigationSound(SCROLLSOUND); }
virtual void onScroll() {
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); }
virtual void onCursorChanged(const CursorState& state);
private:
@ -389,7 +390,7 @@ void TextListComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme, c
setFont(Font::getFromTheme(elem, properties, mFont));
const float selectorHeight = Math::max(mFont->getHeight(1.0), (float)mFont->getSize()) * mLineSpacing;
setSelectorHeight(selectorHeight);
if(properties & ALIGNMENT)
{
if(elem->has("alignment"))

View file

@ -103,7 +103,7 @@ GuiGamelistOptions::GuiGamelistOptions(
row.addElement(mJumpToLetterList, false);
row.input_handler = [&](InputConfig* config, Input input) {
if (config->isMappedTo("a", input) && input.value) {
navigationsounds.playThemeNavigationSound(SCROLLSOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
if (mJumpToLetterList->getSelected() == FAVORITE_CHAR)
jumpToFirstRow();
else
@ -196,7 +196,7 @@ GuiGamelistOptions::~GuiGamelistOptions()
// If a new sorting type was selected, then sort and update mSortTypeString for the system.
if ((*mListSort->getSelected()).description != root->getSortTypeString()) {
navigationsounds.playThemeNavigationSound(SCROLLSOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
// This will also recursively sort children.
root->sort(*mListSort->getSelected(), mFavoritesSorting);

View file

@ -14,7 +14,7 @@
#include "guis/GuiDetectDevice.h"
#include "guis/GuiGeneralScreensaverOptions.h"
#include "guis/GuiMsgBox.h"
#include "guis/GuiScraperStart.h"
#include "guis/GuiScraperMenu.h"
#include "guis/GuiSettings.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
@ -36,6 +36,9 @@ GuiMenu::GuiMenu(
{
bool isFullUI = UIModeController::getInstance()->isUIModeFull();
if (isFullUI)
addEntry("SCRAPER", 0x777777FF, true, [this] { openScraperSettings(); });
if (isFullUI)
addEntry("UI SETTINGS", 0x777777FF, true, [this] { openUISettings(); });
@ -45,9 +48,6 @@ GuiMenu::GuiMenu(
addEntry("GAME COLLECTION SETTINGS", 0x777777FF, true, [this] {
openCollectionSystemSettings(); });
if (isFullUI)
addEntry("SCRAPER", 0x777777FF, true, [this] { openScraperSettings(); });
if (isFullUI)
addEntry("OTHER SETTINGS", 0x777777FF, true, [this] { openOtherSettings(); });
@ -65,44 +65,8 @@ GuiMenu::GuiMenu(
void GuiMenu::openScraperSettings()
{
auto s = new GuiSettings(mWindow, "SCRAPER");
// Scrape from.
auto scraper_list = std::make_shared< OptionListComponent< std::string >
>(mWindow, "SCRAPE FROM", false);
std::vector<std::string> scrapers = getScraperList();
// Select either the first entry or the one read from the settings,
// just in case the scraper from settings has vanished.
for (auto it = scrapers.cbegin(); it != scrapers.cend(); it++)
scraper_list->add(*it, *it, *it == Settings::getInstance()->getString("Scraper"));
s->addWithLabel("SCRAPE FROM", scraper_list);
s->addSaveFunc([scraper_list] { Settings::getInstance()->setString("Scraper",
scraper_list->getSelected()); });
// Scrape ratings.
auto scrape_ratings = std::make_shared<SwitchComponent>(mWindow);
scrape_ratings->setState(Settings::getInstance()->getBool("ScrapeRatings"));
s->addWithLabel("SCRAPE RATINGS", scrape_ratings);
s->addSaveFunc([scrape_ratings] { Settings::getInstance()->setBool("ScrapeRatings",
scrape_ratings->getState()); });
// Scrape now.
ComponentListRow row;
auto openScrapeNow = [this] { mWindow->pushGui(new GuiScraperStart(mWindow)); };
std::function<void()> openAndSave = openScrapeNow;
openAndSave = [s, openAndSave] { s->save(); openAndSave(); };
row.makeAcceptInputHandler(openAndSave);
auto scrape_now = std::make_shared<TextComponent>
(mWindow, "SCRAPE NOW", Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
auto bracket = makeArrow(mWindow);
row.addElement(scrape_now, true);
row.addElement(bracket, false);
s->addRow(row);
mWindow->pushGui(s);
// Open the scrape menu.
mWindow->pushGui(new GuiScraperMenu(mWindow));
}
void GuiMenu::openSoundSettings()
@ -174,28 +138,6 @@ void GuiMenu::openSoundSettings()
});
#endif
// Video audio.
auto video_audio = std::make_shared<SwitchComponent>(mWindow);
video_audio->setState(Settings::getInstance()->getBool("VideoAudio"));
s->addWithLabel("ENABLE AUDIO FOR VIDEO FILES", video_audio);
s->addSaveFunc([video_audio] { Settings::getInstance()->setBool("VideoAudio",
video_audio->getState()); });
// Navigation sounds.
auto sounds_enabled = std::make_shared<SwitchComponent>(mWindow);
sounds_enabled->setState(Settings::getInstance()->getBool("EnableSounds"));
s->addWithLabel("ENABLE NAVIGATION SOUNDS", sounds_enabled);
s->addSaveFunc([sounds_enabled] {
if (sounds_enabled->getState()
&& !Settings::getInstance()->getBool("EnableSounds")
&& PowerSaver::getMode() == PowerSaver::INSTANT)
{
Settings::getInstance()->setString("PowerSaverMode", "default");
PowerSaver::init();
}
Settings::getInstance()->setBool("EnableSounds", sounds_enabled->getState());
});
#ifdef _RPI_
// OMX player Audio Device
auto omx_audio_dev = std::make_shared< OptionListComponent<std::string>
@ -221,6 +163,27 @@ void GuiMenu::openSoundSettings()
Settings::getInstance()->setString("OMXAudioDev", omx_audio_dev->getSelected());
});
#endif
// Video audio.
auto video_audio = std::make_shared<SwitchComponent>(mWindow);
video_audio->setState(Settings::getInstance()->getBool("VideoAudio"));
s->addWithLabel("ENABLE AUDIO FOR VIDEO FILES", video_audio);
s->addSaveFunc([video_audio] { Settings::getInstance()->setBool("VideoAudio",
video_audio->getState()); });
// Navigation sounds.
auto sounds_enabled = std::make_shared<SwitchComponent>(mWindow);
sounds_enabled->setState(Settings::getInstance()->getBool("EnableSounds"));
s->addWithLabel("ENABLE NAVIGATION SOUNDS", sounds_enabled);
s->addSaveFunc([sounds_enabled] {
if (sounds_enabled->getState() &&
!Settings::getInstance()->getBool("EnableSounds") &&
PowerSaver::getMode() == PowerSaver::INSTANT) {
Settings::getInstance()->setString("PowerSaverMode", "default");
PowerSaver::init();
}
Settings::getInstance()->setBool("EnableSounds", sounds_enabled->getState());
});
}
mWindow->pushGui(s);
@ -281,9 +244,9 @@ void GuiMenu::openUISettings()
getString("TransitionStyle") == *it);
s->addWithLabel("TRANSITION STYLE", transition_style);
s->addSaveFunc([transition_style] {
if (Settings::getInstance()->getString("TransitionStyle") == "instant"
&& transition_style->getSelected() != "instant"
&& PowerSaver::getMode() == PowerSaver::INSTANT) {
if (Settings::getInstance()->getString("TransitionStyle") == "instant" &&
transition_style->getSelected() != "instant" &&
PowerSaver::getMode() == PowerSaver::INSTANT) {
Settings::getInstance()->setString("PowerSaverMode", "default");
PowerSaver::init();
}
@ -315,8 +278,7 @@ void GuiMenu::openUISettings()
Settings::getInstance()->setString("ThemeSet", theme_set->getSelected());
if (needReload)
{
if (needReload) {
Scripting::fireEvent("theme-changed", theme_set->getSelected(), oldTheme);
CollectionSystemManager::get()->updateSystemsList();
ViewController::get()->goToStart();
@ -404,9 +366,9 @@ void GuiMenu::openUISettings()
move_carousel->setState(Settings::getInstance()->getBool("MoveCarousel"));
s->addWithLabel("CAROUSEL TRANSITIONS", move_carousel);
s->addSaveFunc([move_carousel] {
if (move_carousel->getState()
&& !Settings::getInstance()->getBool("MoveCarousel")
&& PowerSaver::getMode() == PowerSaver::INSTANT) {
if (move_carousel->getState() &&
!Settings::getInstance()->getBool("MoveCarousel") &&
PowerSaver::getMode() == PowerSaver::INSTANT) {
Settings::getInstance()->setString("PowerSaverMode", "default");
PowerSaver::init();
}
@ -460,8 +422,8 @@ void GuiMenu::openOtherSettings()
fullscreen_mode->add(*it, *it, Settings::getInstance()->getString("FullscreenMode") == *it);
s->addWithLabel("FULLSCREEN MODE (REQUIRES RESTART)", fullscreen_mode);
s->addSaveFunc([fullscreen_mode] {
if (Settings::getInstance()->getString("FullscreenMode") == "normal"
&& fullscreen_mode->getSelected() != "normal") {
if (Settings::getInstance()->getString("FullscreenMode") == "normal" &&
fullscreen_mode->getSelected() != "normal") {
Settings::getInstance()->setString("PowerSaverMode", "default");
PowerSaver::init();
}
@ -542,7 +504,7 @@ void GuiMenu::openOtherSettings()
auto local_art = std::make_shared<SwitchComponent>(mWindow);
local_art->setState(Settings::getInstance()->getBool("LocalArt"));
s->addWithLabel("SEARCH FOR GAME ART IN ROM DIRECTORIES", local_art);
s->addWithLabel("DISPLAY GAME ART FROM ROM DIRECTORIES", local_art);
s->addSaveFunc([local_art] { Settings::getInstance()->
setBool("LocalArt", local_art->getState()); });
@ -583,7 +545,7 @@ void GuiMenu::openConfigInput()
Window* window = mWindow;
window->pushGui(new GuiMsgBox(window, "ARE YOU SURE YOU WANT TO CONFIGURE INPUT?", "YES",
[window] {
window->pushGui(new GuiDetectDevice(window, false, nullptr));
window->pushGui(new GuiDetectDevice(window, false, nullptr));
}, "NO", nullptr)
);
}
@ -600,8 +562,8 @@ void GuiMenu::openQuitMenu()
row.makeAcceptInputHandler([window] {
window->pushGui(new GuiMsgBox(window, "REALLY QUIT?", "YES",
[] {
Scripting::fireEvent("quit");
quitES();
Scripting::fireEvent("quit");
quitES();
}, "NO", nullptr));
});
row.addElement(std::make_shared<TextComponent>(window, "QUIT EMULATIONSTATION",
@ -615,10 +577,10 @@ void GuiMenu::openQuitMenu()
row.makeAcceptInputHandler([window] {
window->pushGui(new GuiMsgBox(window, "REALLY REBOOT?", "YES",
[] {
Scripting::fireEvent("quit", "reboot");
Scripting::fireEvent("reboot");
if (quitES(QuitMode::REBOOT) != 0)
LOG(LogWarning) << "Reboot terminated with non-zero result!";
Scripting::fireEvent("quit", "reboot");
Scripting::fireEvent("reboot");
if (quitES(QuitMode::REBOOT) != 0)
LOG(LogWarning) << "Reboot terminated with non-zero result!";
}, "NO", nullptr));
});
row.addElement(std::make_shared<TextComponent>(window, "REBOOT SYSTEM",
@ -631,10 +593,10 @@ void GuiMenu::openQuitMenu()
row.makeAcceptInputHandler([window] {
window->pushGui(new GuiMsgBox(window, "REALLY POWER OFF?", "YES",
[] {
Scripting::fireEvent("quit", "poweroff");
Scripting::fireEvent("poweroff");
if (quitES(QuitMode::POWEROFF) != 0)
LOG(LogWarning) << "Power off terminated with non-zero result!";
Scripting::fireEvent("quit", "poweroff");
Scripting::fireEvent("poweroff");
if (quitES(QuitMode::POWEROFF) != 0)
LOG(LogWarning) << "Power off terminated with non-zero result!";
}, "NO", nullptr));
});
row.addElement(std::make_shared<TextComponent>(window, "POWER OFF SYSTEM",
@ -668,11 +630,8 @@ void GuiMenu::onSizeChanged()
mVersion.setPosition(0, mSize.y() - mVersion.getSize().y());
}
void GuiMenu::addEntry(
const char* name,
unsigned int color,
bool add_arrow,
const std::function<void()>& func)
void GuiMenu::addEntry(const char* name, unsigned int color,
bool add_arrow, const std::function<void()>& func)
{
std::shared_ptr<Font> font = Font::get(FONT_SIZE_MEDIUM);

View file

@ -46,7 +46,8 @@ GuiMetaDataEd::GuiMetaDataEd(
mMetaDataDecl(mdd),
mMetaData(md),
mSavedCallback(saveCallback),
mDeleteFunc(deleteFunc)
mDeleteFunc(deleteFunc),
mMetadataUpdated(false)
{
addChild(&mBackground);
addChild(&mGrid);
@ -82,7 +83,6 @@ GuiMetaDataEd::GuiMetaDataEd(
assert(ed);
ed->setValue(mMetaData->get(iter->key));
mEditors.push_back(ed);
continue;
}
@ -282,22 +282,41 @@ void GuiMetaDataEd::fetch()
void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
{
for (unsigned int i = 0; i < mEditors.size(); i++) {
if (mMetaDataDecl.at(i).isStatistic)
continue;
// Clone the mMetaData object.
MetaDataList* metadata = nullptr;
metadata = new MetaDataList(*mMetaData);
mMetadataUpdated = ScraperSearchComponent::saveMetadata(result, *metadata);
// Update the list with the scraped metadata values.
for (unsigned int i = 0; i < mEditors.size(); i++) {
const std::string& key = mMetaDataDecl.at(i).key;
mEditors.at(i)->setValue(result.mdl.get(key));
// if (mEditors.at(i)->getValue() != metadata->get(key)) {
// mEditors.at(i)->setOpacity(150);
// }
mEditors.at(i)->setValue(metadata->get(key));
}
delete metadata;
}
void GuiMetaDataEd::close(bool closeAllWindows)
{
// Find out if the user made any changes.
bool dirty = false;
bool dirty = mMetadataUpdated;
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()) {
std::string mMetaDataValue = mMetaData->get(key);
std::string mEditorsValue = mEditors.at(i)->getValue();
// Incredibly ugly workaround to avoid the "SAVE CHANGES?" window for games
// with mising metadata for rating and release date.
if (key == "rating" && (mMetaDataValue == "" || mMetaDataValue == "0.000000"))
mMetaDataValue = "0";
if (key == "releasedate" && (mMetaDataValue == "" || mMetaDataValue == "not-a-date-time"))
mMetaDataValue = "19700101T010000";
if (mMetaDataValue != mEditorsValue) {
dirty = true;
break;
}
@ -315,7 +334,6 @@ void GuiMetaDataEd::close(bool closeAllWindows)
};
}
if (dirty)
{
// Changes were made, ask if the user wants to save them.

View file

@ -58,6 +58,8 @@ private:
MetaDataList* mMetaData;
std::function<void()> mSavedCallback;
std::function<void()> mDeleteFunc;
bool mMetadataUpdated;
};
#endif // ES_APP_GUIS_GUI_META_DATA_ED_H

View file

@ -0,0 +1,319 @@
//
// GuiScraperMenu.cpp
//
// Game media scraper, including settings as well as the scraping start button.
// Submenu to the GuiMenu main menu.
// Will call GuiScraperMulti to perform the actual scraping.
//
#include "guis/GuiScraperMenu.h"
#include "components/OptionListComponent.h"
#include "components/SwitchComponent.h"
#include "guis/GuiMsgBox.h"
#include "guis/GuiScraperMulti.h"
#include "views/ViewController.h"
#include "FileData.h"
#include "SystemData.h"
#include "guis/GuiSettings.h"
GuiScraperMenu::GuiScraperMenu(Window* window) : GuiComponent(window),
mMenu(window, "SCRAPER")
{
// Scrape from.
auto scraper_list = std::make_shared< OptionListComponent<std::string>>
(mWindow, "SCRAPE FROM", false);
std::vector<std::string> scrapers = getScraperList();
// Select either the first entry or the one read from the settings,
// just in case the scraper from settings has vanished.
for (auto it = scrapers.cbegin(); it != scrapers.cend(); it++)
scraper_list->add(*it, *it, *it == Settings::getInstance()->getString("Scraper"));
mMenu.addWithLabel("SCRAPE FROM", scraper_list);
mMenu.addSaveFunc([scraper_list] { Settings::getInstance()->setString("Scraper",
scraper_list->getSelected()); });
// Search filters, getSearches() will generate a queue of games to scrape
// based on the outcome of the checks below.
mFilters = std::make_shared< OptionListComponent<GameFilterFunc>>
(mWindow, "SCRAPE THESE GAMES", false);
mFilters->add("ALL GAMES",
[](SystemData*, FileData*) -> bool { return true; }, true);
mFilters->add("NO METADATA",
[](SystemData*, FileData* g) -> bool {
return g->metadata.get("desc").empty(); }, false);
mFilters->add("NO GAME IMAGE",
[](SystemData*, FileData* g) -> bool {
return g->getImagePath().empty(); }, false);
mMenu.addWithLabel("Filter", mFilters);
// Add systems (all systems with an existing platform ID are listed).
mSystems = std::make_shared< OptionListComponent<SystemData*>>
(mWindow, "SCRAPE THESE SYSTEMS", true);
for (unsigned int i = 0; i < SystemData::sSystemVector.size(); i++) {
if (!SystemData::sSystemVector[i]->hasPlatformId(PlatformIds::PLATFORM_IGNORE)) {
mSystems->add(SystemData::sSystemVector[i]->getFullName(),
SystemData::sSystemVector[i],
!SystemData::sSystemVector[i]->getPlatformIds().empty());
SystemData::sSystemVector[i]->getScrapeFlag() ?
mSystems->selectEntry(i) : mSystems->unselectEntry(i);
}
}
mMenu.addWithLabel("Systems", mSystems);
addEntry("CONTENT SETTINGS", 0x777777FF, true, [this] { openContentSettings(); });
addEntry("OTHER SETTINGS", 0x777777FF, true, [this] { openOtherSettings(); });
addChild(&mMenu);
mMenu.addButton("START", "start", std::bind(&GuiScraperMenu::pressedStart, this));
mMenu.addButton("BACK", "back", [&] { delete this; });
setSize(mMenu.getSize());
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2,
Renderer::getScreenHeight() * 0.15f);
}
GuiScraperMenu::~GuiScraperMenu()
{
// Save the scrape flags to the system settings so that they are
// remembered throughout the program session.
std::vector<SystemData*> sys = mSystems->getSelectedObjects();
for (auto it = SystemData::sSystemVector.cbegin();
it != SystemData::sSystemVector.cend(); it++) {
(*it)->setScrapeFlag(false);
for (auto it_sys = sys.cbegin(); it_sys != sys.cend(); it_sys++) {
if ((*it)->getFullName() == (*it_sys)->getFullName())
(*it)->setScrapeFlag(true);
}
}
}
void GuiScraperMenu::openContentSettings()
{
auto s = new GuiSettings(mWindow, "SCRAPER CONTENT SETTINGS");
// Scrape metadata.
auto scrape_metadata = std::make_shared<SwitchComponent>(mWindow);
scrape_metadata->setState(Settings::getInstance()->getBool("ScrapeMetadata"));
s->addWithLabel("SCRAPE METADATA", scrape_metadata);
s->addSaveFunc([scrape_metadata] { Settings::getInstance()->setBool("ScrapeMetadata",
scrape_metadata->getState()); });
// Scrape game names.
auto scrape_gamename = std::make_shared<SwitchComponent>(mWindow);
scrape_gamename->setState(Settings::getInstance()->getBool("ScrapeGameNames"));
s->addWithLabel("SCRAPE GAME NAMES", scrape_gamename);
s->addSaveFunc([scrape_gamename] { Settings::getInstance()->setBool("ScrapeGameNames",
scrape_gamename->getState()); });
// Scrape ratings.
auto scrape_ratings = std::make_shared<SwitchComponent>(mWindow);
scrape_ratings->setState(Settings::getInstance()->getBool("ScrapeRatings"));
s->addWithLabel("SCRAPE RATINGS", scrape_ratings);
s->addSaveFunc([scrape_ratings] { Settings::getInstance()->setBool("ScrapeRatings",
scrape_ratings->getState()); });
// Scrape screenshots images.
auto scrape_screenshots = std::make_shared<SwitchComponent>(mWindow);
scrape_screenshots->setState(Settings::getInstance()->getBool("ScrapeScreenshots"));
s->addWithLabel("SCRAPE SCREENSHOT IMAGES", scrape_screenshots);
s->addSaveFunc([scrape_screenshots] { Settings::getInstance()->setBool("ScrapeScreenshots",
scrape_screenshots->getState()); });
// Scrape cover images.
auto scrape_covers = std::make_shared<SwitchComponent>(mWindow);
scrape_covers->setState(Settings::getInstance()->getBool("ScrapeCovers"));
s->addWithLabel("SCRAPE BOX COVER IMAGES", scrape_covers);
s->addSaveFunc([scrape_covers] { Settings::getInstance()->setBool("ScrapeCovers",
scrape_covers->getState()); });
// Scrape marquee images.
auto scrape_marquees = std::make_shared<SwitchComponent>(mWindow);
scrape_marquees->setState(Settings::getInstance()->getBool("ScrapeMarquees"));
s->addWithLabel("SCRAPE MARQUEE (WHEEL) IMAGES", scrape_marquees);
s->addSaveFunc([scrape_marquees] { Settings::getInstance()->setBool("ScrapeMarquees",
scrape_marquees->getState()); });
// Scrape 3D box images.
auto scrape_3dboxes = std::make_shared<SwitchComponent>(mWindow);
scrape_3dboxes->setState(Settings::getInstance()->getBool("Scrape3DBoxes"));
s->addWithLabel("SCRAPE 3D BOX IMAGES", scrape_3dboxes);
s->addSaveFunc([scrape_3dboxes] { Settings::getInstance()->setBool("Scrape3DBoxes",
scrape_3dboxes->getState()); });
mWindow->pushGui(s);
}
void GuiScraperMenu::openOtherSettings()
{
auto s = new GuiSettings(mWindow, "OTHER SCRAPER SETTINGS");
// Scraper region.
auto scraper_region = std::make_shared<OptionListComponent<std::string>>
(mWindow, "REGION", false);
std::vector<std::string> transitions_rg;
transitions_rg.push_back("eu");
transitions_rg.push_back("jp");
transitions_rg.push_back("us");
transitions_rg.push_back("ss");
transitions_rg.push_back("wor");
if (Settings::getInstance()->getString("ScraperRegion") != "") {
if (std::find(transitions_rg.begin(), transitions_rg.end(),
Settings::getInstance()->getString("ScraperRegion")) == transitions_rg.end()) {
transitions_rg.push_back(Settings::getInstance()->getString("ScraperRegion"));
}
}
for (auto it = transitions_rg.cbegin(); it != transitions_rg.cend(); it++)
scraper_region->add(*it, *it, Settings::getInstance()->getString("ScraperRegion") == *it);
s->addWithLabel("REGION", scraper_region);
s->addSaveFunc([scraper_region] {
Settings::getInstance()->setString("ScraperRegion", scraper_region->getSelected());
});
// Scraper language.
auto scraper_language = std::make_shared<OptionListComponent<std::string>>
(mWindow, "LANGUAGE", false);
std::vector<std::string> transitions_lg;
transitions_lg.push_back("en");
transitions_lg.push_back("wor");
if (Settings::getInstance()->getString("ScraperLanguage") != "") {
if (std::find(transitions_lg.begin(), transitions_lg.end(),
Settings::getInstance()->getString("ScraperLanguage")) == transitions_lg.end()) {
transitions_lg.push_back(Settings::getInstance()->getString("ScraperLanguage"));
}
}
for (auto it = transitions_lg.cbegin(); it != transitions_lg.cend(); it++)
scraper_language->add(*it, *it,
Settings::getInstance()->getString("ScraperLanguage") == *it);
s->addWithLabel("LANGUAGE", scraper_language);
s->addSaveFunc([scraper_language] {
Settings::getInstance()->setString("ScraperLanguage", scraper_language->getSelected());
});
// Overwrite files and data.
auto scrape_overwrite = std::make_shared<SwitchComponent>(mWindow);
scrape_overwrite->setState(Settings::getInstance()->getBool("ScraperOverwriteData"));
s->addWithLabel("OVERWRITE FILES AND DATA", scrape_overwrite);
s->addSaveFunc([scrape_overwrite] { Settings::getInstance()->setBool("ScraperOverwriteData",
scrape_overwrite->getState()); });
// Automatic 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()); });
mWindow->pushGui(s);
}
void GuiScraperMenu::pressedStart()
{
// Save any GUI settings that may have been modified.
mMenu.save();
std::vector<SystemData*> sys = mSystems->getSelectedObjects();
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?"),
"YES", std::bind(&GuiScraperMenu::start, this),
"NO", nullptr));
return;
}
}
start();
}
void GuiScraperMenu::start()
{
std::queue<ScraperSearchParams> searches = getSearches(mSystems->getSelectedObjects(),
mFilters->getSelected());
if (searches.empty()) {
mWindow->pushGui(new GuiMsgBox(mWindow,
"NO GAMES TO SCRAPE"));
}
else {
bool testbool = Settings::getInstance()->getBool("ScraperInteractive");
GuiScraperMulti* gsm = new GuiScraperMulti(mWindow, searches,
Settings::getInstance()->getBool("ScraperInteractive"));
mWindow->pushGui(gsm);
delete this;
}
}
std::queue<ScraperSearchParams> GuiScraperMenu::getSearches(
std::vector<SystemData*> systems, GameFilterFunc selector)
{
std::queue<ScraperSearchParams> queue;
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))) {
ScraperSearchParams search;
search.game = *game;
search.system = *sys;
queue.push(search);
}
}
}
return queue;
}
void GuiScraperMenu::addEntry(const char* name, unsigned int color,
bool add_arrow, const std::function<void()>& func)
{
std::shared_ptr<Font> font = Font::get(FONT_SIZE_MEDIUM);
// Populate the list.
ComponentListRow row;
row.addElement(std::make_shared<TextComponent>(mWindow, name, font, color), true);
if (add_arrow) {
std::shared_ptr<ImageComponent> bracket = makeArrow(mWindow);
row.addElement(bracket, false);
}
row.makeAcceptInputHandler(func);
mMenu.addRow(row);
}
bool GuiScraperMenu::input(InputConfig* config, Input input)
{
if (GuiComponent::input(config, input))
return true;
if ((config->isMappedTo("b", input) || config->isMappedTo("start", input)) &&
input.value != 0) {
delete this;
return true;
}
return false;
}
HelpStyle GuiScraperMenu::getHelpStyle()
{
HelpStyle style = HelpStyle();
style.applyTheme(ViewController::get()->getState().getSystem()->getTheme(), "system");
return style;
}
std::vector<HelpPrompt> GuiScraperMenu::getHelpPrompts()
{
std::vector<HelpPrompt> prompts;
prompts.push_back(HelpPrompt("up/down", "choose"));
prompts.push_back(HelpPrompt("a", "select"));
prompts.push_back(HelpPrompt("start", "close"));
return prompts;
}

View file

@ -0,0 +1,53 @@
//
// GuiScraperMenu.h
//
// Game media scraper, including settings as well as the scraping start button.
// Submenu to the GuiMenu main menu.
// Will call GuiScraperMulti to perform the actual scraping.
//
#pragma once
#ifndef ES_APP_GUIS_GUI_SCRAPER_MENU_H
#define ES_APP_GUIS_GUI_SCRAPER_MENU_H
#include "components/MenuComponent.h"
#include "scrapers/Scraper.h"
class FileData;
template<typename T>
class OptionListComponent;
class SwitchComponent;
class SystemData;
typedef std::function<bool(SystemData*, FileData*)> GameFilterFunc;
class GuiScraperMenu : public GuiComponent
{
public:
GuiScraperMenu(Window* window);
~GuiScraperMenu();
bool input(InputConfig* config, Input input) override;
std::vector<HelpPrompt> getHelpPrompts() override;
HelpStyle getHelpStyle() override;
private:
void pressedStart();
void start();
void addEntry(const char* name, unsigned int color,
bool add_arrow, const std::function<void()>& func);
void openContentSettings();
void openOtherSettings();
std::queue<ScraperSearchParams> getSearches(
std::vector<SystemData*> systems, GameFilterFunc selector);
std::shared_ptr<OptionListComponent<GameFilterFunc>> mFilters;
std::shared_ptr<OptionListComponent<SystemData*>> mSystems;
MenuComponent mMenu;
};
#endif // ES_APP_GUIS_GUI_SCRAPER_MENU_H

View file

@ -3,7 +3,7 @@
//
// Multiple game scraping user interface.
// Shows the progress for the scraping as it's running.
// This interface is triggered from the GuiScraperStart menu.
// This interface is triggered from GuiScraperMenu.
// ScraperSearchComponent is called from here.
//
@ -136,7 +136,8 @@ void GuiScraperMulti::acceptResult(const ScraperSearchResult& result)
{
ScraperSearchParams& search = mSearchQueue.front();
search.game->metadata = result.mdl;
ScraperSearchComponent::saveMetadata(result, search.game->metadata);
updateGamelist(search.system);
mSearchQueue.pop();
@ -157,15 +158,15 @@ void GuiScraperMulti::finish()
{
std::stringstream ss;
if (mTotalSuccessful == 0) {
ss << "NO GAMES WERE SCRAPED.";
ss << "NO GAMES WERE SCRAPED";
}
else {
ss << mTotalSuccessful << " GAME" << ((mTotalSuccessful > 1) ? "S" : "") <<
" SUCCESSFULLY SCRAPED!";
" SUCCESSFULLY SCRAPED";
if (mTotalSkipped > 0)
ss << "\n" << mTotalSkipped << " GAME" << ((mTotalSkipped > 1) ? "S" : "") <<
" SKIPPED.";
" SKIPPED";
}
mWindow->pushGui(new GuiMsgBox(mWindow, ss.str(), "OK", [&] {

View file

@ -3,7 +3,7 @@
//
// Multiple game scraping user interface.
// Shows the progress for the scraping as it's running.
// This interface is triggered from the GuiScraperStart menu.
// This interface is triggered from GuiScraperMenu.
// ScraperSearchComponent is called from here.
//
@ -15,6 +15,7 @@
#include "components/NinePatchComponent.h"
#include "scrapers/Scraper.h"
#include "GuiComponent.h"
#include "MetaData.h"
class ScraperSearchComponent;
class TextComponent;
@ -44,6 +45,7 @@ private:
unsigned int mTotalSuccessful;
unsigned int mTotalSkipped;
std::queue<ScraperSearchParams> mSearchQueue;
std::vector<MetaDataDecl> mMetaDataDecl;
NinePatchComponent mBackground;
ComponentGrid mGrid;

View file

@ -1,134 +0,0 @@
//
// 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"
#include "components/SwitchComponent.h"
#include "guis/GuiMsgBox.h"
#include "guis/GuiScraperMulti.h"
#include "views/ViewController.h"
#include "FileData.h"
#include "SystemData.h"
GuiScraperStart::GuiScraperStart(Window* window) : GuiComponent(window),
mMenu(window, "SCRAPE NOW")
{
addChild(&mMenu);
// 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);
mMenu.addWithLabel("Filter", mFilters);
// 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);
mApproveResults = std::make_shared<SwitchComponent>(mWindow);
mApproveResults->setState(true);
mMenu.addWithLabel("User decides on conflicts", mApproveResults);
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);
}
void GuiScraperStart::pressedStart()
{
std::vector<SystemData*> sys = mSystems->getSelectedObjects();
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?"),
"YES", std::bind(&GuiScraperStart::start, this),
"NO", nullptr));
return;
}
}
start();
}
void GuiScraperStart::start()
{
std::queue<ScraperSearchParams> searches = getSearches(mSystems->getSelectedObjects(),
mFilters->getSelected());
if (searches.empty()) {
mWindow->pushGui(new GuiMsgBox(mWindow,
"NO GAMES FIT THAT CRITERIA."));
}
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> queue;
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))) {
ScraperSearchParams search;
search.game = *game;
search.system = *sys;
queue.push(search);
}
}
}
return queue;
}
bool GuiScraperStart::input(InputConfig* config, Input input)
{
bool consumed = GuiComponent::input(config, input);
if (consumed)
return true;
if (input.value != 0 && config->isMappedTo("b", input)) {
delete this;
return true;
}
if (config->isMappedTo("start", input) && input.value != 0) {
// Close everything.
Window* window = mWindow;
while (window->peekGui() && window->peekGui() != ViewController::get())
delete window->peekGui();
}
return false;
}
std::vector<HelpPrompt> GuiScraperStart::getHelpPrompts()
{
std::vector<HelpPrompt> prompts = mMenu.getHelpPrompts();
prompts.push_back(HelpPrompt("b", "back"));
prompts.push_back(HelpPrompt("start", "close"));
return prompts;
}

View file

@ -1,49 +0,0 @@
//
// 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
#include "components/MenuComponent.h"
#include "scrapers/Scraper.h"
class FileData;
template<typename T>
class OptionListComponent;
class SwitchComponent;
class SystemData;
typedef std::function<bool(SystemData*, FileData*)> GameFilterFunc;
// 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:
GuiScraperStart(Window* window);
bool input(InputConfig* config, Input input) override;
virtual std::vector<HelpPrompt> getHelpPrompts() override;
private:
void pressedStart();
void start();
std::queue<ScraperSearchParams> getSearches(
std::vector<SystemData*> systems, GameFilterFunc selector);
std::shared_ptr< OptionListComponent<GameFilterFunc> > mFilters;
std::shared_ptr< OptionListComponent<SystemData*> > mSystems;
std::shared_ptr<SwitchComponent> mApproveResults;
MenuComponent mMenu;
};
#endif // ES_APP_GUIS_GUI_SCRAPER_START_H

View file

@ -16,6 +16,7 @@
#include "PlatformId.h"
#include "Settings.h"
#include "SystemData.h"
#include "MameNames.h"
#include "utils/TimeUtil.h"
#include <pugixml/src/pugixml.hpp>
@ -118,23 +119,30 @@ void thegamesdb_generate_json_scraper_requests(
std::string gameID = cleanName.substr(3);
path += "/Games/ByGameID?" + apiKey +
"&fields=players,publishers,genres,overview,last_updated,rating,"
"platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates&"
"include=boxart&id=" +
"platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates&id=" +
HttpReq::urlEncode(gameID);
usingGameID = true;
}
else {
if (cleanName.empty())
cleanName = params.game->getCleanName();
// If it's an arcade game (MAME or Neo Geo) then use the regular name.
if (params.system->hasPlatformId(PlatformIds::ARCADE) ||
params.system->hasPlatformId(PlatformIds::NEOGEO)) {
cleanName = params.game->getName();
cleanName = MameNames::getInstance()->getCleanName(params.game->getCleanName());
}
else
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=" +
"platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates&name=" +
HttpReq::urlEncode(cleanName);
}
if (usingGameID) {
// Ff we have the ID already, we don't need the GetGameList request.
// If we have the ID already, we don't need the GetGameList request.
requests.push(std::unique_ptr<ScraperRequest>(new TheGamesDBJSONRequest(results, path)));
}
else {
@ -165,6 +173,21 @@ void thegamesdb_generate_json_scraper_requests(
}
}
void thegamesdb_generate_json_scraper_requests(
const std::string& gameIDs,
std::queue<std::unique_ptr<ScraperRequest>>& requests,
std::vector<ScraperSearchResult>& results)
{
resources.prepare();
std::string path = "https://api.thegamesdb.net/v1";
const std::string apiKey = std::string("apikey=") + resources.getApiKey();
path += "/Games/Images/GamesImages?" + apiKey + "&games_id=" + gameIDs;
requests.push(std::unique_ptr<ScraperRequest>
(new TheGamesDBJSONRequest(requests, results, path)));
}
namespace
{
@ -194,21 +217,6 @@ int getIntOrThrow(const Value& v)
return v.GetInt();
}
std::string getBoxartImage(const Value& v)
{
if (!v.IsArray() || v.Size() == 0)
return "";
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");
}
std::string getDeveloperString(const Value& v)
{
if (!v.IsArray())
@ -274,13 +282,13 @@ std::string getGenreString(const Value& v)
return out;
}
void processGame(const Value& game, const Value& boxart, std::vector<ScraperSearchResult>& results)
void processGame(const Value& game, std::vector<ScraperSearchResult>& results)
{
std::string baseImageUrlThumb = getStringOrThrow(boxart["base_url"], "thumb");
std::string baseImageUrlLarge = getStringOrThrow(boxart["base_url"], "large");
ScraperSearchResult result;
if (game.HasMember("id") && game["id"].IsInt())
result.gameID = std::to_string(getIntOrThrow(game, "id"));
result.mdl.set("name", getStringOrThrow(game, "game_title"));
if (game.HasMember("overview") && game["overview"].IsString())
result.mdl.set("desc", game["overview"].GetString());
@ -301,19 +309,52 @@ void processGame(const Value& game, const Value& boxart, std::vector<ScraperSear
if (game.HasMember("players") && game["players"].IsInt())
result.mdl.set("players", std::to_string(game["players"].GetInt()));
if (boxart.HasMember("data") && boxart["data"].IsObject()) {
std::string id = std::to_string(getIntOrThrow(game, "id"));
if (boxart["data"].HasMember(id.c_str())) {
std::string image = getBoxartImage(boxart["data"][id.c_str()]);
result.thumbnailUrl = baseImageUrlThumb + "/" + image;
result.imageUrl = baseImageUrlLarge + "/" + image;
}
}
result.mediaURLFetch = NOT_STARTED;
results.push_back(result);
}
} // namespace
void processMediaURLs(const Value& images, const std::string& base_url,
std::vector<ScraperSearchResult>& results)
{
ScraperSearchResult result;
// Step through each game ID in the JSON server response.
for (auto it = images.MemberBegin(); it != images.MemberEnd(); it++) {
result.gameID = it->name.GetString();
const Value& gameMedia = images[it->name];
result.coverUrl = "";
result.marqueeUrl = "";
result.screenshotUrl = "";
// Quite excessive testing for valid values, but you never know
// what the server has returned and we don't want to crash the
// program due to malformed data.
if (gameMedia.IsArray()) {
for (SizeType i = 0; i < gameMedia.Size(); i++) {
std::string mediatype;
std::string mediaside;
if (gameMedia[i]["type"].IsString())
mediatype = gameMedia[i]["type"].GetString();
if (gameMedia[i]["side"].IsString())
mediaside = gameMedia[i]["side"].GetString();
if (mediatype == "boxart" && mediaside == "front")
if (gameMedia[i]["filename"].IsString())
result.coverUrl = base_url + gameMedia[i]["filename"].GetString();
if (mediatype == "clearlogo")
if (gameMedia[i]["filename"].IsString())
result.marqueeUrl = base_url + gameMedia[i]["filename"].GetString();
if (mediatype == "screenshot")
if (gameMedia[i]["filename"].IsString())
result.screenshotUrl = base_url + gameMedia[i]["filename"].GetString();
}
}
result.mediaURLFetch = COMPLETED;
results.push_back(result);
}
}
void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req,
std::vector<ScraperSearchResult>& results)
{
@ -331,34 +372,58 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req,
return;
}
// If the response contains the 'images' object, then it's a game media URL request.
if (doc.HasMember("data") && doc["data"].HasMember("images") &&
doc["data"]["images"].IsObject()) {
const Value& images = doc["data"]["images"];
const Value& base_url = doc["data"]["base_url"];
std::string baseImageUrlLarge;
if (base_url.HasMember("large") && base_url["large"].IsString()) {
baseImageUrlLarge = base_url["large"].GetString();
}
else {
std::string warn = "TheGamesDBJSONRequest - No URL path for large images.\n";
LOG(LogWarning) << warn;
return;
}
try {
processMediaURLs(images, baseImageUrlLarge, results);
}
catch (std::runtime_error& e) {
LOG(LogError) << "Error while processing media URLs: " << e.what();
}
// Find how many more requests we can make before the scraper
// request allowance counter is reset.
if (doc.HasMember("remaining_monthly_allowance") && doc.HasMember("extra_allowance")) {
for (auto i = 0; i < results.size(); i++) {
results[i].scraperRequestAllowance =
doc["remaining_monthly_allowance"].GetInt() +
doc["extra_allowance"].GetInt();
}
}
return;
}
// These process steps are for the initial scraping response.
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")) {
std::string warn = "TheGamesDBJSONRequest - Response had no include boxart data.\n";
LOG(LogWarning) << warn;
return;
}
const Value& boxart = doc["include"]["boxart"];
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;
}
resources.ensureResources();
for (int i = 0; i < (int)games.Size(); ++i) {
auto& v = games[i];
try {
processGame(v, boxart, results);
processGame(v, results);
}
catch (std::runtime_error& e) {
LOG(LogError) << "Error while processing game: " << e.what();

View file

@ -20,6 +20,11 @@ void thegamesdb_generate_json_scraper_requests(
std::queue<std::unique_ptr<ScraperRequest>>& requests,
std::vector<ScraperSearchResult>& results);
void thegamesdb_generate_json_scraper_requests(
const std::string& gameIDs,
std::queue<std::unique_ptr<ScraperRequest>>& requests,
std::vector<ScraperSearchResult>& results);
class TheGamesDBJSONRequest : public ScraperHttpRequest
{
public:
@ -42,6 +47,8 @@ class TheGamesDBJSONRequest : public ScraperHttpRequest
}
protected:
//void retrieveMediaURLs()
void process(const std::unique_ptr<HttpReq>& req,
std::vector<ScraperSearchResult>& results) override;
bool isGameRequest() { return !mRequestQueue; }

View file

@ -4,6 +4,11 @@
// Functions specifically for scraping from thegamesdb.net
// Called from GamesDBJSONScraper.
//
// Downloads these resource files to ~/.emulationstation/scrapers:
// gamesdb_developers.json
// gamesdb_genres.json
// gamesdb_publishers.json
//
#include <chrono>
#include <fstream>

View file

@ -4,6 +4,11 @@
// Functions specifically for scraping from thegamesdb.net
// Called from GamesDBJSONScraper.
//
// Downloads these resource files to ~/.emulationstation/scrapers:
// gamesdb_developers.json
// gamesdb_genres.json
// gamesdb_publishers.json
//
#pragma once
#ifndef ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_RESOURCES_H

View file

@ -36,10 +36,28 @@ std::unique_ptr<ScraperSearchHandle> startScraperSearch(const ScraperSearchParam
return handle;
}
std::unique_ptr<ScraperSearchHandle> startMediaURLsFetch(const std::string& gameIDs)
{
const std::string& name = Settings::getInstance()->getString("Scraper");
std::unique_ptr<ScraperSearchHandle> handle(new ScraperSearchHandle());
ScraperSearchParams params;
// 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
// Specifically use the TheGamesDB function as this type of request
// will never occur for ScreenScraper.
thegamesdb_generate_json_scraper_requests(gameIDs, handle->mRequestQueue,
handle->mResults);
return handle;
}
std::vector<std::string> getScraperList()
{
std::vector<std::string> list;
for(auto it = scraper_request_funcs.cbegin(); it != scraper_request_funcs.cend(); it++)
for (auto it = scraper_request_funcs.cbegin(); it != scraper_request_funcs.cend(); it++)
list.push_back(it->first);
return list;
@ -59,36 +77,35 @@ ScraperSearchHandle::ScraperSearchHandle()
void ScraperSearchHandle::update()
{
if(mStatus == ASYNC_DONE)
if (mStatus == ASYNC_DONE)
return;
if(!mRequestQueue.empty())
{
if (!mRequestQueue.empty()) {
// 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) {
if (status == ASYNC_ERROR) {
// Propagate error.
setError(req.getStatusString());
// Empty our queue.
while(!mRequestQueue.empty())
while (!mRequestQueue.empty())
mRequestQueue.pop();
return;
}
// Finished this one, see if we have any more.
if(status == ASYNC_DONE)
if (status == ASYNC_DONE)
mRequestQueue.pop();
// Status == ASYNC_IN_PROGRESS.
}
// We finished without any errors!
if(mRequestQueue.empty()) {
if (mRequestQueue.empty()) {
setStatus(ASYNC_DONE);
return;
}
@ -101,8 +118,8 @@ ScraperRequest::ScraperRequest(std::vector<ScraperSearchResult>& resultsWrite)
}
// ScraperHttpRequest.
ScraperHttpRequest::ScraperHttpRequest(std::vector<ScraperSearchResult>&
resultsWrite, const std::string& url) : ScraperRequest(resultsWrite)
ScraperHttpRequest::ScraperHttpRequest(std::vector<ScraperSearchResult>& resultsWrite,
const std::string& url) : ScraperRequest(resultsWrite)
{
setStatus(ASYNC_IN_PROGRESS);
mReq = std::unique_ptr<HttpReq>(new HttpReq(url));
@ -111,8 +128,7 @@ ScraperHttpRequest::ScraperHttpRequest(std::vector<ScraperSearchResult>&
void ScraperHttpRequest::update()
{
HttpReq::Status status = mReq->status();
if(status == HttpReq::REQ_SUCCESS)
{
if (status == HttpReq::REQ_SUCCESS) {
// If process() has an error, status will be changed to ASYNC_ERROR.
setStatus(ASYNC_DONE);
process(mReq, mResults);
@ -120,7 +136,7 @@ void ScraperHttpRequest::update()
}
// Not ready yet.
if(status == HttpReq::REQ_IN_PROGRESS)
if (status == HttpReq::REQ_IN_PROGRESS)
return;
// Everything else is some sort of error.
@ -129,8 +145,7 @@ void ScraperHttpRequest::update()
setError(mReq->getErrorMsg());
}
// Metadata resolving stuff.
// Download and write the media files to disk.
std::unique_ptr<MDResolveHandle> resolveMetaDataAssets(const ScraperSearchResult& result,
const ScraperSearchParams& search)
{
@ -140,44 +155,124 @@ std::unique_ptr<MDResolveHandle> resolveMetaDataAssets(const ScraperSearchResult
MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result,
const ScraperSearchParams& search) : mResult(result)
{
if(!result.imageUrl.empty()) {
struct mediaFileInfoStruct {
std::string fileURL;
std::string fileFormat;
std::string subDirectory;
std::string existingMediaFile;
} mediaFileInfo;
std::vector<struct mediaFileInfoStruct> scrapeFiles;
if (Settings::getInstance()->getBool("Scrape3DBoxes") && result.box3dUrl != "") {
mediaFileInfo.fileURL = result.box3dUrl;
mediaFileInfo.fileFormat = result.box3dFormat;
mediaFileInfo.subDirectory = "3dboxes";
mediaFileInfo.existingMediaFile = search.game->get3DBoxPath();
scrapeFiles.push_back(mediaFileInfo);
}
if (Settings::getInstance()->getBool("ScrapeCovers") && result.coverUrl != "") {
mediaFileInfo.fileURL = result.coverUrl;
mediaFileInfo.fileFormat = result.coverFormat;
mediaFileInfo.subDirectory = "covers";
mediaFileInfo.existingMediaFile = search.game->getCoverPath();
scrapeFiles.push_back(mediaFileInfo);
}
if (Settings::getInstance()->getBool("ScrapeMarquees") && result.marqueeUrl != "") {
mediaFileInfo.fileURL = result.marqueeUrl;
mediaFileInfo.fileFormat = result.marqueeFormat;
mediaFileInfo.subDirectory = "marquees";
mediaFileInfo.existingMediaFile = search.game->getMarqueePath();
scrapeFiles.push_back(mediaFileInfo);
}
if (Settings::getInstance()->getBool("ScrapeScreenshots") && result.screenshotUrl != "") {
mediaFileInfo.fileURL = result.screenshotUrl;
mediaFileInfo.fileFormat = result.screenshotFormat;
mediaFileInfo.subDirectory = "screenshots";
mediaFileInfo.existingMediaFile = search.game->getScreenshotPath();
scrapeFiles.push_back(mediaFileInfo);
}
for (auto it = scrapeFiles.cbegin(); it != scrapeFiles.cend(); it++) {
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()) {
ext = result.imageType;
if (!it->fileFormat.empty()) {
ext = it->fileFormat;
}
else {
size_t dot = result.imageUrl.find_last_of('.');
size_t dot = it->fileURL.find_last_of('.');
if (dot != std::string::npos)
ext = result.imageUrl.substr(dot, std::string::npos);
ext = it->fileURL.substr(dot, std::string::npos);
}
std::string imgPath = getSaveAsPath(search, "image", ext);
std::string filePath = getSaveAsPath(search, it->subDirectory, ext);
mFuncs.push_back(ResolvePair(downloadImageAsync(result.imageUrl, imgPath),
[this, imgPath] {
mResult.mdl.set("image", imgPath);
mResult.imageUrl = "";
}));
// If there is an existing media file on disk and the setting to overwrite data
// has been set to no, then don't proceed with downloading or saving a new file.
if (it->existingMediaFile != "" &&
!Settings::getInstance()->getBool("ScraperOverwriteData"))
continue;
// If the image is cached already as the thumbnail, then we don't need
// to download it again, in this case just save it to disk and resize it.
if (mResult.ThumbnailImageUrl == it->fileURL &&
mResult.ThumbnailImageData.size() > 0) {
// Remove any existing media file before attempting to write a new one.
// This avoids the problem where there's already a file for this media type
// with a different format/extension (e.g. game.jpg and we're going to write
// game.png) which would lead to two media files for this game.
if(it->existingMediaFile != "")
Utils::FileSystem::removeFile(it->existingMediaFile);
std::ofstream stream(filePath, std::ios_base::out | std::ios_base::binary);
if (stream.bad()) {
setError("Failed to open image path to write. Permission error? Disk full?");
return;
}
const std::string& content = mResult.ThumbnailImageData;
stream.write(content.data(), content.length());
stream.close();
if (stream.bad()) {
setError("Failed to save image. Disk full?");
return;
}
// Resize it.
if (!resizeImage(filePath, Settings::getInstance()->getInt("ScraperResizeWidth"),
Settings::getInstance()->getInt("ScraperResizeHeight"))) {
setError("Error saving resized image. Out of memory? Disk full?");
return;
}
}
// If it's not cached, then initiate the download.
else {
mFuncs.push_back(ResolvePair(downloadImageAsync(it->fileURL, filePath,
it->existingMediaFile), [this, filePath] {
// mResult.mdl.set("image", filePath);
}));
}
}
}
void MDResolveHandle::update()
{
if(mStatus == ASYNC_DONE || mStatus == ASYNC_ERROR)
if (mStatus == ASYNC_DONE || mStatus == ASYNC_ERROR)
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;
@ -185,30 +280,41 @@ void MDResolveHandle::update()
it++;
}
if(mFuncs.empty())
if (mFuncs.empty())
setStatus(ASYNC_DONE);
}
std::unique_ptr<ImageDownloadHandle> downloadImageAsync(const std::string& url,
const std::string& saveAs)
const std::string& saveAs, const std::string& existingMediaFile)
{
return std::unique_ptr<ImageDownloadHandle>(new ImageDownloadHandle(url, saveAs,
return std::unique_ptr<ImageDownloadHandle>(new ImageDownloadHandle(
url,
saveAs,
existingMediaFile,
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,
const std::string& existingMediaPath,
int maxWidth,
int maxHeight)
: mSavePath(path),
mExistingMediaFile(existingMediaPath),
mMaxWidth(maxWidth),
mMaxHeight(maxHeight),
mReq(new HttpReq(url))
{
}
void ImageDownloadHandle::update()
{
if(mReq->status() == HttpReq::REQ_IN_PROGRESS)
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());
@ -216,8 +322,16 @@ void ImageDownloadHandle::update()
}
// Download is done, save it to disk.
// Remove any existing media file before attempting to write a new one.
// This avoids the problem where there's already a file for this media type
// with a different format/extension (e.g. game.jpg and we're going to write
// game.png) which would lead to two media files for this game.
if(mExistingMediaFile != "")
Utils::FileSystem::removeFile(mExistingMediaFile);
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;
}
@ -225,13 +339,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)) {
if (!resizeImage(mSavePath, mMaxWidth, mMaxHeight)) {
setError("Error saving resized image. Out of memory? Disk full?");
return;
}
@ -243,7 +357,7 @@ void ImageDownloadHandle::update()
bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
{
// Nothing to do.
if(maxWidth == 0 && maxHeight == 0)
if (maxWidth == 0 && maxHeight == 0)
return true;
FREE_IMAGE_FORMAT format = FIF_UNKNOWN;
@ -251,15 +365,15 @@ bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
// Detect the filetype.
format = FreeImage_GetFileType(path.c_str(), 0);
if(format == FIF_UNKNOWN)
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)) {
if (FreeImage_FIFSupportsReading(format)) {
image = FreeImage_Load(format, path.c_str());
}
else {
@ -270,15 +384,20 @@ bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
float width = (float)FreeImage_GetWidth(image);
float height = (float)FreeImage_GetHeight(image);
if(maxWidth == 0)
// If the image is smaller than maxWidth or maxHeight, then don't do any
// scaling. It doesn't make sense to upscale the image and waste disk space.
if (maxWidth > width || maxHeight > height)
return true;
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;
}
@ -286,26 +405,26 @@ bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
bool saved = (FreeImage_Save(format, imageRescaled, path.c_str()) != 0);
FreeImage_Unload(imageRescaled);
if(!saved)
if (!saved)
LOG(LogError) << "Failed to save resized image!";
return saved;
}
std::string getSaveAsPath(const ScraperSearchParams& params,
const std::string& suffix, const std::string& extension)
const std::string& filetypeSubdirectory, const std::string& extension)
{
const std::string subdirectory = params.system->getName();
const std::string name = Utils::FileSystem::getStem(params.game->getPath()) + "-" + suffix;
const std::string systemsubdirectory = params.system->getName();
const std::string name = Utils::FileSystem::getStem(params.game->getPath());
std::string path = Utils::FileSystem::getHomePath() + "/.emulationstation/downloaded_images/";
std::string path = FileData::getMediaDirectory();
if(!Utils::FileSystem::exists(path))
if (!Utils::FileSystem::exists(path))
Utils::FileSystem::createDirectory(path);
path += subdirectory + "/";
path += systemsubdirectory + "/" + filetypeSubdirectory + "/";
if(!Utils::FileSystem::exists(path))
if (!Utils::FileSystem::exists(path))
Utils::FileSystem::createDirectory(path);
path += name + extension;

View file

@ -24,6 +24,12 @@
class FileData;
class SystemData;
enum eDownloadStatus {
NOT_STARTED,
IN_PROGRESS,
COMPLETED
};
struct ScraperSearchParams {
SystemData* system;
FileData* game;
@ -35,11 +41,29 @@ struct ScraperSearchResult {
ScraperSearchResult() : mdl(GAME_METADATA) {};
MetaDataList mdl;
std::string imageUrl;
std::string thumbnailUrl;
std::string gameID;
// How many more objects the scraper service allows to be downloaded
// within a given time period.
unsigned int scraperRequestAllowance;
enum eDownloadStatus mediaURLFetch = NOT_STARTED;
enum eDownloadStatus thumbnailDownloadStatus = NOT_STARTED;
enum eDownloadStatus mediaFilesDownloadStatus = NOT_STARTED;
std::string ThumbnailImageData; // Thumbnail cache, will containe entire image.
std::string ThumbnailImageUrl;
std::string box3dUrl;
std::string coverUrl;
std::string marqueeUrl;
std::string screenshotUrl;
// Needed to pre-set the image type.
std::string imageType;
std::string box3dFormat;
std::string coverFormat;
std::string marqueeFormat;
std::string screenshotFormat;
};
// So let me explain why I've abstracted this so heavily.
@ -83,7 +107,6 @@ protected:
std::vector<ScraperSearchResult>& mResults;
};
// A single HTTP request that needs to be processed to get the results.
class ScraperHttpRequest : public ScraperRequest
{
@ -113,6 +136,9 @@ protected:
friend std::unique_ptr<ScraperSearchHandle>
startScraperSearch(const ScraperSearchParams& params);
friend std::unique_ptr<ScraperSearchHandle>
startMediaURLsFetch(const std::string& gameIDs);
std::queue< std::unique_ptr<ScraperRequest> > mRequestQueue;
std::vector<ScraperSearchResult> mResults;
};
@ -120,6 +146,8 @@ protected:
// Will use the current scraper settings to pick the result source.
std::unique_ptr<ScraperSearchHandle> startScraperSearch(const ScraperSearchParams& params);
std::unique_ptr<ScraperSearchHandle> startMediaURLsFetch(const std::string& gameIDs);
// Returns a list of valid scraper names.
std::vector<std::string> getScraperList();
@ -127,7 +155,7 @@ std::vector<std::string> getScraperList();
bool isValidConfiguredScraper();
typedef void (*generate_scraper_requests_func)(const ScraperSearchParams& params,
std::queue< std::unique_ptr<ScraperRequest> >& requests,
std::queue<std::unique_ptr<ScraperRequest>>& requests,
std::vector<ScraperSearchResult>& results);
// -------------------------------------------------------------------------
@ -145,21 +173,26 @@ public:
private:
ScraperSearchResult mResult;
typedef std::pair< std::unique_ptr<AsyncHandle>, std::function<void()> > ResolvePair;
typedef std::pair<std::unique_ptr<AsyncHandle>, std::function<void()>> ResolvePair;
std::vector<ResolvePair> mFuncs;
};
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,
const std::string& existingMediaPath,
int maxWidth,
int maxHeight);
void update() override;
private:
std::unique_ptr<HttpReq> mReq;
std::string mSavePath;
std::string mExistingMediaFile;
int mMaxWidth;
int mMaxHeight;
};
@ -167,13 +200,13 @@ private:
// 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);
std::string getSaveAsPath(const ScraperSearchParams& params,
const std::string& filetypeSubdirectory, 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);
const std::string& saveAs, const std::string& existingMediaPath);
// Resolves all metadata assets that need to be downloaded.
std::unique_ptr<MDResolveHandle> resolveMetaDataAssets(const ScraperSearchResult& result,

View file

@ -14,6 +14,7 @@
#include "PlatformId.h"
#include "Settings.h"
#include "SystemData.h"
#include "math/Misc.h"
#include <pugixml/src/pugixml.hpp>
#include <cstring>
@ -132,7 +133,11 @@ void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
ScreenScraperRequest::ScreenScraperConfig ssConfig;
path = ssConfig.getGameSearchUrl(params.game->getFileName());
if (params.nameOverride == "")
path = ssConfig.getGameSearchUrl(params.game->getCleanName());
else
path = ssConfig.getGameSearchUrl(params.nameOverride);
auto& platforms = params.system->getPlatformIds();
std::vector<unsigned short> p_ids;
@ -164,7 +169,6 @@ void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
requests.push(std::unique_ptr<ScraperRequest>
(new ScreenScraperRequest(requests, results, path)));
}
}
void ScreenScraperRequest::process(const std::unique_ptr<HttpReq>& req,
@ -200,8 +204,20 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
ScraperSearchResult result;
ScreenScraperRequest::ScreenScraperConfig ssConfig;
std::string region = Utils::String::toLower(ssConfig.region).c_str();
std::string language = Utils::String::toLower(ssConfig.language).c_str();
result.gameID = game.attribute("id").as_string();
// Find how many more requests we can make before the scraper request
// allowance counter is reset.
unsigned requestsToday =
data.child("ssuser").child("requeststoday").text().as_uint();
unsigned maxRequestsPerDay =
data.child("ssuser").child("maxrequestsperday").text().as_uint();
result.scraperRequestAllowance = maxRequestsPerDay - requestsToday;
std::string region =
Utils::String::toLower(Settings::getInstance()->getString("ScraperRegion"));
std::string language =
Utils::String::toLower(Settings::getInstance()->getString("ScraperLanguage"));
// Name fallback: US, WOR(LD). ( Xpath: Data/jeu[0]/noms/nom[*] ).
result.mdl.set("name", find_child_by_attribute_list(game.child("noms"),
@ -251,9 +267,11 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
// Players.
result.mdl.set("players", game.child("joueurs").text().get());
// TODO: Validate rating.
// Validate rating.
if (Settings::getInstance()->getBool("ScrapeRatings") && game.child("note")) {
float ratingVal = (game.child("note").text().as_int() / 20.0f);
// Round up to the closest .1 value, i.e. to the closest half-star.
ratingVal = Math::ceilf(ratingVal / 0.1) / 10;
std::stringstream ss;
ss << ratingVal;
result.mdl.set("rating", ss.str());
@ -263,52 +281,70 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
pugi::xml_node media_list = game.child("medias");
if (media_list) {
pugi::xml_node art = pugi::xml_node(NULL);
// 3D box
processMedia(result, media_list, ssConfig.media_3dbox,
result.box3dUrl, result.box3dFormat, region);
// Cover
processMedia(result, media_list, ssConfig.media_cover,
result.coverUrl, result.coverFormat, region);
// Marquee (wheel)
processMedia(result, media_list, ssConfig.media_marquee,
result.marqueeUrl, result.marqueeFormat, region);
// Screenshot
processMedia(result, media_list, ssConfig.media_screenshot,
result.screenshotUrl, result.screenshotFormat, region);
}
result.mediaURLFetch = COMPLETED;
out_results.push_back(result);
} // Game.
}
// 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());
void ScreenScraperRequest::processMedia(
ScraperSearchResult& result,
const pugi::xml_node& media_list,
std::string mediaType,
std::string& fileURL,
std::string& fileFormat,
std::string region)
{
pugi::xml_node art = pugi::xml_node(NULL);
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)
// 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='") + mediaType + "']").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 (art)
break;
for (auto node : results) {
if (node.node().attribute("region").value() == _region) {
art = node.node();
break;
for (auto node : results) {
if (node.node().attribute("region").value() == _region) {
art = node.node();
break;
}
}
}
} // 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
result.imageUrl = Utils::String::replace(art.text().get(), " ", "%20");
// 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.
result.thumbnailUrl = result.imageUrl + "&maxheight=250";
}
else {
LOG(LogDebug) << "Failed to find media XML node with name=" << ssConfig.media_name;
}
}
out_results.push_back(result);
} // Game.
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.
fileURL = Utils::String::replace(art.text().get(), " ", "%20");
// Get the media type returned by ScreenScraper.
std::string media_type = art.attribute("format").value();
if (!media_type.empty())
fileFormat = "." + media_type;
}
else {
LOG(LogDebug) << "Failed to find media XML node with name=" << mediaType;
}
}
// Currently not used in this module.

View file

@ -62,15 +62,19 @@ public:
// Note that not all games contain values for all these, so we default to "box-2D"
// since it's the most common.
//
std::string media_name = "box-2D";
std::string media_3dbox = "box-3D";
std::string media_cover = "box-2D";
std::string media_marquee = "wheel";
std::string media_screenshot = "ss";
// Which Region to use when selecting the artwork.
// Applies to: artwork, name of the game, date of release.
std::string region = "US";
// This is read from es_settings.cfg, setting 'ScraperRegion'.
// Which Language to use when selecting the textual information.
// Applies to: description, genre.
std::string language = "EN";
// This is read from es_settings.cfg, setting 'ScraperLanguage'.
ScreenScraperConfig() {};
} configuration;
@ -81,6 +85,12 @@ protected:
void processList(const pugi::xml_document& xmldoc, std::vector<ScraperSearchResult>& results);
void processGame(const pugi::xml_document& xmldoc, std::vector<ScraperSearchResult>& results);
void processMedia(ScraperSearchResult& result,
const pugi::xml_node& media_list,
std::string mediaType,
std::string& fileURL,
std::string& fileFormat,
std::string region);
bool isGameRequest() { return !mRequestQueue; }
std::queue< std::unique_ptr<ScraperRequest> >* mRequestQueue;

View file

@ -155,13 +155,13 @@ bool SystemView::input(InputConfig* config, Input input)
case VERTICAL_WHEEL:
if (config->isMappedLike("up", input))
{
navigationsounds.playThemeNavigationSound(SYSTEMBROWSESOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND);
listInput(-1);
return true;
}
if (config->isMappedLike("down", input))
{
navigationsounds.playThemeNavigationSound(SYSTEMBROWSESOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND);
listInput(1);
return true;
}
@ -171,13 +171,13 @@ bool SystemView::input(InputConfig* config, Input input)
default:
if (config->isMappedLike("left", input))
{
navigationsounds.playThemeNavigationSound(SYSTEMBROWSESOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND);
listInput(-1);
return true;
}
if (config->isMappedLike("right", input))
{
navigationsounds.playThemeNavigationSound(SYSTEMBROWSESOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND);
listInput(1);
return true;
}
@ -188,14 +188,14 @@ bool SystemView::input(InputConfig* config, Input input)
{
stopScrolling();
ViewController::get()->goToGameList(getSelected());
navigationsounds.playThemeNavigationSound(SELECTSOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(SELECTSOUND);
return true;
}
if (config->isMappedTo("x", input))
{
// get random system
// go to system
navigationsounds.playThemeNavigationSound(SYSTEMBROWSESOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND);
setCursor(SystemData::getRandomSystem());
return true;
}

View file

@ -28,7 +28,6 @@
#include "Sound.h"
ViewController* ViewController::sInstance = nullptr;
NavigationSounds navigationsounds;
ViewController* ViewController::get()
{
@ -117,7 +116,7 @@ void ViewController::goToNextGameList()
assert(mState.viewing == GAME_LIST);
SystemData* system = getState().getSystem();
assert(system);
navigationsounds.playThemeNavigationSound(QUICKSYSSELECTSOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(QUICKSYSSELECTSOUND);
goToGameList(system->getNext());
}
@ -126,7 +125,7 @@ void ViewController::goToPrevGameList()
assert(mState.viewing == GAME_LIST);
SystemData* system = getState().getSystem();
assert(system);
navigationsounds.playThemeNavigationSound(QUICKSYSSELECTSOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(QUICKSYSSELECTSOUND);
goToGameList(system->getPrev());
}
@ -239,9 +238,9 @@ void ViewController::launch(FileData* game, Vector3f center)
std::string transition_style = Settings::getInstance()->getString("TransitionStyle");
navigationsounds.playThemeNavigationSound(LAUNCHSOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(LAUNCHSOUND);
// Let launch sound play to the end before launching game.
while(navigationsounds.isPlayingThemeNavigationSound(LAUNCHSOUND));
while (NavigationSounds::getInstance()->isPlayingThemeNavigationSound(LAUNCHSOUND));
if (transition_style == "fade") {
// Fade out, launch game, fade back in.
@ -472,7 +471,8 @@ void ViewController::preload()
getGameListView(*it);
}
// Load navigation sounds.
navigationsounds.loadThemeNavigationSounds(SystemData::sSystemVector[0]->getTheme());
NavigationSounds::getInstance()->loadThemeNavigationSounds(
SystemData::sSystemVector.front()->getTheme());
}
void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
@ -539,7 +539,8 @@ void ViewController::reloadAll()
}
// Load navigation sounds.
navigationsounds.loadThemeNavigationSounds(SystemData::sSystemVector[0]->getTheme());
NavigationSounds::getInstance()->loadThemeNavigationSounds(
SystemData::sSystemVector.front()->getTheme());
updateHelpPrompts();
}

View file

@ -176,7 +176,7 @@ bool GridGameListView::input(InputConfig* config, Input input)
config->isMappedLike("right", input) ||
(config->isMappedLike("up", input)) ||
(config->isMappedLike("down", input)) ))
navigationsounds.playThemeNavigationSound(SCROLLSOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
if (config->isMappedLike("left", input) || config->isMappedLike("right", input))
return GuiComponent::input(config, input);

View file

@ -98,7 +98,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
else {
// It's a folder.
if (cursor->getChildren().size() > 0) {
navigationsounds.playThemeNavigationSound(SELECTSOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(SELECTSOUND);
mCursorStack.push(cursor);
populateList(cursor->getChildrenListToDisplay());
FileData* cursor = getCursor();
@ -110,13 +110,13 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
}
else if (config->isMappedTo("b", input)) {
if (mCursorStack.size()) {
navigationsounds.playThemeNavigationSound(BACKSOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(BACKSOUND);
populateList(mCursorStack.top()->getParent()->getChildren());
setCursor(mCursorStack.top());
mCursorStack.pop();
}
else {
navigationsounds.playThemeNavigationSound(BACKSOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(BACKSOUND);
onFocusLost();
SystemData* systemToView = getCursor()->getSystem();
if (systemToView->isCollection())
@ -145,7 +145,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
else if (config->isMappedTo("x", input)) {
if (mRoot->getSystem()->isGameSystem()) {
// Go to random system game.
navigationsounds.playThemeNavigationSound(SCROLLSOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
FileData* randomGame = getCursor()->getSystem()->getRandomGame();
if (randomGame)
setCursor(randomGame);
@ -155,7 +155,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
else if (config->isMappedTo("y", input) &&
!UIModeController::getInstance()->isUIModeKid()) {
if (mRoot->getSystem()->isGameSystem()) {
navigationsounds.playThemeNavigationSound(FAVORITESOUND);
NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND);
if (CollectionSystemManager::get()->toggleGameInCollection(getCursor()))
return true;
}

View file

@ -287,7 +287,6 @@ void VideoGameListView::updateInfoPanel()
mVideo->setImage(file->getImagePath());
mThumbnail.setImage(file->getThumbnailPath());
mMarquee.setImage(file->getMarqueePath());
mImage.setImage(file->getImagePath());
mDescription.setText(file->metadata.get("desc"));
mDescContainer.reset();

View file

@ -44,8 +44,7 @@ bool HttpReq::isUrl(const std::string& str)
std::string::npos || str.find("www.") != std::string::npos));
}
HttpReq::HttpReq(const std::string& url)
: mStatus(REQ_IN_PROGRESS), mHandle(NULL)
HttpReq::HttpReq(const std::string& url) : mStatus(REQ_IN_PROGRESS), mHandle(NULL)
{
mHandle = curl_easy_init();

View file

@ -26,8 +26,7 @@
// Once one of those calls complete, the request is ready.
//
// Do something like this to capture errors:
// if(myRequest.status() != REQ_SUCCESS)
// {
// if(myRequest.status() != REQ_SUCCESS) {
// // An error occured.
// LOG(LogError) << "HTTP request error - " << myRequest.getErrorMessage();
// return;

View file

@ -1,7 +1,17 @@
//
// MameNames.cpp
//
// Provides expanded game names based on short MAME name arguments. Also contains
// functions to check whether a passed argument is a MAME BIOS or a MAME device.
// The data sources are stored in the .emulationstation/resources directory
// as the files mamebioses.xml, mamedevices.xml and mamenames.xml.
//
#include "MameNames.h"
#include "resources/ResourceManager.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "Log.h"
#include <pugixml/src/pugixml.hpp>
#include <string.h>
@ -10,35 +20,31 @@ MameNames* MameNames::sInstance = nullptr;
void MameNames::init()
{
if(!sInstance)
if (!sInstance)
sInstance = new MameNames();
} // init
}
void MameNames::deinit()
{
if(sInstance)
{
if (sInstance) {
delete sInstance;
sInstance = nullptr;
}
} // deinit
}
MameNames* MameNames::getInstance()
{
if(!sInstance)
if (!sInstance)
sInstance = new MameNames();
return sInstance;
} // getInstance
}
MameNames::MameNames()
{
std::string xmlpath = ResourceManager::getInstance()->getResourcePath(":/mamenames.xml");
if(!Utils::FileSystem::exists(xmlpath))
if (!Utils::FileSystem::exists(xmlpath))
return;
LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"...";
@ -46,115 +52,123 @@ MameNames::MameNames()
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(xmlpath.c_str());
if(!result)
{
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description();
if (!result) {
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n "
<< result.description();
return;
}
for(pugi::xml_node gameNode = doc.child("game"); gameNode; gameNode = gameNode.next_sibling("game"))
{
NamePair namePair = { gameNode.child("mamename").text().get(), gameNode.child("realname").text().get() };
for (pugi::xml_node gameNode = doc.child("game");
gameNode; gameNode = gameNode.next_sibling("game")) {
NamePair namePair = {
gameNode.child("mamename").text().get(),
gameNode.child("realname").text().get()
};
mNamePairs.push_back(namePair);
}
// Read bios
// Read BIOS file.
xmlpath = ResourceManager::getInstance()->getResourcePath(":/mamebioses.xml");
if(!Utils::FileSystem::exists(xmlpath))
if (!Utils::FileSystem::exists(xmlpath))
return;
LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"...";
result = doc.load_file(xmlpath.c_str());
if(!result)
{
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description();
if (!result) {
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n "
<< result.description();
return;
}
for(pugi::xml_node biosNode = doc.child("bios"); biosNode; biosNode = biosNode.next_sibling("bios"))
{
for (pugi::xml_node biosNode = doc.child("bios");
biosNode; biosNode = biosNode.next_sibling("bios")) {
std::string bios = biosNode.text().get();
mMameBioses.push_back(bios);
}
// Read devices
// Read devices file.
xmlpath = ResourceManager::getInstance()->getResourcePath(":/mamedevices.xml");
if(!Utils::FileSystem::exists(xmlpath))
if (!Utils::FileSystem::exists(xmlpath))
return;
LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"...";
result = doc.load_file(xmlpath.c_str());
if(!result)
{
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description();
if (!result) {
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n "
<< result.description();
return;
}
for(pugi::xml_node deviceNode = doc.child("device"); deviceNode; deviceNode = deviceNode.next_sibling("device"))
{
for (pugi::xml_node deviceNode = doc.child("device");
deviceNode; deviceNode = deviceNode.next_sibling("device")) {
std::string device = deviceNode.text().get();
mMameDevices.push_back(device);
}
} // MameNames
} // MameNames.
MameNames::~MameNames()
{
} // ~MameNames
}
std::string MameNames::getRealName(const std::string& _mameName)
{
size_t start = 0;
size_t end = mNamePairs.size();
size_t end = mNamePairs.size();
while(start < end)
{
const size_t index = (start + end) / 2;
const int compare = strcmp(mNamePairs[index].mameName.c_str(), _mameName.c_str());
while (start < end) {
const size_t index = (start + end) / 2;
const int compare = strcmp(mNamePairs[index].mameName.c_str(), _mameName.c_str());
if(compare < 0) start = index + 1;
else if( compare > 0) end = index;
else return mNamePairs[index].realName;
if (compare < 0)
start = index + 1;
else if (compare > 0)
end = index;
else
return mNamePairs[index].realName;
}
return _mameName;
}
} // getRealName
std::string MameNames::getCleanName(const std::string& _mameName)
{
std::string cleanName = Utils::String::removeParenthesis(getRealName(_mameName));
return cleanName;
}
const bool MameNames::isBios(const std::string& _biosName)
{
return MameNames::find(mMameBioses, _biosName);
} // isBios
}
const bool MameNames::isDevice(const std::string& _deviceName)
{
return MameNames::find(mMameDevices, _deviceName);
} // isDevice
}
const bool MameNames::find(std::vector<std::string> devices, const std::string& name)
{
size_t start = 0;
size_t end = devices.size();
size_t end = devices.size();
while(start < end)
{
const size_t index = (start + end) / 2;
const int compare = strcmp(devices[index].c_str(), name.c_str());
while (start < end) {
const size_t index = (start + end) / 2;
const int compare = strcmp(devices[index].c_str(), name.c_str());
if(compare < 0) start = index + 1;
else if( compare > 0) end = index;
else return true;
if (compare < 0)
start = index + 1;
else if (compare > 0)
end = index;
else
return true;
}
return false;
}

View file

@ -1,3 +1,12 @@
//
// MameNames.h
//
// Provides expanded game names based on short MAME name arguments. Also contains
// functions to check whether a passed argument is a MAME BIOS or a MAME device.
// The data sources are stored in the .emulationstation/resources directory
// as the files mamebioses.xml, mamedevices.xml and mamenames.xml.
//
#pragma once
#ifndef ES_CORE_MAMENAMES_H
#define ES_CORE_MAMENAMES_H
@ -5,6 +14,7 @@
#include <string>
#include <vector>
// Expand MAME names to full game names.
class MameNames
{
public:
@ -13,13 +23,13 @@ public:
static void deinit ();
static MameNames* getInstance();
std::string getRealName(const std::string& _mameName);
std::string getCleanName(const std::string& _mameName);
const bool isBios(const std::string& _biosName);
const bool isDevice(const std::string& _deviceName);
private:
struct NamePair
{
struct NamePair {
std::string mameName;
std::string realName;
};

View file

@ -132,7 +132,19 @@ void Settings::setDefaults()
// Scraper.
mStringMap["Scraper"] = "ScreenScraper";
mStringMap["ScraperRegion"] = "eu";
mStringMap["ScraperLanguage"] = "en";
// mBoolMap["ScraperGenerateMiximages"] = false;
// mBoolMap["ScraperGenerateThumbnails"] = false;
mBoolMap["ScraperInteractive"] = true;
mBoolMap["ScraperOverwriteData"] = false;
mBoolMap["ScrapeMetadata"] = true;
mBoolMap["ScrapeGameNames"] = true;
mBoolMap["ScrapeRatings"] = true;
mBoolMap["Scrape3DBoxes"] = true;
mBoolMap["ScrapeCovers"] = true;
mBoolMap["ScrapeMarquees"] = true;
mBoolMap["ScrapeScreenshots"] = true;
// Other settings.
#ifdef _RPI_
@ -189,6 +201,16 @@ void Settings::setDefaults()
mIntMap["ScreenOffsetY"] = 0;
mIntMap["ScreenRotate"] = 0;
//
// Settings that can be changed in es_settings.cfg
// but that are not configurable via the GUI (yet).
//
mStringMap["DefaultSortOrder"] = "filename, ascending";
mStringMap["MediaDirectory"] = "";
mIntMap["ScraperResizeWidth"] = 600;
mIntMap["ScraperResizeHeight"] = 0;
//
// Hardcoded or program-internal settings.
//
@ -197,10 +219,6 @@ void Settings::setDefaults()
mBoolMap["DebugGrid"] = false;
mBoolMap["DebugText"] = false;
mBoolMap["DebugImage"] = false;
mStringMap["DefaultSortOrder"] = "filename, ascending";
mStringMap["MediaDirectory"] = "";
mIntMap["ScraperResizeWidth"] = 400;
mIntMap["ScraperResizeHeight"] = 0;
mBoolMap["SplashScreenProgress"] = true;
mStringMap["UIMode_passkey"] = "uuddlrlrba";
}

View file

@ -5,6 +5,8 @@
#include "Settings.h"
#include "ThemeData.h"
NavigationSounds* NavigationSounds::sInstance = nullptr;
std::map< std::string, std::shared_ptr<Sound> > Sound::sMap;
std::shared_ptr<Sound> Sound::get(const std::string& path)
@ -26,79 +28,41 @@ std::shared_ptr<Sound> Sound::getFromTheme(const std::shared_ptr<ThemeData>& the
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "sound");
if(!elem || !elem->has("path"))
{
LOG(LogInfo) << "[" << element << "] not found, can't play sound file";
LOG(LogInfo) << "[" << element << "] not found, won't load any sound file";
return get("");
}
LOG(LogInfo) << "[" << element << "] found, ready to play sound file";
LOG(LogInfo) << "[" << element << "] found, ready to load sound file";
return get(elem->get<std::string>("path"));
}
NavigationSounds* NavigationSounds::getInstance()
{
if (sInstance == NULL)
sInstance = new NavigationSounds();
return sInstance;
}
void NavigationSounds::loadThemeNavigationSounds(const std::shared_ptr<ThemeData>& theme)
{
systembrowseSound = Sound::getFromTheme(theme, "all", "systembrowseSound");
quicksysselectSound = Sound::getFromTheme(theme, "all", "quicksysselectSound");
selectSound = Sound::getFromTheme(theme, "all", "selectSound");
backSound = Sound::getFromTheme(theme, "all", "backSound");
scrollSound = Sound::getFromTheme(theme, "all", "scrollSound");
favoriteSound = Sound::getFromTheme(theme, "all", "favoriteSound");
launchSound = Sound::getFromTheme(theme, "all", "launchSound");
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "systembrowseSound"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "quicksysselectSound"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "selectSound"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "backSound"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "scrollSound"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "favoriteSound"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "launchSound"));
}
void NavigationSounds::playThemeNavigationSound(NavigationSoundsID soundID)
{
switch(soundID)
{
case SYSTEMBROWSESOUND:
navigationsounds.systembrowseSound->play();
break;
case QUICKSYSSELECTSOUND:
navigationsounds.quicksysselectSound->play();
break;
case SELECTSOUND:
navigationsounds.selectSound->play();
break;
case BACKSOUND:
navigationsounds.backSound->play();
break;
case SCROLLSOUND:
navigationsounds.scrollSound->play();
break;
case FAVORITESOUND:
navigationsounds.favoriteSound->play();
break;
case LAUNCHSOUND:
navigationsounds.launchSound->play();
}
NavigationSounds::getInstance()->navigationSounds[soundID]->play();
}
bool NavigationSounds::isPlayingThemeNavigationSound(NavigationSoundsID soundID)
{
switch(soundID)
{
case SYSTEMBROWSESOUND:
return navigationsounds.systembrowseSound->isPlaying();
break;
case QUICKSYSSELECTSOUND:
return navigationsounds.quicksysselectSound->isPlaying();
break;
case SELECTSOUND:
return navigationsounds.selectSound->isPlaying();
break;
case BACKSOUND:
return navigationsounds.backSound->isPlaying();
break;
case SCROLLSOUND:
return navigationsounds.scrollSound->isPlaying();
break;
case FAVORITESOUND:
return navigationsounds.favoriteSound->isPlaying();
break;
case LAUNCHSOUND:
return navigationsounds.launchSound->isPlaying();
}
return false;
return NavigationSounds::getInstance()->navigationSounds[soundID]->isPlaying();
}
Sound::Sound(const std::string & path) : mSampleData(NULL), mSamplePos(0), mSampleLength(0), playing(false)
@ -130,7 +94,7 @@ void Sound::init()
Uint8 * data = NULL;
Uint32 dlen = 0;
if (SDL_LoadWAV(mPath.c_str(), &wave, &data, &dlen) == NULL) {
LOG(LogError) << "Error loading sound \"" << mPath << "\"!\n" << " " << SDL_GetError();
LOG(LogError) << "Error loading sound file \"" << mPath << "\"!\n" << " " << SDL_GetError();
return;
}
//build conversion buffer

View file

@ -5,6 +5,7 @@
#include "SDL_audio.h"
#include <map>
#include <memory>
#include <vector>
class ThemeData;
@ -57,18 +58,15 @@ enum NavigationSoundsID
class NavigationSounds
{
public:
static NavigationSounds* getInstance();
void loadThemeNavigationSounds(const std::shared_ptr<ThemeData>& theme);
void playThemeNavigationSound(NavigationSoundsID soundID);
bool isPlayingThemeNavigationSound(NavigationSoundsID soundID);
private:
std::shared_ptr<Sound> systembrowseSound;
std::shared_ptr<Sound> quicksysselectSound;
std::shared_ptr<Sound> selectSound;
std::shared_ptr<Sound> backSound;
std::shared_ptr<Sound> scrollSound;
std::shared_ptr<Sound> favoriteSound;
std::shared_ptr<Sound> launchSound;
static NavigationSounds* sInstance;
std::vector<std::shared_ptr<Sound>> navigationSounds;
};
extern NavigationSounds navigationsounds;

View file

@ -1,6 +1,7 @@
#include "components/MenuComponent.h"
#include "components/ButtonComponent.h"
#include "Settings.h"
#define BUTTON_GRID_VERT_PADDING 32
#define BUTTON_GRID_HORIZ_PADDING 10
@ -32,6 +33,22 @@ MenuComponent::MenuComponent(Window* window, const char* title, const std::share
mGrid.resetCursor();
}
MenuComponent::~MenuComponent()
{
save();
}
void MenuComponent::save()
{
if(!mSaveFuncs.size())
return;
for(auto it = mSaveFuncs.cbegin(); it != mSaveFuncs.cend(); it++)
(*it)();
Settings::getInstance()->saveFile();
}
void MenuComponent::setTitle(const char* title, const std::shared_ptr<Font>& font)
{
mTitle->setText(Utils::String::toUpper(title));

View file

@ -20,7 +20,9 @@ class MenuComponent : public GuiComponent
{
public:
MenuComponent(Window* window, const char* title, const std::shared_ptr<Font>& titleFont = Font::get(FONT_SIZE_LARGE));
virtual ~MenuComponent(); // just calls save();
void save();
void onSizeChanged() override;
inline void addRow(const ComponentListRow& row, bool setCursorHere = false) { mList->addRow(row, setCursorHere); updateSize(); }
@ -33,6 +35,8 @@ public:
addRow(row, setCursorHere);
}
inline void addSaveFunc(const std::function<void()>& func) { mSaveFuncs.push_back(func); };
void addButton(const std::string& label, const std::string& helpText, const std::function<void()>& callback);
void setTitle(const char* title, const std::shared_ptr<Font>& font);
@ -54,6 +58,7 @@ private:
std::shared_ptr<ComponentList> mList;
std::shared_ptr<ComponentGrid> mButtonGrid;
std::vector< std::shared_ptr<ButtonComponent> > mButtons;
std::vector< std::function<void()> > mSaveFuncs;
};
#endif // ES_CORE_COMPONENTS_MENU_COMPONENT_H

View file

@ -250,6 +250,30 @@ public:
onSelectedChanged();
}
bool selectEntry(unsigned int entry)
{
if (entry > mEntries.size()) {
return false;
}
else {
mEntries.at(entry).selected = true;
onSelectedChanged();
return true;
}
}
bool unselectEntry(unsigned int entry)
{
if (entry > mEntries.size()) {
return false;
}
else {
mEntries.at(entry).selected = false;
onSelectedChanged();
return true;
}
}
void selectAll()
{
for(unsigned int i = 0; i < mEntries.size(); i++)