mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-22 14:15:38 +00:00
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:
parent
d85ad49523
commit
90735d44e3
|
@ -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
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -38,7 +38,8 @@ SystemData::SystemData(
|
|||
mEnvData(envData),
|
||||
mThemeFolder(themeFolder),
|
||||
mIsCollectionSystem(CollectionSystem),
|
||||
mIsGameSystem(true)
|
||||
mIsGameSystem(true),
|
||||
mScrapeFlag(false)
|
||||
{
|
||||
mFilterIndex = new FileFilterIndex();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
319
es-app/src/guis/GuiScraperMenu.cpp
Normal file
319
es-app/src/guis/GuiScraperMenu.cpp
Normal 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;
|
||||
}
|
53
es-app/src/guis/GuiScraperMenu.h
Normal file
53
es-app/src/guis/GuiScraperMenu.h
Normal 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
|
|
@ -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", [&] {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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++)
|
||||
|
|
Loading…
Reference in a new issue