mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-22 22:25: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/GuiSlideshowScreensaverOptions.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.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/GuiScraperMulti.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.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/GuiSlideshowScreensaverOptions.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.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/GuiScraperMulti.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.cpp
|
||||||
|
|
|
@ -354,7 +354,6 @@ void CollectionSystemManager::updateCollectionSystem(FileData* file, CollectionS
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ViewController::get()->onFileChanged(rootFolder, FILE_SORTED);
|
ViewController::get()->onFileChanged(rootFolder, FILE_SORTED);
|
||||||
std::string teststring1 = rootFolder->getPath();
|
|
||||||
// If it's a custom collection and the collections
|
// If it's a custom collection and the collections
|
||||||
// are grouped, update the parent instead.
|
// are grouped, update the parent instead.
|
||||||
if (sysData.decl.isCustom &&
|
if (sysData.decl.isCustom &&
|
||||||
|
|
|
@ -39,8 +39,18 @@ FileData::FileData(
|
||||||
metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA)
|
metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA)
|
||||||
{
|
{
|
||||||
// Metadata needs at least a name field (since that's what getName() will return).
|
// Metadata needs at least a name field (since that's what getName() will return).
|
||||||
if (metadata.get("name").empty())
|
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());
|
metadata.set("name", getDisplayName());
|
||||||
|
}
|
||||||
|
}
|
||||||
mSystemName = system->getName();
|
mSystemName = system->getName();
|
||||||
metadata.resetChangedFlag();
|
metadata.resetChangedFlag();
|
||||||
}
|
}
|
||||||
|
@ -88,7 +98,7 @@ const bool FileData::getFavorite()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string FileData::getMediaDirectory() const
|
const std::string FileData::getMediaDirectory()
|
||||||
{
|
{
|
||||||
std::string mediaDirSetting = Settings::getInstance()->getString("MediaDirectory");
|
std::string mediaDirSetting = Settings::getInstance()->getString("MediaDirectory");
|
||||||
std::string mediaDirPath = "";
|
std::string mediaDirPath = "";
|
||||||
|
@ -111,12 +121,13 @@ const std::string FileData::getMediaDirectory() const
|
||||||
return mediaDirPath;
|
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" };
|
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++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
std::string mediaPath = tempPath + extList[i];
|
std::string mediaPath = tempPath + extList[i];
|
||||||
if (Utils::FileSystem::exists(mediaPath))
|
if (Utils::FileSystem::exists(mediaPath))
|
||||||
|
@ -125,11 +136,10 @@ const std::string FileData::getThumbnailPath() const
|
||||||
|
|
||||||
// No media found in the media directory, so look
|
// No media found in the media directory, so look
|
||||||
// for local art as well (if configured to do so).
|
// 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++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
std::string localMediaPath = mEnvData->mStartPath + "/images/" +
|
std::string localMediaPath = mEnvData->mStartPath + "/images/" +
|
||||||
getDisplayName() + "-thumbnail" + extList[i];
|
getDisplayName() + "-" + mediatype + extList[i];
|
||||||
if (Utils::FileSystem::exists(localMediaPath))
|
if (Utils::FileSystem::exists(localMediaPath))
|
||||||
return localMediaPath;
|
return localMediaPath;
|
||||||
}
|
}
|
||||||
|
@ -138,6 +148,52 @@ const std::string FileData::getThumbnailPath() const
|
||||||
return "";
|
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 std::string FileData::getVideoPath() const
|
||||||
{
|
{
|
||||||
const char* extList[5] = { ".avi", ".mkv", ".mov", ".mp4", ".wmv" };
|
const char* extList[5] = { ".avi", ".mkv", ".mov", ".mp4", ".wmv" };
|
||||||
|
@ -155,7 +211,7 @@ const std::string FileData::getVideoPath() const
|
||||||
if (Settings::getInstance()->getBool("LocalArt"))
|
if (Settings::getInstance()->getBool("LocalArt"))
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
std::string localMediaPath = mEnvData->mStartPath + "/images/" + getDisplayName() +
|
std::string localMediaPath = mEnvData->mStartPath + "/videos/" + getDisplayName() +
|
||||||
"-video" + extList[i];
|
"-video" + extList[i];
|
||||||
if (Utils::FileSystem::exists(localMediaPath))
|
if (Utils::FileSystem::exists(localMediaPath))
|
||||||
return localMediaPath;
|
return localMediaPath;
|
||||||
|
@ -165,68 +221,6 @@ const std::string FileData::getVideoPath() const
|
||||||
return "";
|
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()
|
const std::vector<FileData*>& FileData::getChildrenListToDisplay()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -312,7 +306,6 @@ void FileData::removeChild(FileData* file)
|
||||||
|
|
||||||
// File somehow wasn't in our children.
|
// File somehow wasn't in our children.
|
||||||
assert(false);
|
assert(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileData::sort(ComparisonFunction& comparator, bool ascending)
|
void FileData::sort(ComparisonFunction& comparator, bool ascending)
|
||||||
|
|
|
@ -57,11 +57,17 @@ public:
|
||||||
inline const std::vector<FileData*>& getChildren() const { return mChildren; }
|
inline const std::vector<FileData*>& getChildren() const { return mChildren; }
|
||||||
inline SystemData* getSystem() const { return mSystem; }
|
inline SystemData* getSystem() const { return mSystem; }
|
||||||
inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; }
|
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 getThumbnailPath() const;
|
||||||
virtual const std::string getVideoPath() const;
|
virtual const std::string getVideoPath() const;
|
||||||
virtual const std::string getMarqueePath() const;
|
|
||||||
virtual const std::string getImagePath() const;
|
|
||||||
|
|
||||||
const std::vector<FileData*>& getChildrenListToDisplay();
|
const std::vector<FileData*>& getChildrenListToDisplay();
|
||||||
std::vector<FileData*> getFilesRecursive(unsigned int typeMask,
|
std::vector<FileData*> getFilesRecursive(unsigned int typeMask,
|
||||||
|
|
|
@ -12,38 +12,40 @@
|
||||||
#include <pugixml/src/pugixml.hpp>
|
#include <pugixml/src/pugixml.hpp>
|
||||||
|
|
||||||
MetaDataDecl gameDecls[] = {
|
MetaDataDecl gameDecls[] = {
|
||||||
// key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd
|
// key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd, shouldScrape
|
||||||
{"name", MD_STRING, "", false, "name", "enter game name"},
|
{"name", MD_STRING, "", false, "name", "enter game name", true},
|
||||||
{"sortname", MD_STRING, "", false, "sortname", "enter game sort name"},
|
{"sortname", MD_STRING, "", false, "sortname", "enter game sort name", false},
|
||||||
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description"},
|
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true},
|
||||||
{"rating", MD_RATING, "0.000000", false, "rating", "enter rating"},
|
{"rating", MD_RATING, "0", false, "rating", "enter rating", true},
|
||||||
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"},
|
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date", true},
|
||||||
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer"},
|
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer", true},
|
||||||
{"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher"},
|
{"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher", true},
|
||||||
{"genre", MD_STRING, "unknown", false, "genre", "enter game genre"},
|
{"genre", MD_STRING, "unknown", false, "genre", "enter game genre", true},
|
||||||
{"players", MD_INT, "1", false, "players", "enter number of players"},
|
{"players", MD_INT, "unknown", false, "players", "enter number of players", true},
|
||||||
{"favorite", MD_BOOL, "false", false, "favorite", "enter favorite off/on"},
|
{"favorite", MD_BOOL, "false", false, "favorite", "enter favorite off/on", false},
|
||||||
{"completed", MD_BOOL, "false", false, "completed", "enter completed off/on"},
|
{"completed", MD_BOOL, "false", false, "completed", "enter completed off/on", false},
|
||||||
{"hidden", MD_BOOL, "false", false, "hidden", "enter hidden off/on"},
|
{"broken", MD_BOOL, "false", false, "broken/not working", "enter broken off/on", false},
|
||||||
{"kidgame", MD_BOOL, "false", false, "kidgame", "enter kidgame off/on"},
|
{"hidden", MD_BOOL, "false", false, "hidden", "enter hidden off/on", false},
|
||||||
{"launchstring", MD_LAUNCHSTRING, "", false, "launch string", "enter game launch string (emulator override)"},
|
{"kidgame", MD_BOOL, "false", false, "kidgame", "enter kidgame off/on", false},
|
||||||
{"playcount", MD_INT, "0", false, "play count", "enter number of times played"},
|
{"launchstring", MD_LAUNCHSTRING, "", false, "launch string", "enter game launch string "
|
||||||
{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date"}
|
"(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 +
|
const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls +
|
||||||
sizeof(gameDecls) / sizeof(gameDecls[0]));
|
sizeof(gameDecls) / sizeof(gameDecls[0]));
|
||||||
|
|
||||||
MetaDataDecl folderDecls[] = {
|
MetaDataDecl folderDecls[] = {
|
||||||
{"name", MD_STRING, "", false, "name", "enter game name"},
|
{"name", MD_STRING, "", false, "name", "enter game name", true},
|
||||||
{"sortname", MD_STRING, "", false, "sortname", "enter game sort name"},
|
{"sortname", MD_STRING, "", false, "sortname", "enter game sort name", false},
|
||||||
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description"},
|
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true},
|
||||||
{"rating", MD_RATING, "0.000000", false, "rating", "enter rating"},
|
{"rating", MD_RATING, "0", false, "rating", "enter rating", true},
|
||||||
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"},
|
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date", true},
|
||||||
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer"},
|
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer", true},
|
||||||
{"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher"},
|
{"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher", true},
|
||||||
{"genre", MD_STRING, "unknown", false, "genre", "enter game genre"},
|
{"genre", MD_STRING, "unknown", false, "genre", "enter game genre", true},
|
||||||
{"players", MD_INT, "1", false, "players", "enter number of players"}
|
{"players", MD_INT, "unknown", false, "players", "enter number of players", true}
|
||||||
};
|
};
|
||||||
const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls +
|
const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls +
|
||||||
sizeof(folderDecls) / sizeof(folderDecls[0]));
|
sizeof(folderDecls) / sizeof(folderDecls[0]));
|
||||||
|
@ -78,7 +80,7 @@ MetaDataList MetaDataList::createFromXML(MetaDataListType type,
|
||||||
|
|
||||||
for (auto iter = mdd.cbegin(); iter != mdd.cend(); iter++) {
|
for (auto iter = mdd.cbegin(); iter != mdd.cend(); iter++) {
|
||||||
pugi::xml_node md = node.child(iter->key.c_str());
|
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.
|
// If it's a path, resolve relative paths.
|
||||||
std::string value = md.text().get();
|
std::string value = md.text().get();
|
||||||
if (iter->type == MD_PATH)
|
if (iter->type == MD_PATH)
|
||||||
|
|
|
@ -15,19 +15,19 @@
|
||||||
namespace pugi { class xml_node; }
|
namespace pugi { class xml_node; }
|
||||||
|
|
||||||
enum MetaDataType {
|
enum MetaDataType {
|
||||||
// Generic types
|
// Generic types.
|
||||||
MD_STRING,
|
MD_STRING,
|
||||||
MD_INT,
|
MD_INT,
|
||||||
MD_FLOAT,
|
MD_FLOAT,
|
||||||
MD_BOOL,
|
MD_BOOL,
|
||||||
|
|
||||||
// Specialized types
|
// Specialized types.
|
||||||
MD_MULTILINE_STRING,
|
MD_MULTILINE_STRING,
|
||||||
MD_LAUNCHSTRING,
|
MD_LAUNCHSTRING,
|
||||||
MD_PATH,
|
MD_PATH,
|
||||||
MD_RATING,
|
MD_RATING,
|
||||||
MD_DATE,
|
MD_DATE,
|
||||||
MD_TIME // Used for lastplayed
|
MD_TIME // Used for lastplayed.
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MetaDataDecl {
|
struct MetaDataDecl {
|
||||||
|
@ -40,6 +40,8 @@ struct MetaDataDecl {
|
||||||
std::string displayName;
|
std::string displayName;
|
||||||
// Phrase displayed in editors when prompted to enter value (currently only for strings).
|
// Phrase displayed in editors when prompted to enter value (currently only for strings).
|
||||||
std::string displayPrompt;
|
std::string displayPrompt;
|
||||||
|
// If set to false, the scraper will not overwrite this metadata.
|
||||||
|
bool shouldScrape;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum MetaDataListType {
|
enum MetaDataListType {
|
||||||
|
|
|
@ -38,7 +38,8 @@ SystemData::SystemData(
|
||||||
mEnvData(envData),
|
mEnvData(envData),
|
||||||
mThemeFolder(themeFolder),
|
mThemeFolder(themeFolder),
|
||||||
mIsCollectionSystem(CollectionSystem),
|
mIsCollectionSystem(CollectionSystem),
|
||||||
mIsGameSystem(true)
|
mIsGameSystem(true),
|
||||||
|
mScrapeFlag(false)
|
||||||
{
|
{
|
||||||
mFilterIndex = new FileFilterIndex();
|
mFilterIndex = new FileFilterIndex();
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,8 @@ public:
|
||||||
|
|
||||||
unsigned int getGameCount() const;
|
unsigned int getGameCount() const;
|
||||||
unsigned int getDisplayedGameCount() const;
|
unsigned int getDisplayedGameCount() const;
|
||||||
|
bool getScrapeFlag() { return mScrapeFlag; };
|
||||||
|
void setScrapeFlag(bool scrapeflag) { mScrapeFlag = scrapeflag; }
|
||||||
|
|
||||||
static void deleteSystems();
|
static void deleteSystems();
|
||||||
// Load the system config file at getConfigPath().
|
// Load the system config file at getConfigPath().
|
||||||
|
@ -100,6 +102,7 @@ public:
|
||||||
private:
|
private:
|
||||||
bool mIsCollectionSystem;
|
bool mIsCollectionSystem;
|
||||||
bool mIsGameSystem;
|
bool mIsGameSystem;
|
||||||
|
bool mScrapeFlag; // Only used by scraper GUI to remember which systems to scrape.
|
||||||
std::string mName;
|
std::string mName;
|
||||||
std::string mFullName;
|
std::string mFullName;
|
||||||
SystemEnvironmentData* mEnvData;
|
SystemEnvironmentData* mEnvData;
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
// User interface component for the scraper where the user is able to see an overview
|
// 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.
|
// 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
|
// 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
|
// This component is called from GuiGameScraper for single-game scraping and
|
||||||
// from GuiScraperMulti for multi-game scraping.
|
// from GuiScraperMulti for multi-game scraping.
|
||||||
|
@ -22,6 +23,9 @@
|
||||||
#include "guis/GuiTextEditPopup.h"
|
#include "guis/GuiTextEditPopup.h"
|
||||||
#include "resources/Font.h"
|
#include "resources/Font.h"
|
||||||
#include "utils/StringUtil.h"
|
#include "utils/StringUtil.h"
|
||||||
|
#include "PlatformId.h"
|
||||||
|
#include "MameNames.h"
|
||||||
|
#include "SystemData.h"
|
||||||
#include "FileData.h"
|
#include "FileData.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "Window.h"
|
#include "Window.h"
|
||||||
|
@ -69,6 +73,8 @@ ScraperSearchComponent::ScraperSearchComponent(
|
||||||
mMD_Genre = std::make_shared<TextComponent>(mWindow, "", font, mdColor);
|
mMD_Genre = std::make_shared<TextComponent>(mWindow, "", font, mdColor);
|
||||||
mMD_Players = std::make_shared<TextComponent>(mWindow, "", font, mdColor);
|
mMD_Players = std::make_shared<TextComponent>(mWindow, "", font, mdColor);
|
||||||
|
|
||||||
|
if (Settings::getInstance()->getBool("ScrapeRatings") &&
|
||||||
|
Settings::getInstance()->getString("Scraper") != "TheGamesDB")
|
||||||
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>
|
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>
|
||||||
(mWindow, "RATING:", font, mdLblColor), mMD_Rating, false));
|
(mWindow, "RATING:", font, mdLblColor), mMD_Rating, false));
|
||||||
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>
|
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent>
|
||||||
|
@ -144,7 +150,7 @@ void ScraperSearchComponent::onSizeChanged()
|
||||||
mGrid.getColWidth(2), mResultDesc->getFont()->getHeight() * 3);
|
mGrid.getColWidth(2), mResultDesc->getFont()->getHeight() * 3);
|
||||||
else
|
else
|
||||||
mDescContainer->setSize(mGrid.getColWidth(3)*boxartCellScale,
|
mDescContainer->setSize(mGrid.getColWidth(3)*boxartCellScale,
|
||||||
mResultDesc->getFont()->getHeight() * 8);
|
mResultDesc->getFont()->getHeight() * 7);
|
||||||
|
|
||||||
// Make description text wrap at edge of container.
|
// Make description text wrap at edge of container.
|
||||||
mResultDesc->setSize(mDescContainer->getSize().x(), 0);
|
mResultDesc->setSize(mDescContainer->getSize().x(), 0);
|
||||||
|
@ -184,9 +190,12 @@ void ScraperSearchComponent::resizeMetadata()
|
||||||
|
|
||||||
mMD_Grid->setColWidthPerc(0, maxLblWidth / mMD_Grid->getSize().x());
|
mMD_Grid->setColWidthPerc(0, maxLblWidth / mMD_Grid->getSize().x());
|
||||||
|
|
||||||
|
if (Settings::getInstance()->getBool("ScrapeRatings") &&
|
||||||
|
Settings::getInstance()->getString("Scraper") != "TheGamesDB") {
|
||||||
// Rating is manually sized.
|
// Rating is manually sized.
|
||||||
mMD_Rating->setSize(mMD_Grid->getColWidth(1), fontLbl->getHeight() * 0.65f);
|
mMD_Rating->setSize(mMD_Grid->getColWidth(1), fontLbl->getHeight() * 0.65f);
|
||||||
mMD_Grid->onSizeChanged();
|
mMD_Grid->onSizeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
// Make result font follow label font.
|
// Make result font follow label font.
|
||||||
mResultDesc->setFont(Font::get(fontHeight, FONT_PATH_REGULAR));
|
mResultDesc->setFont(Font::get(fontHeight, FONT_PATH_REGULAR));
|
||||||
|
@ -239,6 +248,7 @@ void ScraperSearchComponent::search(const ScraperSearchParams& params)
|
||||||
|
|
||||||
mResultList->clear();
|
mResultList->clear();
|
||||||
mScraperResults.clear();
|
mScraperResults.clear();
|
||||||
|
mMDRetrieveURLsHandle.reset();
|
||||||
mThumbnailReq.reset();
|
mThumbnailReq.reset();
|
||||||
mMDResolveHandle.reset();
|
mMDResolveHandle.reset();
|
||||||
updateInfoPane();
|
updateInfoPane();
|
||||||
|
@ -252,6 +262,7 @@ void ScraperSearchComponent::stop()
|
||||||
mThumbnailReq.reset();
|
mThumbnailReq.reset();
|
||||||
mSearchHandle.reset();
|
mSearchHandle.reset();
|
||||||
mMDResolveHandle.reset();
|
mMDResolveHandle.reset();
|
||||||
|
mMDRetrieveURLsHandle.reset();
|
||||||
mBlockAccept = false;
|
mBlockAccept = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,15 +308,18 @@ void ScraperSearchComponent::onSearchDone(const std::vector<ScraperSearchResult>
|
||||||
mBlockAccept = false;
|
mBlockAccept = false;
|
||||||
updateInfoPane();
|
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 (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) {
|
||||||
|
if (mScraperResults.size() == 0 || (mScraperResults.size() > 0 &&
|
||||||
|
mScraperResults.front().ThumbnailImageUrl == "")) {
|
||||||
if (mScraperResults.size() == 0)
|
if (mScraperResults.size() == 0)
|
||||||
mSkipCallback();
|
mSkipCallback();
|
||||||
else
|
else
|
||||||
returnResult(mScraperResults.front());
|
returnResult(mScraperResults.front());
|
||||||
}
|
}
|
||||||
else if (mSearchType == ALWAYS_ACCEPT_MATCHING_CRC) {
|
|
||||||
// TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScraperSearchComponent::onSearchError(const std::string& error)
|
void ScraperSearchComponent::onSearchError(const std::string& error)
|
||||||
|
@ -338,13 +352,34 @@ void ScraperSearchComponent::updateInfoPane()
|
||||||
mDescContainer->reset();
|
mDescContainer->reset();
|
||||||
|
|
||||||
mResultThumbnail->setImage("");
|
mResultThumbnail->setImage("");
|
||||||
const std::string& thumb = res.thumbnailUrl.empty() ? res.imageUrl : res.thumbnailUrl;
|
const std::string& thumb = res.screenshotUrl.empty() ? res.coverUrl : res.screenshotUrl;
|
||||||
if (!thumb.empty())
|
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));
|
mThumbnailReq = std::unique_ptr<HttpReq>(new HttpReq(thumb));
|
||||||
else
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
mThumbnailReq.reset();
|
mThumbnailReq.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Metadata.
|
// Metadata.
|
||||||
|
if (Settings::getInstance()->getBool("ScrapeRatings") &&
|
||||||
|
Settings::getInstance()->getString("Scraper") != "TheGamesDB")
|
||||||
mMD_Rating->setValue(Utils::String::toUpper(res.mdl.get("rating")));
|
mMD_Rating->setValue(Utils::String::toUpper(res.mdl.get("rating")));
|
||||||
mMD_ReleaseDate->setValue(Utils::String::toUpper(res.mdl.get("releasedate")));
|
mMD_ReleaseDate->setValue(Utils::String::toUpper(res.mdl.get("releasedate")));
|
||||||
mMD_Developer->setText(Utils::String::toUpper(res.mdl.get("developer")));
|
mMD_Developer->setText(Utils::String::toUpper(res.mdl.get("developer")));
|
||||||
|
@ -359,6 +394,8 @@ void ScraperSearchComponent::updateInfoPane()
|
||||||
mResultThumbnail->setImage("");
|
mResultThumbnail->setImage("");
|
||||||
|
|
||||||
// Metadata.
|
// Metadata.
|
||||||
|
if (Settings::getInstance()->getBool("ScrapeRatings") &&
|
||||||
|
Settings::getInstance()->getString("Scraper") != "TheGamesDB")
|
||||||
mMD_Rating->setValue("");
|
mMD_Rating->setValue("");
|
||||||
mMD_ReleaseDate->setValue("");
|
mMD_ReleaseDate->setValue("");
|
||||||
mMD_Developer->setText("");
|
mMD_Developer->setText("");
|
||||||
|
@ -397,7 +434,8 @@ void ScraperSearchComponent::returnResult(ScraperSearchResult result)
|
||||||
mBlockAccept = true;
|
mBlockAccept = true;
|
||||||
|
|
||||||
// Resolve metadata image before returning.
|
// Resolve metadata image before returning.
|
||||||
if (!result.imageUrl.empty()) {
|
if (result.mediaFilesDownloadStatus != COMPLETED) {
|
||||||
|
result.mediaFilesDownloadStatus = IN_PROGRESS;
|
||||||
mMDResolveHandle = resolveMetaDataAssets(result, mLastSearch);
|
mMDResolveHandle = resolveMetaDataAssets(result, mLastSearch);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -417,22 +455,69 @@ void ScraperSearchComponent::update(int deltaTime)
|
||||||
|
|
||||||
if (mSearchHandle && mSearchHandle->status() != ASYNC_IN_PROGRESS) {
|
if (mSearchHandle && mSearchHandle->status() != ASYNC_IN_PROGRESS) {
|
||||||
auto status = mSearchHandle->status();
|
auto status = mSearchHandle->status();
|
||||||
auto results = mSearchHandle->getResults();
|
mScraperResults = mSearchHandle->getResults();
|
||||||
auto statusString = mSearchHandle->getStatusString();
|
auto statusString = mSearchHandle->getStatusString();
|
||||||
|
|
||||||
// We reset here because onSearchDone in auto mode can call mSkipCallback() which
|
// We reset here because onSearchDone in auto mode can call mSkipCallback() which
|
||||||
// can call another search() which will set our mSearchHandle to something important.
|
// can call another search() which will set our mSearchHandle to something important.
|
||||||
mSearchHandle.reset();
|
mSearchHandle.reset();
|
||||||
|
|
||||||
if (status == ASYNC_DONE)
|
if (status == ASYNC_DONE && mScraperResults.size() == 0)
|
||||||
onSearchDone(results);
|
onSearchDone(mScraperResults);
|
||||||
else if (status == ASYNC_ERROR)
|
|
||||||
|
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);
|
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 && mMDResolveHandle->status() != ASYNC_IN_PROGRESS) {
|
||||||
if (mMDResolveHandle->status() == ASYNC_DONE) {
|
if (mMDResolveHandle->status() == ASYNC_DONE) {
|
||||||
ScraperSearchResult result = mMDResolveHandle->getResult();
|
ScraperSearchResult result = mMDResolveHandle->getResult();
|
||||||
|
result.mediaFilesDownloadStatus = COMPLETED;
|
||||||
mMDResolveHandle.reset();
|
mMDResolveHandle.reset();
|
||||||
// This might end in us being deleted, depending on mAcceptCallback -
|
// This might end in us being deleted, depending on mAcceptCallback -
|
||||||
// so make sure this is the last thing we do in update().
|
// so make sure this is the last thing we do in update().
|
||||||
|
@ -448,6 +533,15 @@ void ScraperSearchComponent::update(int deltaTime)
|
||||||
void ScraperSearchComponent::updateThumbnail()
|
void ScraperSearchComponent::updateThumbnail()
|
||||||
{
|
{
|
||||||
if (mThumbnailReq && mThumbnailReq->status() == HttpReq::REQ_SUCCESS) {
|
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();
|
std::string content = mThumbnailReq->getContent();
|
||||||
mResultThumbnail->setImage(content.data(), content.length());
|
mResultThumbnail->setImage(content.data(), content.length());
|
||||||
mGrid.onSizeChanged(); // A hack to fix the thumbnail position since its size changed.
|
mGrid.onSizeChanged(); // A hack to fix the thumbnail position since its size changed.
|
||||||
|
@ -458,6 +552,21 @@ void ScraperSearchComponent::updateThumbnail()
|
||||||
}
|
}
|
||||||
|
|
||||||
mThumbnailReq.reset();
|
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)
|
void ScraperSearchComponent::openInputScreen(ScraperSearchParams& params)
|
||||||
|
@ -468,10 +577,78 @@ void ScraperSearchComponent::openInputScreen(ScraperSearchParams& params)
|
||||||
};
|
};
|
||||||
|
|
||||||
stop();
|
stop();
|
||||||
|
|
||||||
|
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",
|
mWindow->pushGui(new GuiTextEditPopup(mWindow, "SEARCH FOR",
|
||||||
// Initial value is last search if there was one, otherwise the clean path name.
|
// Initial value is last search if there was one, otherwise the clean path name.
|
||||||
params.nameOverride.empty() ? params.game->getCleanName() : params.nameOverride,
|
params.nameOverride.empty() ? params.game->getCleanName() : params.nameOverride,
|
||||||
searchForFunc, false, "SEARCH"));
|
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()
|
std::vector<HelpPrompt> ScraperSearchComponent::getHelpPrompts()
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
// User interface component for the scraper where the user is able to see an overview
|
// 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.
|
// 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
|
// 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
|
// This component is called from GuiGameScraper for single-game scraping and
|
||||||
// from GuiScraperMulti for multi-game scraping.
|
// from GuiScraperMulti for multi-game scraping.
|
||||||
|
@ -41,6 +42,7 @@ public:
|
||||||
void openInputScreen(ScraperSearchParams& from);
|
void openInputScreen(ScraperSearchParams& from);
|
||||||
void stop();
|
void stop();
|
||||||
inline SearchType getSearchType() const { return mSearchType; }
|
inline SearchType getSearchType() const { return mSearchType; }
|
||||||
|
static bool saveMetadata(const ScraperSearchResult& result, MetaDataList& metadata);
|
||||||
|
|
||||||
// Metadata assets will be resolved before calling the accept callback
|
// Metadata assets will be resolved before calling the accept callback
|
||||||
// (e.g. result.mdl's "image" is automatically downloaded and properly set).
|
// (e.g. result.mdl's "image" is automatically downloaded and properly set).
|
||||||
|
@ -71,6 +73,10 @@ private:
|
||||||
|
|
||||||
int getSelectedIndex();
|
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.
|
// Resolve any metadata assets that need to be downloaded and return.
|
||||||
void returnResult(ScraperSearchResult result);
|
void returnResult(ScraperSearchResult result);
|
||||||
|
|
||||||
|
@ -111,6 +117,7 @@ private:
|
||||||
bool mBlockAccept;
|
bool mBlockAccept;
|
||||||
|
|
||||||
std::unique_ptr<ScraperSearchHandle> mSearchHandle;
|
std::unique_ptr<ScraperSearchHandle> mSearchHandle;
|
||||||
|
std::unique_ptr<ScraperSearchHandle> mMDRetrieveURLsHandle;
|
||||||
std::unique_ptr<MDResolveHandle> mMDResolveHandle;
|
std::unique_ptr<MDResolveHandle> mMDResolveHandle;
|
||||||
std::vector<ScraperSearchResult> mScraperResults;
|
std::vector<ScraperSearchResult> mScraperResults;
|
||||||
std::unique_ptr<HttpReq> mThumbnailReq;
|
std::unique_ptr<HttpReq> mThumbnailReq;
|
||||||
|
|
|
@ -80,7 +80,8 @@ public:
|
||||||
inline void setLineSpacing(float lineSpacing) { mLineSpacing = lineSpacing; }
|
inline void setLineSpacing(float lineSpacing) { mLineSpacing = lineSpacing; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void onScroll() { navigationsounds.playThemeNavigationSound(SCROLLSOUND); }
|
virtual void onScroll() {
|
||||||
|
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); }
|
||||||
virtual void onCursorChanged(const CursorState& state);
|
virtual void onCursorChanged(const CursorState& state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -103,7 +103,7 @@ GuiGamelistOptions::GuiGamelistOptions(
|
||||||
row.addElement(mJumpToLetterList, false);
|
row.addElement(mJumpToLetterList, false);
|
||||||
row.input_handler = [&](InputConfig* config, Input input) {
|
row.input_handler = [&](InputConfig* config, Input input) {
|
||||||
if (config->isMappedTo("a", input) && input.value) {
|
if (config->isMappedTo("a", input) && input.value) {
|
||||||
navigationsounds.playThemeNavigationSound(SCROLLSOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
|
||||||
if (mJumpToLetterList->getSelected() == FAVORITE_CHAR)
|
if (mJumpToLetterList->getSelected() == FAVORITE_CHAR)
|
||||||
jumpToFirstRow();
|
jumpToFirstRow();
|
||||||
else
|
else
|
||||||
|
@ -196,7 +196,7 @@ GuiGamelistOptions::~GuiGamelistOptions()
|
||||||
|
|
||||||
// If a new sorting type was selected, then sort and update mSortTypeString for the system.
|
// If a new sorting type was selected, then sort and update mSortTypeString for the system.
|
||||||
if ((*mListSort->getSelected()).description != root->getSortTypeString()) {
|
if ((*mListSort->getSelected()).description != root->getSortTypeString()) {
|
||||||
navigationsounds.playThemeNavigationSound(SCROLLSOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
|
||||||
|
|
||||||
// This will also recursively sort children.
|
// This will also recursively sort children.
|
||||||
root->sort(*mListSort->getSelected(), mFavoritesSorting);
|
root->sort(*mListSort->getSelected(), mFavoritesSorting);
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
#include "guis/GuiDetectDevice.h"
|
#include "guis/GuiDetectDevice.h"
|
||||||
#include "guis/GuiGeneralScreensaverOptions.h"
|
#include "guis/GuiGeneralScreensaverOptions.h"
|
||||||
#include "guis/GuiMsgBox.h"
|
#include "guis/GuiMsgBox.h"
|
||||||
#include "guis/GuiScraperStart.h"
|
#include "guis/GuiScraperMenu.h"
|
||||||
#include "guis/GuiSettings.h"
|
#include "guis/GuiSettings.h"
|
||||||
#include "views/UIModeController.h"
|
#include "views/UIModeController.h"
|
||||||
#include "views/ViewController.h"
|
#include "views/ViewController.h"
|
||||||
|
@ -36,6 +36,9 @@ GuiMenu::GuiMenu(
|
||||||
{
|
{
|
||||||
bool isFullUI = UIModeController::getInstance()->isUIModeFull();
|
bool isFullUI = UIModeController::getInstance()->isUIModeFull();
|
||||||
|
|
||||||
|
if (isFullUI)
|
||||||
|
addEntry("SCRAPER", 0x777777FF, true, [this] { openScraperSettings(); });
|
||||||
|
|
||||||
if (isFullUI)
|
if (isFullUI)
|
||||||
addEntry("UI SETTINGS", 0x777777FF, true, [this] { openUISettings(); });
|
addEntry("UI SETTINGS", 0x777777FF, true, [this] { openUISettings(); });
|
||||||
|
|
||||||
|
@ -45,9 +48,6 @@ GuiMenu::GuiMenu(
|
||||||
addEntry("GAME COLLECTION SETTINGS", 0x777777FF, true, [this] {
|
addEntry("GAME COLLECTION SETTINGS", 0x777777FF, true, [this] {
|
||||||
openCollectionSystemSettings(); });
|
openCollectionSystemSettings(); });
|
||||||
|
|
||||||
if (isFullUI)
|
|
||||||
addEntry("SCRAPER", 0x777777FF, true, [this] { openScraperSettings(); });
|
|
||||||
|
|
||||||
if (isFullUI)
|
if (isFullUI)
|
||||||
addEntry("OTHER SETTINGS", 0x777777FF, true, [this] { openOtherSettings(); });
|
addEntry("OTHER SETTINGS", 0x777777FF, true, [this] { openOtherSettings(); });
|
||||||
|
|
||||||
|
@ -65,44 +65,8 @@ GuiMenu::GuiMenu(
|
||||||
|
|
||||||
void GuiMenu::openScraperSettings()
|
void GuiMenu::openScraperSettings()
|
||||||
{
|
{
|
||||||
auto s = new GuiSettings(mWindow, "SCRAPER");
|
// Open the scrape menu.
|
||||||
|
mWindow->pushGui(new GuiScraperMenu(mWindow));
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiMenu::openSoundSettings()
|
void GuiMenu::openSoundSettings()
|
||||||
|
@ -174,28 +138,6 @@ void GuiMenu::openSoundSettings()
|
||||||
});
|
});
|
||||||
#endif
|
#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_
|
#ifdef _RPI_
|
||||||
// OMX player Audio Device
|
// OMX player Audio Device
|
||||||
auto omx_audio_dev = std::make_shared< OptionListComponent<std::string>
|
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());
|
Settings::getInstance()->setString("OMXAudioDev", omx_audio_dev->getSelected());
|
||||||
});
|
});
|
||||||
#endif
|
#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);
|
mWindow->pushGui(s);
|
||||||
|
@ -281,9 +244,9 @@ void GuiMenu::openUISettings()
|
||||||
getString("TransitionStyle") == *it);
|
getString("TransitionStyle") == *it);
|
||||||
s->addWithLabel("TRANSITION STYLE", transition_style);
|
s->addWithLabel("TRANSITION STYLE", transition_style);
|
||||||
s->addSaveFunc([transition_style] {
|
s->addSaveFunc([transition_style] {
|
||||||
if (Settings::getInstance()->getString("TransitionStyle") == "instant"
|
if (Settings::getInstance()->getString("TransitionStyle") == "instant" &&
|
||||||
&& transition_style->getSelected() != "instant"
|
transition_style->getSelected() != "instant" &&
|
||||||
&& PowerSaver::getMode() == PowerSaver::INSTANT) {
|
PowerSaver::getMode() == PowerSaver::INSTANT) {
|
||||||
Settings::getInstance()->setString("PowerSaverMode", "default");
|
Settings::getInstance()->setString("PowerSaverMode", "default");
|
||||||
PowerSaver::init();
|
PowerSaver::init();
|
||||||
}
|
}
|
||||||
|
@ -315,8 +278,7 @@ void GuiMenu::openUISettings()
|
||||||
|
|
||||||
Settings::getInstance()->setString("ThemeSet", theme_set->getSelected());
|
Settings::getInstance()->setString("ThemeSet", theme_set->getSelected());
|
||||||
|
|
||||||
if (needReload)
|
if (needReload) {
|
||||||
{
|
|
||||||
Scripting::fireEvent("theme-changed", theme_set->getSelected(), oldTheme);
|
Scripting::fireEvent("theme-changed", theme_set->getSelected(), oldTheme);
|
||||||
CollectionSystemManager::get()->updateSystemsList();
|
CollectionSystemManager::get()->updateSystemsList();
|
||||||
ViewController::get()->goToStart();
|
ViewController::get()->goToStart();
|
||||||
|
@ -404,9 +366,9 @@ void GuiMenu::openUISettings()
|
||||||
move_carousel->setState(Settings::getInstance()->getBool("MoveCarousel"));
|
move_carousel->setState(Settings::getInstance()->getBool("MoveCarousel"));
|
||||||
s->addWithLabel("CAROUSEL TRANSITIONS", move_carousel);
|
s->addWithLabel("CAROUSEL TRANSITIONS", move_carousel);
|
||||||
s->addSaveFunc([move_carousel] {
|
s->addSaveFunc([move_carousel] {
|
||||||
if (move_carousel->getState()
|
if (move_carousel->getState() &&
|
||||||
&& !Settings::getInstance()->getBool("MoveCarousel")
|
!Settings::getInstance()->getBool("MoveCarousel") &&
|
||||||
&& PowerSaver::getMode() == PowerSaver::INSTANT) {
|
PowerSaver::getMode() == PowerSaver::INSTANT) {
|
||||||
Settings::getInstance()->setString("PowerSaverMode", "default");
|
Settings::getInstance()->setString("PowerSaverMode", "default");
|
||||||
PowerSaver::init();
|
PowerSaver::init();
|
||||||
}
|
}
|
||||||
|
@ -460,8 +422,8 @@ void GuiMenu::openOtherSettings()
|
||||||
fullscreen_mode->add(*it, *it, Settings::getInstance()->getString("FullscreenMode") == *it);
|
fullscreen_mode->add(*it, *it, Settings::getInstance()->getString("FullscreenMode") == *it);
|
||||||
s->addWithLabel("FULLSCREEN MODE (REQUIRES RESTART)", fullscreen_mode);
|
s->addWithLabel("FULLSCREEN MODE (REQUIRES RESTART)", fullscreen_mode);
|
||||||
s->addSaveFunc([fullscreen_mode] {
|
s->addSaveFunc([fullscreen_mode] {
|
||||||
if (Settings::getInstance()->getString("FullscreenMode") == "normal"
|
if (Settings::getInstance()->getString("FullscreenMode") == "normal" &&
|
||||||
&& fullscreen_mode->getSelected() != "normal") {
|
fullscreen_mode->getSelected() != "normal") {
|
||||||
Settings::getInstance()->setString("PowerSaverMode", "default");
|
Settings::getInstance()->setString("PowerSaverMode", "default");
|
||||||
PowerSaver::init();
|
PowerSaver::init();
|
||||||
}
|
}
|
||||||
|
@ -542,7 +504,7 @@ void GuiMenu::openOtherSettings()
|
||||||
|
|
||||||
auto local_art = std::make_shared<SwitchComponent>(mWindow);
|
auto local_art = std::make_shared<SwitchComponent>(mWindow);
|
||||||
local_art->setState(Settings::getInstance()->getBool("LocalArt"));
|
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()->
|
s->addSaveFunc([local_art] { Settings::getInstance()->
|
||||||
setBool("LocalArt", local_art->getState()); });
|
setBool("LocalArt", local_art->getState()); });
|
||||||
|
|
||||||
|
@ -668,11 +630,8 @@ void GuiMenu::onSizeChanged()
|
||||||
mVersion.setPosition(0, mSize.y() - mVersion.getSize().y());
|
mVersion.setPosition(0, mSize.y() - mVersion.getSize().y());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiMenu::addEntry(
|
void GuiMenu::addEntry(const char* name, unsigned int color,
|
||||||
const char* name,
|
bool add_arrow, const std::function<void()>& func)
|
||||||
unsigned int color,
|
|
||||||
bool add_arrow,
|
|
||||||
const std::function<void()>& func)
|
|
||||||
{
|
{
|
||||||
std::shared_ptr<Font> font = Font::get(FONT_SIZE_MEDIUM);
|
std::shared_ptr<Font> font = Font::get(FONT_SIZE_MEDIUM);
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,8 @@ GuiMetaDataEd::GuiMetaDataEd(
|
||||||
mMetaDataDecl(mdd),
|
mMetaDataDecl(mdd),
|
||||||
mMetaData(md),
|
mMetaData(md),
|
||||||
mSavedCallback(saveCallback),
|
mSavedCallback(saveCallback),
|
||||||
mDeleteFunc(deleteFunc)
|
mDeleteFunc(deleteFunc),
|
||||||
|
mMetadataUpdated(false)
|
||||||
{
|
{
|
||||||
addChild(&mBackground);
|
addChild(&mBackground);
|
||||||
addChild(&mGrid);
|
addChild(&mGrid);
|
||||||
|
@ -82,7 +83,6 @@ GuiMetaDataEd::GuiMetaDataEd(
|
||||||
assert(ed);
|
assert(ed);
|
||||||
ed->setValue(mMetaData->get(iter->key));
|
ed->setValue(mMetaData->get(iter->key));
|
||||||
mEditors.push_back(ed);
|
mEditors.push_back(ed);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,22 +282,41 @@ void GuiMetaDataEd::fetch()
|
||||||
|
|
||||||
void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
|
void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
|
||||||
{
|
{
|
||||||
for (unsigned int i = 0; i < mEditors.size(); i++) {
|
// Clone the mMetaData object.
|
||||||
if (mMetaDataDecl.at(i).isStatistic)
|
MetaDataList* metadata = nullptr;
|
||||||
continue;
|
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;
|
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)
|
void GuiMetaDataEd::close(bool closeAllWindows)
|
||||||
{
|
{
|
||||||
// Find out if the user made any changes.
|
// Find out if the user made any changes.
|
||||||
bool dirty = false;
|
bool dirty = mMetadataUpdated;
|
||||||
for (unsigned int i = 0; i < mEditors.size(); i++) {
|
for (unsigned int i = 0; i < mEditors.size(); i++) {
|
||||||
const std::string& key = mMetaDataDecl.at(i).key;
|
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;
|
dirty = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -315,7 +334,6 @@ void GuiMetaDataEd::close(bool closeAllWindows)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (dirty)
|
if (dirty)
|
||||||
{
|
{
|
||||||
// Changes were made, ask if the user wants to save them.
|
// Changes were made, ask if the user wants to save them.
|
||||||
|
|
|
@ -58,6 +58,8 @@ private:
|
||||||
MetaDataList* mMetaData;
|
MetaDataList* mMetaData;
|
||||||
std::function<void()> mSavedCallback;
|
std::function<void()> mSavedCallback;
|
||||||
std::function<void()> mDeleteFunc;
|
std::function<void()> mDeleteFunc;
|
||||||
|
|
||||||
|
bool mMetadataUpdated;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ES_APP_GUIS_GUI_META_DATA_ED_H
|
#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.
|
// Multiple game scraping user interface.
|
||||||
// Shows the progress for the scraping as it's running.
|
// 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.
|
// ScraperSearchComponent is called from here.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
@ -136,7 +136,8 @@ void GuiScraperMulti::acceptResult(const ScraperSearchResult& result)
|
||||||
{
|
{
|
||||||
ScraperSearchParams& search = mSearchQueue.front();
|
ScraperSearchParams& search = mSearchQueue.front();
|
||||||
|
|
||||||
search.game->metadata = result.mdl;
|
ScraperSearchComponent::saveMetadata(result, search.game->metadata);
|
||||||
|
|
||||||
updateGamelist(search.system);
|
updateGamelist(search.system);
|
||||||
|
|
||||||
mSearchQueue.pop();
|
mSearchQueue.pop();
|
||||||
|
@ -157,15 +158,15 @@ void GuiScraperMulti::finish()
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
if (mTotalSuccessful == 0) {
|
if (mTotalSuccessful == 0) {
|
||||||
ss << "NO GAMES WERE SCRAPED.";
|
ss << "NO GAMES WERE SCRAPED";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ss << mTotalSuccessful << " GAME" << ((mTotalSuccessful > 1) ? "S" : "") <<
|
ss << mTotalSuccessful << " GAME" << ((mTotalSuccessful > 1) ? "S" : "") <<
|
||||||
" SUCCESSFULLY SCRAPED!";
|
" SUCCESSFULLY SCRAPED";
|
||||||
|
|
||||||
if (mTotalSkipped > 0)
|
if (mTotalSkipped > 0)
|
||||||
ss << "\n" << mTotalSkipped << " GAME" << ((mTotalSkipped > 1) ? "S" : "") <<
|
ss << "\n" << mTotalSkipped << " GAME" << ((mTotalSkipped > 1) ? "S" : "") <<
|
||||||
" SKIPPED.";
|
" SKIPPED";
|
||||||
}
|
}
|
||||||
|
|
||||||
mWindow->pushGui(new GuiMsgBox(mWindow, ss.str(), "OK", [&] {
|
mWindow->pushGui(new GuiMsgBox(mWindow, ss.str(), "OK", [&] {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//
|
//
|
||||||
// Multiple game scraping user interface.
|
// Multiple game scraping user interface.
|
||||||
// Shows the progress for the scraping as it's running.
|
// 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.
|
// ScraperSearchComponent is called from here.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
#include "components/NinePatchComponent.h"
|
#include "components/NinePatchComponent.h"
|
||||||
#include "scrapers/Scraper.h"
|
#include "scrapers/Scraper.h"
|
||||||
#include "GuiComponent.h"
|
#include "GuiComponent.h"
|
||||||
|
#include "MetaData.h"
|
||||||
|
|
||||||
class ScraperSearchComponent;
|
class ScraperSearchComponent;
|
||||||
class TextComponent;
|
class TextComponent;
|
||||||
|
@ -44,6 +45,7 @@ private:
|
||||||
unsigned int mTotalSuccessful;
|
unsigned int mTotalSuccessful;
|
||||||
unsigned int mTotalSkipped;
|
unsigned int mTotalSkipped;
|
||||||
std::queue<ScraperSearchParams> mSearchQueue;
|
std::queue<ScraperSearchParams> mSearchQueue;
|
||||||
|
std::vector<MetaDataDecl> mMetaDataDecl;
|
||||||
|
|
||||||
NinePatchComponent mBackground;
|
NinePatchComponent mBackground;
|
||||||
ComponentGrid mGrid;
|
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 "PlatformId.h"
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
#include "SystemData.h"
|
#include "SystemData.h"
|
||||||
|
#include "MameNames.h"
|
||||||
#include "utils/TimeUtil.h"
|
#include "utils/TimeUtil.h"
|
||||||
#include <pugixml/src/pugixml.hpp>
|
#include <pugixml/src/pugixml.hpp>
|
||||||
|
|
||||||
|
@ -118,23 +119,30 @@ void thegamesdb_generate_json_scraper_requests(
|
||||||
std::string gameID = cleanName.substr(3);
|
std::string gameID = cleanName.substr(3);
|
||||||
path += "/Games/ByGameID?" + apiKey +
|
path += "/Games/ByGameID?" + apiKey +
|
||||||
"&fields=players,publishers,genres,overview,last_updated,rating,"
|
"&fields=players,publishers,genres,overview,last_updated,rating,"
|
||||||
"platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates&"
|
"platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates&id=" +
|
||||||
"include=boxart&id=" +
|
|
||||||
HttpReq::urlEncode(gameID);
|
HttpReq::urlEncode(gameID);
|
||||||
usingGameID = true;
|
usingGameID = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (cleanName.empty())
|
if (cleanName.empty())
|
||||||
|
// 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();
|
cleanName = params.game->getCleanName();
|
||||||
|
|
||||||
path += "/Games/ByGameName?" + apiKey +
|
path += "/Games/ByGameName?" + apiKey +
|
||||||
"&fields=players,publishers,genres,overview,last_updated,rating,"
|
"&fields=players,publishers,genres,overview,last_updated,rating,"
|
||||||
"platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates&"
|
"platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates&name=" +
|
||||||
"include=boxart&name=" +
|
|
||||||
HttpReq::urlEncode(cleanName);
|
HttpReq::urlEncode(cleanName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usingGameID) {
|
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)));
|
requests.push(std::unique_ptr<ScraperRequest>(new TheGamesDBJSONRequest(results, path)));
|
||||||
}
|
}
|
||||||
else {
|
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
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -194,21 +217,6 @@ int getIntOrThrow(const Value& v)
|
||||||
return v.GetInt();
|
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)
|
std::string getDeveloperString(const Value& v)
|
||||||
{
|
{
|
||||||
if (!v.IsArray())
|
if (!v.IsArray())
|
||||||
|
@ -274,13 +282,13 @@ std::string getGenreString(const Value& v)
|
||||||
return out;
|
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;
|
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"));
|
result.mdl.set("name", getStringOrThrow(game, "game_title"));
|
||||||
if (game.HasMember("overview") && game["overview"].IsString())
|
if (game.HasMember("overview") && game["overview"].IsString())
|
||||||
result.mdl.set("desc", game["overview"].GetString());
|
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())
|
if (game.HasMember("players") && game["players"].IsInt())
|
||||||
result.mdl.set("players", std::to_string(game["players"].GetInt()));
|
result.mdl.set("players", std::to_string(game["players"].GetInt()));
|
||||||
|
|
||||||
if (boxart.HasMember("data") && boxart["data"].IsObject()) {
|
result.mediaURLFetch = NOT_STARTED;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push_back(result);
|
results.push_back(result);
|
||||||
}
|
}
|
||||||
} // namespace
|
} // 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,
|
void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req,
|
||||||
std::vector<ScraperSearchResult>& results)
|
std::vector<ScraperSearchResult>& results)
|
||||||
{
|
{
|
||||||
|
@ -331,34 +372,58 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req,
|
||||||
return;
|
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") ||
|
if (!doc.HasMember("data") || !doc["data"].HasMember("games") ||
|
||||||
!doc["data"]["games"].IsArray()) {
|
!doc["data"]["games"].IsArray()) {
|
||||||
std::string warn = "TheGamesDBJSONRequest - Response had no game data.\n";
|
std::string warn = "TheGamesDBJSONRequest - Response had no game data.\n";
|
||||||
LOG(LogWarning) << warn;
|
LOG(LogWarning) << warn;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Value& games = doc["data"]["games"];
|
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();
|
resources.ensureResources();
|
||||||
|
|
||||||
for (int i = 0; i < (int)games.Size(); ++i) {
|
for (int i = 0; i < (int)games.Size(); ++i) {
|
||||||
auto& v = games[i];
|
auto& v = games[i];
|
||||||
try {
|
try {
|
||||||
processGame(v, boxart, results);
|
processGame(v, results);
|
||||||
}
|
}
|
||||||
catch (std::runtime_error& e) {
|
catch (std::runtime_error& e) {
|
||||||
LOG(LogError) << "Error while processing game: " << e.what();
|
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::queue<std::unique_ptr<ScraperRequest>>& requests,
|
||||||
std::vector<ScraperSearchResult>& results);
|
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
|
class TheGamesDBJSONRequest : public ScraperHttpRequest
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -42,6 +47,8 @@ class TheGamesDBJSONRequest : public ScraperHttpRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
//void retrieveMediaURLs()
|
||||||
|
|
||||||
void process(const std::unique_ptr<HttpReq>& req,
|
void process(const std::unique_ptr<HttpReq>& req,
|
||||||
std::vector<ScraperSearchResult>& results) override;
|
std::vector<ScraperSearchResult>& results) override;
|
||||||
bool isGameRequest() { return !mRequestQueue; }
|
bool isGameRequest() { return !mRequestQueue; }
|
||||||
|
|
|
@ -4,6 +4,11 @@
|
||||||
// Functions specifically for scraping from thegamesdb.net
|
// Functions specifically for scraping from thegamesdb.net
|
||||||
// Called from GamesDBJSONScraper.
|
// Called from GamesDBJSONScraper.
|
||||||
//
|
//
|
||||||
|
// Downloads these resource files to ~/.emulationstation/scrapers:
|
||||||
|
// gamesdb_developers.json
|
||||||
|
// gamesdb_genres.json
|
||||||
|
// gamesdb_publishers.json
|
||||||
|
//
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
|
@ -4,6 +4,11 @@
|
||||||
// Functions specifically for scraping from thegamesdb.net
|
// Functions specifically for scraping from thegamesdb.net
|
||||||
// Called from GamesDBJSONScraper.
|
// Called from GamesDBJSONScraper.
|
||||||
//
|
//
|
||||||
|
// Downloads these resource files to ~/.emulationstation/scrapers:
|
||||||
|
// gamesdb_developers.json
|
||||||
|
// gamesdb_genres.json
|
||||||
|
// gamesdb_publishers.json
|
||||||
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#ifndef ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_RESOURCES_H
|
#ifndef ES_APP_SCRAPERS_GAMES_DB_JSON_SCRAPER_RESOURCES_H
|
||||||
|
|
|
@ -36,10 +36,28 @@ std::unique_ptr<ScraperSearchHandle> startScraperSearch(const ScraperSearchParam
|
||||||
return handle;
|
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> getScraperList()
|
||||||
{
|
{
|
||||||
std::vector<std::string> list;
|
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);
|
list.push_back(it->first);
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
|
@ -59,36 +77,35 @@ ScraperSearchHandle::ScraperSearchHandle()
|
||||||
|
|
||||||
void ScraperSearchHandle::update()
|
void ScraperSearchHandle::update()
|
||||||
{
|
{
|
||||||
if(mStatus == ASYNC_DONE)
|
if (mStatus == ASYNC_DONE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(!mRequestQueue.empty())
|
if (!mRequestQueue.empty()) {
|
||||||
{
|
|
||||||
// A request can add more requests to the queue while running,
|
// A request can add more requests to the queue while running,
|
||||||
// so be careful with references into the queue.
|
// so be careful with references into the queue.
|
||||||
auto& req = *(mRequestQueue.front());
|
auto& req = *(mRequestQueue.front());
|
||||||
AsyncHandleStatus status = req.status();
|
AsyncHandleStatus status = req.status();
|
||||||
|
|
||||||
if(status == ASYNC_ERROR) {
|
if (status == ASYNC_ERROR) {
|
||||||
// Propagate error.
|
// Propagate error.
|
||||||
setError(req.getStatusString());
|
setError(req.getStatusString());
|
||||||
|
|
||||||
// Empty our queue.
|
// Empty our queue.
|
||||||
while(!mRequestQueue.empty())
|
while (!mRequestQueue.empty())
|
||||||
mRequestQueue.pop();
|
mRequestQueue.pop();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finished this one, see if we have any more.
|
// Finished this one, see if we have any more.
|
||||||
if(status == ASYNC_DONE)
|
if (status == ASYNC_DONE)
|
||||||
mRequestQueue.pop();
|
mRequestQueue.pop();
|
||||||
|
|
||||||
// Status == ASYNC_IN_PROGRESS.
|
// Status == ASYNC_IN_PROGRESS.
|
||||||
}
|
}
|
||||||
|
|
||||||
// We finished without any errors!
|
// We finished without any errors!
|
||||||
if(mRequestQueue.empty()) {
|
if (mRequestQueue.empty()) {
|
||||||
setStatus(ASYNC_DONE);
|
setStatus(ASYNC_DONE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -101,8 +118,8 @@ ScraperRequest::ScraperRequest(std::vector<ScraperSearchResult>& resultsWrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScraperHttpRequest.
|
// ScraperHttpRequest.
|
||||||
ScraperHttpRequest::ScraperHttpRequest(std::vector<ScraperSearchResult>&
|
ScraperHttpRequest::ScraperHttpRequest(std::vector<ScraperSearchResult>& resultsWrite,
|
||||||
resultsWrite, const std::string& url) : ScraperRequest(resultsWrite)
|
const std::string& url) : ScraperRequest(resultsWrite)
|
||||||
{
|
{
|
||||||
setStatus(ASYNC_IN_PROGRESS);
|
setStatus(ASYNC_IN_PROGRESS);
|
||||||
mReq = std::unique_ptr<HttpReq>(new HttpReq(url));
|
mReq = std::unique_ptr<HttpReq>(new HttpReq(url));
|
||||||
|
@ -111,8 +128,7 @@ ScraperHttpRequest::ScraperHttpRequest(std::vector<ScraperSearchResult>&
|
||||||
void ScraperHttpRequest::update()
|
void ScraperHttpRequest::update()
|
||||||
{
|
{
|
||||||
HttpReq::Status status = mReq->status();
|
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.
|
// If process() has an error, status will be changed to ASYNC_ERROR.
|
||||||
setStatus(ASYNC_DONE);
|
setStatus(ASYNC_DONE);
|
||||||
process(mReq, mResults);
|
process(mReq, mResults);
|
||||||
|
@ -120,7 +136,7 @@ void ScraperHttpRequest::update()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not ready yet.
|
// Not ready yet.
|
||||||
if(status == HttpReq::REQ_IN_PROGRESS)
|
if (status == HttpReq::REQ_IN_PROGRESS)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Everything else is some sort of error.
|
// Everything else is some sort of error.
|
||||||
|
@ -129,8 +145,7 @@ void ScraperHttpRequest::update()
|
||||||
setError(mReq->getErrorMsg());
|
setError(mReq->getErrorMsg());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata resolving stuff.
|
// Download and write the media files to disk.
|
||||||
|
|
||||||
std::unique_ptr<MDResolveHandle> resolveMetaDataAssets(const ScraperSearchResult& result,
|
std::unique_ptr<MDResolveHandle> resolveMetaDataAssets(const ScraperSearchResult& result,
|
||||||
const ScraperSearchParams& search)
|
const ScraperSearchParams& search)
|
||||||
{
|
{
|
||||||
|
@ -140,44 +155,124 @@ std::unique_ptr<MDResolveHandle> resolveMetaDataAssets(const ScraperSearchResult
|
||||||
MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result,
|
MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result,
|
||||||
const ScraperSearchParams& search) : mResult(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;
|
std::string ext;
|
||||||
|
|
||||||
// If we have a file extension returned by the scraper, then use it.
|
// 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.
|
// Otherwise, try to guess it by the name of the URL, which point to an image.
|
||||||
if (!result.imageType.empty()) {
|
if (!it->fileFormat.empty()) {
|
||||||
ext = result.imageType;
|
ext = it->fileFormat;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
size_t dot = result.imageUrl.find_last_of('.');
|
size_t dot = it->fileURL.find_last_of('.');
|
||||||
|
|
||||||
if (dot != std::string::npos)
|
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),
|
// If there is an existing media file on disk and the setting to overwrite data
|
||||||
[this, imgPath] {
|
// has been set to no, then don't proceed with downloading or saving a new file.
|
||||||
mResult.mdl.set("image", imgPath);
|
if (it->existingMediaFile != "" &&
|
||||||
mResult.imageUrl = "";
|
!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()
|
void MDResolveHandle::update()
|
||||||
{
|
{
|
||||||
if(mStatus == ASYNC_DONE || mStatus == ASYNC_ERROR)
|
if (mStatus == ASYNC_DONE || mStatus == ASYNC_ERROR)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto it = mFuncs.cbegin();
|
auto it = mFuncs.cbegin();
|
||||||
while(it != mFuncs.cend()) {
|
while (it != mFuncs.cend()) {
|
||||||
if(it->first->status() == ASYNC_ERROR) {
|
|
||||||
|
if (it->first->status() == ASYNC_ERROR) {
|
||||||
setError(it->first->getStatusString());
|
setError(it->first->getStatusString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if(it->first->status() == ASYNC_DONE) {
|
else if (it->first->status() == ASYNC_DONE) {
|
||||||
it->second();
|
it->second();
|
||||||
it = mFuncs.erase(it);
|
it = mFuncs.erase(it);
|
||||||
continue;
|
continue;
|
||||||
|
@ -185,30 +280,41 @@ void MDResolveHandle::update()
|
||||||
it++;
|
it++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mFuncs.empty())
|
if (mFuncs.empty())
|
||||||
setStatus(ASYNC_DONE);
|
setStatus(ASYNC_DONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ImageDownloadHandle> downloadImageAsync(const std::string& url,
|
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("ScraperResizeWidth"),
|
||||||
Settings::getInstance()->getInt("ScraperResizeHeight")));
|
Settings::getInstance()->getInt("ScraperResizeHeight")));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageDownloadHandle::ImageDownloadHandle(const std::string& url,
|
ImageDownloadHandle::ImageDownloadHandle(
|
||||||
const std::string& path, int maxWidth, int maxHeight) : mSavePath(path),
|
const std::string& url,
|
||||||
mMaxWidth(maxWidth), mMaxHeight(maxHeight), mReq(new HttpReq(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()
|
void ImageDownloadHandle::update()
|
||||||
{
|
{
|
||||||
if(mReq->status() == HttpReq::REQ_IN_PROGRESS)
|
if (mReq->status() == HttpReq::REQ_IN_PROGRESS)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(mReq->status() != HttpReq::REQ_SUCCESS) {
|
if (mReq->status() != HttpReq::REQ_SUCCESS) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Network error: " << mReq->getErrorMsg();
|
ss << "Network error: " << mReq->getErrorMsg();
|
||||||
setError(ss.str());
|
setError(ss.str());
|
||||||
|
@ -216,8 +322,16 @@ void ImageDownloadHandle::update()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download is done, save it to disk.
|
// 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);
|
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?");
|
setError("Failed to open image path to write. Permission error? Disk full?");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -225,13 +339,13 @@ void ImageDownloadHandle::update()
|
||||||
const std::string& content = mReq->getContent();
|
const std::string& content = mReq->getContent();
|
||||||
stream.write(content.data(), content.length());
|
stream.write(content.data(), content.length());
|
||||||
stream.close();
|
stream.close();
|
||||||
if(stream.bad()) {
|
if (stream.bad()) {
|
||||||
setError("Failed to save image. Disk full?");
|
setError("Failed to save image. Disk full?");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize it.
|
// Resize it.
|
||||||
if(!resizeImage(mSavePath, mMaxWidth, mMaxHeight)) {
|
if (!resizeImage(mSavePath, mMaxWidth, mMaxHeight)) {
|
||||||
setError("Error saving resized image. Out of memory? Disk full?");
|
setError("Error saving resized image. Out of memory? Disk full?");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -243,7 +357,7 @@ void ImageDownloadHandle::update()
|
||||||
bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
|
bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
|
||||||
{
|
{
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
if(maxWidth == 0 && maxHeight == 0)
|
if (maxWidth == 0 && maxHeight == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
FREE_IMAGE_FORMAT format = FIF_UNKNOWN;
|
FREE_IMAGE_FORMAT format = FIF_UNKNOWN;
|
||||||
|
@ -251,15 +365,15 @@ bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
|
||||||
|
|
||||||
// Detect the filetype.
|
// Detect the filetype.
|
||||||
format = FreeImage_GetFileType(path.c_str(), 0);
|
format = FreeImage_GetFileType(path.c_str(), 0);
|
||||||
if(format == FIF_UNKNOWN)
|
if (format == FIF_UNKNOWN)
|
||||||
format = FreeImage_GetFIFFromFilename(path.c_str());
|
format = FreeImage_GetFIFFromFilename(path.c_str());
|
||||||
if(format == FIF_UNKNOWN) {
|
if (format == FIF_UNKNOWN) {
|
||||||
LOG(LogError) << "Error - could not detect filetype for image \"" << path << "\"!";
|
LOG(LogError) << "Error - could not detect filetype for image \"" << path << "\"!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we can read this filetype first, then load it.
|
// 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());
|
image = FreeImage_Load(format, path.c_str());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -270,15 +384,20 @@ bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
|
||||||
float width = (float)FreeImage_GetWidth(image);
|
float width = (float)FreeImage_GetWidth(image);
|
||||||
float height = (float)FreeImage_GetHeight(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);
|
maxWidth = (int)((maxHeight / height) * width);
|
||||||
else if(maxHeight == 0)
|
else if (maxHeight == 0)
|
||||||
maxHeight = (int)((maxWidth / width) * height);
|
maxHeight = (int)((maxWidth / width) * height);
|
||||||
|
|
||||||
FIBITMAP* imageRescaled = FreeImage_Rescale(image, maxWidth, maxHeight, FILTER_BILINEAR);
|
FIBITMAP* imageRescaled = FreeImage_Rescale(image, maxWidth, maxHeight, FILTER_BILINEAR);
|
||||||
FreeImage_Unload(image);
|
FreeImage_Unload(image);
|
||||||
|
|
||||||
if(imageRescaled == NULL) {
|
if (imageRescaled == NULL) {
|
||||||
LOG(LogError) << "Could not resize image! (not enough memory? invalid bitdepth?)";
|
LOG(LogError) << "Could not resize image! (not enough memory? invalid bitdepth?)";
|
||||||
return false;
|
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);
|
bool saved = (FreeImage_Save(format, imageRescaled, path.c_str()) != 0);
|
||||||
FreeImage_Unload(imageRescaled);
|
FreeImage_Unload(imageRescaled);
|
||||||
|
|
||||||
if(!saved)
|
if (!saved)
|
||||||
LOG(LogError) << "Failed to save resized image!";
|
LOG(LogError) << "Failed to save resized image!";
|
||||||
|
|
||||||
return saved;
|
return saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getSaveAsPath(const ScraperSearchParams& params,
|
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 systemsubdirectory = params.system->getName();
|
||||||
const std::string name = Utils::FileSystem::getStem(params.game->getPath()) + "-" + suffix;
|
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);
|
Utils::FileSystem::createDirectory(path);
|
||||||
|
|
||||||
path += subdirectory + "/";
|
path += systemsubdirectory + "/" + filetypeSubdirectory + "/";
|
||||||
|
|
||||||
if(!Utils::FileSystem::exists(path))
|
if (!Utils::FileSystem::exists(path))
|
||||||
Utils::FileSystem::createDirectory(path);
|
Utils::FileSystem::createDirectory(path);
|
||||||
|
|
||||||
path += name + extension;
|
path += name + extension;
|
||||||
|
|
|
@ -24,6 +24,12 @@
|
||||||
class FileData;
|
class FileData;
|
||||||
class SystemData;
|
class SystemData;
|
||||||
|
|
||||||
|
enum eDownloadStatus {
|
||||||
|
NOT_STARTED,
|
||||||
|
IN_PROGRESS,
|
||||||
|
COMPLETED
|
||||||
|
};
|
||||||
|
|
||||||
struct ScraperSearchParams {
|
struct ScraperSearchParams {
|
||||||
SystemData* system;
|
SystemData* system;
|
||||||
FileData* game;
|
FileData* game;
|
||||||
|
@ -35,11 +41,29 @@ struct ScraperSearchResult {
|
||||||
ScraperSearchResult() : mdl(GAME_METADATA) {};
|
ScraperSearchResult() : mdl(GAME_METADATA) {};
|
||||||
|
|
||||||
MetaDataList mdl;
|
MetaDataList mdl;
|
||||||
std::string imageUrl;
|
std::string gameID;
|
||||||
std::string thumbnailUrl;
|
|
||||||
|
// 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.
|
// 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.
|
// So let me explain why I've abstracted this so heavily.
|
||||||
|
@ -83,7 +107,6 @@ protected:
|
||||||
std::vector<ScraperSearchResult>& mResults;
|
std::vector<ScraperSearchResult>& mResults;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// A single HTTP request that needs to be processed to get the results.
|
// A single HTTP request that needs to be processed to get the results.
|
||||||
class ScraperHttpRequest : public ScraperRequest
|
class ScraperHttpRequest : public ScraperRequest
|
||||||
{
|
{
|
||||||
|
@ -113,6 +136,9 @@ protected:
|
||||||
friend std::unique_ptr<ScraperSearchHandle>
|
friend std::unique_ptr<ScraperSearchHandle>
|
||||||
startScraperSearch(const ScraperSearchParams& params);
|
startScraperSearch(const ScraperSearchParams& params);
|
||||||
|
|
||||||
|
friend std::unique_ptr<ScraperSearchHandle>
|
||||||
|
startMediaURLsFetch(const std::string& gameIDs);
|
||||||
|
|
||||||
std::queue< std::unique_ptr<ScraperRequest> > mRequestQueue;
|
std::queue< std::unique_ptr<ScraperRequest> > mRequestQueue;
|
||||||
std::vector<ScraperSearchResult> mResults;
|
std::vector<ScraperSearchResult> mResults;
|
||||||
};
|
};
|
||||||
|
@ -120,6 +146,8 @@ protected:
|
||||||
// Will use the current scraper settings to pick the result source.
|
// Will use the current scraper settings to pick the result source.
|
||||||
std::unique_ptr<ScraperSearchHandle> startScraperSearch(const ScraperSearchParams& params);
|
std::unique_ptr<ScraperSearchHandle> startScraperSearch(const ScraperSearchParams& params);
|
||||||
|
|
||||||
|
std::unique_ptr<ScraperSearchHandle> startMediaURLsFetch(const std::string& gameIDs);
|
||||||
|
|
||||||
// Returns a list of valid scraper names.
|
// Returns a list of valid scraper names.
|
||||||
std::vector<std::string> getScraperList();
|
std::vector<std::string> getScraperList();
|
||||||
|
|
||||||
|
@ -127,7 +155,7 @@ std::vector<std::string> getScraperList();
|
||||||
bool isValidConfiguredScraper();
|
bool isValidConfiguredScraper();
|
||||||
|
|
||||||
typedef void (*generate_scraper_requests_func)(const ScraperSearchParams& params,
|
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);
|
std::vector<ScraperSearchResult>& results);
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
@ -145,21 +173,26 @@ public:
|
||||||
private:
|
private:
|
||||||
ScraperSearchResult mResult;
|
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;
|
std::vector<ResolvePair> mFuncs;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ImageDownloadHandle : public AsyncHandle
|
class ImageDownloadHandle : public AsyncHandle
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ImageDownloadHandle(const std::string& url, const std::string& path,
|
ImageDownloadHandle(
|
||||||
int maxWidth, int maxHeight);
|
const std::string& url,
|
||||||
|
const std::string& path,
|
||||||
|
const std::string& existingMediaPath,
|
||||||
|
int maxWidth,
|
||||||
|
int maxHeight);
|
||||||
|
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<HttpReq> mReq;
|
std::unique_ptr<HttpReq> mReq;
|
||||||
std::string mSavePath;
|
std::string mSavePath;
|
||||||
|
std::string mExistingMediaFile;
|
||||||
int mMaxWidth;
|
int mMaxWidth;
|
||||||
int mMaxHeight;
|
int mMaxHeight;
|
||||||
};
|
};
|
||||||
|
@ -167,13 +200,13 @@ private:
|
||||||
// About the same as:
|
// About the same as:
|
||||||
// "~/.emulationstation/downloaded_images/[system_name]/[game_name].[url's extension]".
|
// "~/.emulationstation/downloaded_images/[system_name]/[game_name].[url's extension]".
|
||||||
// Will create the "downloaded_images" and "subdirectory" directories if they do not exist.
|
// Will create the "downloaded_images" and "subdirectory" directories if they do not exist.
|
||||||
std::string getSaveAsPath(const ScraperSearchParams& params, const std::string& suffix,
|
std::string getSaveAsPath(const ScraperSearchParams& params,
|
||||||
const std::string& url);
|
const std::string& filetypeSubdirectory, const std::string& url);
|
||||||
|
|
||||||
// Will resize according to Settings::getInt("ScraperResizeWidth") and
|
// Will resize according to Settings::getInt("ScraperResizeWidth") and
|
||||||
// Settings::getInt("ScraperResizeHeight").
|
// Settings::getInt("ScraperResizeHeight").
|
||||||
std::unique_ptr<ImageDownloadHandle> downloadImageAsync(const std::string& url,
|
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.
|
// Resolves all metadata assets that need to be downloaded.
|
||||||
std::unique_ptr<MDResolveHandle> resolveMetaDataAssets(const ScraperSearchResult& result,
|
std::unique_ptr<MDResolveHandle> resolveMetaDataAssets(const ScraperSearchResult& result,
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "PlatformId.h"
|
#include "PlatformId.h"
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
#include "SystemData.h"
|
#include "SystemData.h"
|
||||||
|
#include "math/Misc.h"
|
||||||
#include <pugixml/src/pugixml.hpp>
|
#include <pugixml/src/pugixml.hpp>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
@ -132,7 +133,11 @@ void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
|
||||||
|
|
||||||
ScreenScraperRequest::ScreenScraperConfig ssConfig;
|
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();
|
auto& platforms = params.system->getPlatformIds();
|
||||||
std::vector<unsigned short> p_ids;
|
std::vector<unsigned short> p_ids;
|
||||||
|
|
||||||
|
@ -164,7 +169,6 @@ void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
|
||||||
requests.push(std::unique_ptr<ScraperRequest>
|
requests.push(std::unique_ptr<ScraperRequest>
|
||||||
(new ScreenScraperRequest(requests, results, path)));
|
(new ScreenScraperRequest(requests, results, path)));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenScraperRequest::process(const std::unique_ptr<HttpReq>& req,
|
void ScreenScraperRequest::process(const std::unique_ptr<HttpReq>& req,
|
||||||
|
@ -200,8 +204,20 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
|
||||||
ScraperSearchResult result;
|
ScraperSearchResult result;
|
||||||
ScreenScraperRequest::ScreenScraperConfig ssConfig;
|
ScreenScraperRequest::ScreenScraperConfig ssConfig;
|
||||||
|
|
||||||
std::string region = Utils::String::toLower(ssConfig.region).c_str();
|
result.gameID = game.attribute("id").as_string();
|
||||||
std::string language = Utils::String::toLower(ssConfig.language).c_str();
|
|
||||||
|
// 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[*] ).
|
// Name fallback: US, WOR(LD). ( Xpath: Data/jeu[0]/noms/nom[*] ).
|
||||||
result.mdl.set("name", find_child_by_attribute_list(game.child("noms"),
|
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.
|
// Players.
|
||||||
result.mdl.set("players", game.child("joueurs").text().get());
|
result.mdl.set("players", game.child("joueurs").text().get());
|
||||||
|
|
||||||
// TODO: Validate rating.
|
// Validate rating.
|
||||||
if (Settings::getInstance()->getBool("ScrapeRatings") && game.child("note")) {
|
if (Settings::getInstance()->getBool("ScrapeRatings") && game.child("note")) {
|
||||||
float ratingVal = (game.child("note").text().as_int() / 20.0f);
|
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;
|
std::stringstream ss;
|
||||||
ss << ratingVal;
|
ss << ratingVal;
|
||||||
result.mdl.set("rating", ss.str());
|
result.mdl.set("rating", ss.str());
|
||||||
|
@ -263,6 +281,32 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
|
||||||
pugi::xml_node media_list = game.child("medias");
|
pugi::xml_node media_list = game.child("medias");
|
||||||
|
|
||||||
if (media_list) {
|
if (media_list) {
|
||||||
|
// 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.
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
pugi::xml_node art = pugi::xml_node(NULL);
|
||||||
|
|
||||||
// Do an XPath query for media[type='$media_type'], then filter by region.
|
// Do an XPath query for media[type='$media_type'], then filter by region.
|
||||||
|
@ -270,12 +314,12 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
|
||||||
// <media type="..." region="..." format="...">
|
// <media type="..." region="..." format="...">
|
||||||
// and we need to find the right media for the region.
|
// and we need to find the right media for the region.
|
||||||
pugi::xpath_node_set results = media_list.select_nodes((static_cast<std::string>
|
pugi::xpath_node_set results = media_list.select_nodes((static_cast<std::string>
|
||||||
("media[@type='") + ssConfig.media_name + "']").c_str());
|
("media[@type='") + mediaType + "']").c_str());
|
||||||
|
|
||||||
if (results.size()) {
|
if (results.size()) {
|
||||||
// Region fallback: WOR(LD), US, CUS(TOM?), JP, EU.
|
// Region fallback: WOR(LD), US, CUS(TOM?), JP, EU.
|
||||||
for (auto _region : std::vector<std::string>{ region,
|
for (auto _region : std::vector<std::string>{
|
||||||
"wor", "us", "cus", "jp", "eu" }) {
|
region, "wor", "us", "cus", "jp", "eu" }) {
|
||||||
if (art)
|
if (art)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -286,29 +330,21 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // Results.
|
}
|
||||||
|
|
||||||
if (art) {
|
if (art) {
|
||||||
// Sending a 'softname' containing space will make the image URLs returned
|
// 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
|
// by the API also contain the space. Escape any spaces in the URL here.
|
||||||
result.imageUrl = Utils::String::replace(art.text().get(), " ", "%20");
|
fileURL = Utils::String::replace(art.text().get(), " ", "%20");
|
||||||
|
|
||||||
// Get the media type returned by ScreenScraper.
|
// Get the media type returned by ScreenScraper.
|
||||||
std::string media_type = art.attribute("format").value();
|
std::string media_type = art.attribute("format").value();
|
||||||
if (!media_type.empty())
|
if (!media_type.empty())
|
||||||
result.imageType = "." + media_type;
|
fileFormat = "." + 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 {
|
else {
|
||||||
LOG(LogDebug) << "Failed to find media XML node with name=" << ssConfig.media_name;
|
LOG(LogDebug) << "Failed to find media XML node with name=" << mediaType;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
out_results.push_back(result);
|
|
||||||
} // Game.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currently not used in this module.
|
// 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"
|
// Note that not all games contain values for all these, so we default to "box-2D"
|
||||||
// since it's the most common.
|
// 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.
|
// Which Region to use when selecting the artwork.
|
||||||
// Applies to: artwork, name of the game, date of release.
|
// 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.
|
// Which Language to use when selecting the textual information.
|
||||||
// Applies to: description, genre.
|
// Applies to: description, genre.
|
||||||
std::string language = "EN";
|
// This is read from es_settings.cfg, setting 'ScraperLanguage'.
|
||||||
|
|
||||||
ScreenScraperConfig() {};
|
ScreenScraperConfig() {};
|
||||||
} configuration;
|
} configuration;
|
||||||
|
@ -81,6 +85,12 @@ protected:
|
||||||
|
|
||||||
void processList(const pugi::xml_document& xmldoc, std::vector<ScraperSearchResult>& results);
|
void processList(const pugi::xml_document& xmldoc, std::vector<ScraperSearchResult>& results);
|
||||||
void processGame(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; }
|
bool isGameRequest() { return !mRequestQueue; }
|
||||||
|
|
||||||
std::queue< std::unique_ptr<ScraperRequest> >* mRequestQueue;
|
std::queue< std::unique_ptr<ScraperRequest> >* mRequestQueue;
|
||||||
|
|
|
@ -155,13 +155,13 @@ bool SystemView::input(InputConfig* config, Input input)
|
||||||
case VERTICAL_WHEEL:
|
case VERTICAL_WHEEL:
|
||||||
if (config->isMappedLike("up", input))
|
if (config->isMappedLike("up", input))
|
||||||
{
|
{
|
||||||
navigationsounds.playThemeNavigationSound(SYSTEMBROWSESOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND);
|
||||||
listInput(-1);
|
listInput(-1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (config->isMappedLike("down", input))
|
if (config->isMappedLike("down", input))
|
||||||
{
|
{
|
||||||
navigationsounds.playThemeNavigationSound(SYSTEMBROWSESOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND);
|
||||||
listInput(1);
|
listInput(1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -171,13 +171,13 @@ bool SystemView::input(InputConfig* config, Input input)
|
||||||
default:
|
default:
|
||||||
if (config->isMappedLike("left", input))
|
if (config->isMappedLike("left", input))
|
||||||
{
|
{
|
||||||
navigationsounds.playThemeNavigationSound(SYSTEMBROWSESOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND);
|
||||||
listInput(-1);
|
listInput(-1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (config->isMappedLike("right", input))
|
if (config->isMappedLike("right", input))
|
||||||
{
|
{
|
||||||
navigationsounds.playThemeNavigationSound(SYSTEMBROWSESOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND);
|
||||||
listInput(1);
|
listInput(1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -188,14 +188,14 @@ bool SystemView::input(InputConfig* config, Input input)
|
||||||
{
|
{
|
||||||
stopScrolling();
|
stopScrolling();
|
||||||
ViewController::get()->goToGameList(getSelected());
|
ViewController::get()->goToGameList(getSelected());
|
||||||
navigationsounds.playThemeNavigationSound(SELECTSOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(SELECTSOUND);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (config->isMappedTo("x", input))
|
if (config->isMappedTo("x", input))
|
||||||
{
|
{
|
||||||
// get random system
|
// get random system
|
||||||
// go to system
|
// go to system
|
||||||
navigationsounds.playThemeNavigationSound(SYSTEMBROWSESOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND);
|
||||||
setCursor(SystemData::getRandomSystem());
|
setCursor(SystemData::getRandomSystem());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
#include "Sound.h"
|
#include "Sound.h"
|
||||||
|
|
||||||
ViewController* ViewController::sInstance = nullptr;
|
ViewController* ViewController::sInstance = nullptr;
|
||||||
NavigationSounds navigationsounds;
|
|
||||||
|
|
||||||
ViewController* ViewController::get()
|
ViewController* ViewController::get()
|
||||||
{
|
{
|
||||||
|
@ -117,7 +116,7 @@ void ViewController::goToNextGameList()
|
||||||
assert(mState.viewing == GAME_LIST);
|
assert(mState.viewing == GAME_LIST);
|
||||||
SystemData* system = getState().getSystem();
|
SystemData* system = getState().getSystem();
|
||||||
assert(system);
|
assert(system);
|
||||||
navigationsounds.playThemeNavigationSound(QUICKSYSSELECTSOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(QUICKSYSSELECTSOUND);
|
||||||
goToGameList(system->getNext());
|
goToGameList(system->getNext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +125,7 @@ void ViewController::goToPrevGameList()
|
||||||
assert(mState.viewing == GAME_LIST);
|
assert(mState.viewing == GAME_LIST);
|
||||||
SystemData* system = getState().getSystem();
|
SystemData* system = getState().getSystem();
|
||||||
assert(system);
|
assert(system);
|
||||||
navigationsounds.playThemeNavigationSound(QUICKSYSSELECTSOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(QUICKSYSSELECTSOUND);
|
||||||
goToGameList(system->getPrev());
|
goToGameList(system->getPrev());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,9 +238,9 @@ void ViewController::launch(FileData* game, Vector3f center)
|
||||||
|
|
||||||
std::string transition_style = Settings::getInstance()->getString("TransitionStyle");
|
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.
|
// Let launch sound play to the end before launching game.
|
||||||
while(navigationsounds.isPlayingThemeNavigationSound(LAUNCHSOUND));
|
while (NavigationSounds::getInstance()->isPlayingThemeNavigationSound(LAUNCHSOUND));
|
||||||
|
|
||||||
if (transition_style == "fade") {
|
if (transition_style == "fade") {
|
||||||
// Fade out, launch game, fade back in.
|
// Fade out, launch game, fade back in.
|
||||||
|
@ -472,7 +471,8 @@ void ViewController::preload()
|
||||||
getGameListView(*it);
|
getGameListView(*it);
|
||||||
}
|
}
|
||||||
// Load navigation sounds.
|
// Load navigation sounds.
|
||||||
navigationsounds.loadThemeNavigationSounds(SystemData::sSystemVector[0]->getTheme());
|
NavigationSounds::getInstance()->loadThemeNavigationSounds(
|
||||||
|
SystemData::sSystemVector.front()->getTheme());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
|
void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
|
||||||
|
@ -539,7 +539,8 @@ void ViewController::reloadAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load navigation sounds.
|
// Load navigation sounds.
|
||||||
navigationsounds.loadThemeNavigationSounds(SystemData::sSystemVector[0]->getTheme());
|
NavigationSounds::getInstance()->loadThemeNavigationSounds(
|
||||||
|
SystemData::sSystemVector.front()->getTheme());
|
||||||
|
|
||||||
updateHelpPrompts();
|
updateHelpPrompts();
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,7 +176,7 @@ bool GridGameListView::input(InputConfig* config, Input input)
|
||||||
config->isMappedLike("right", input) ||
|
config->isMappedLike("right", input) ||
|
||||||
(config->isMappedLike("up", input)) ||
|
(config->isMappedLike("up", input)) ||
|
||||||
(config->isMappedLike("down", input)) ))
|
(config->isMappedLike("down", input)) ))
|
||||||
navigationsounds.playThemeNavigationSound(SCROLLSOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
|
||||||
|
|
||||||
if (config->isMappedLike("left", input) || config->isMappedLike("right", input))
|
if (config->isMappedLike("left", input) || config->isMappedLike("right", input))
|
||||||
return GuiComponent::input(config, input);
|
return GuiComponent::input(config, input);
|
||||||
|
|
|
@ -98,7 +98,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
|
||||||
else {
|
else {
|
||||||
// It's a folder.
|
// It's a folder.
|
||||||
if (cursor->getChildren().size() > 0) {
|
if (cursor->getChildren().size() > 0) {
|
||||||
navigationsounds.playThemeNavigationSound(SELECTSOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(SELECTSOUND);
|
||||||
mCursorStack.push(cursor);
|
mCursorStack.push(cursor);
|
||||||
populateList(cursor->getChildrenListToDisplay());
|
populateList(cursor->getChildrenListToDisplay());
|
||||||
FileData* cursor = getCursor();
|
FileData* cursor = getCursor();
|
||||||
|
@ -110,13 +110,13 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
|
||||||
}
|
}
|
||||||
else if (config->isMappedTo("b", input)) {
|
else if (config->isMappedTo("b", input)) {
|
||||||
if (mCursorStack.size()) {
|
if (mCursorStack.size()) {
|
||||||
navigationsounds.playThemeNavigationSound(BACKSOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(BACKSOUND);
|
||||||
populateList(mCursorStack.top()->getParent()->getChildren());
|
populateList(mCursorStack.top()->getParent()->getChildren());
|
||||||
setCursor(mCursorStack.top());
|
setCursor(mCursorStack.top());
|
||||||
mCursorStack.pop();
|
mCursorStack.pop();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
navigationsounds.playThemeNavigationSound(BACKSOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(BACKSOUND);
|
||||||
onFocusLost();
|
onFocusLost();
|
||||||
SystemData* systemToView = getCursor()->getSystem();
|
SystemData* systemToView = getCursor()->getSystem();
|
||||||
if (systemToView->isCollection())
|
if (systemToView->isCollection())
|
||||||
|
@ -145,7 +145,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
|
||||||
else if (config->isMappedTo("x", input)) {
|
else if (config->isMappedTo("x", input)) {
|
||||||
if (mRoot->getSystem()->isGameSystem()) {
|
if (mRoot->getSystem()->isGameSystem()) {
|
||||||
// Go to random system game.
|
// Go to random system game.
|
||||||
navigationsounds.playThemeNavigationSound(SCROLLSOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
|
||||||
FileData* randomGame = getCursor()->getSystem()->getRandomGame();
|
FileData* randomGame = getCursor()->getSystem()->getRandomGame();
|
||||||
if (randomGame)
|
if (randomGame)
|
||||||
setCursor(randomGame);
|
setCursor(randomGame);
|
||||||
|
@ -155,7 +155,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
|
||||||
else if (config->isMappedTo("y", input) &&
|
else if (config->isMappedTo("y", input) &&
|
||||||
!UIModeController::getInstance()->isUIModeKid()) {
|
!UIModeController::getInstance()->isUIModeKid()) {
|
||||||
if (mRoot->getSystem()->isGameSystem()) {
|
if (mRoot->getSystem()->isGameSystem()) {
|
||||||
navigationsounds.playThemeNavigationSound(FAVORITESOUND);
|
NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND);
|
||||||
if (CollectionSystemManager::get()->toggleGameInCollection(getCursor()))
|
if (CollectionSystemManager::get()->toggleGameInCollection(getCursor()))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -287,7 +287,6 @@ void VideoGameListView::updateInfoPanel()
|
||||||
mVideo->setImage(file->getImagePath());
|
mVideo->setImage(file->getImagePath());
|
||||||
mThumbnail.setImage(file->getThumbnailPath());
|
mThumbnail.setImage(file->getThumbnailPath());
|
||||||
mMarquee.setImage(file->getMarqueePath());
|
mMarquee.setImage(file->getMarqueePath());
|
||||||
mImage.setImage(file->getImagePath());
|
|
||||||
|
|
||||||
mDescription.setText(file->metadata.get("desc"));
|
mDescription.setText(file->metadata.get("desc"));
|
||||||
mDescContainer.reset();
|
mDescContainer.reset();
|
||||||
|
|
|
@ -44,8 +44,7 @@ bool HttpReq::isUrl(const std::string& str)
|
||||||
std::string::npos || str.find("www.") != std::string::npos));
|
std::string::npos || str.find("www.") != std::string::npos));
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpReq::HttpReq(const std::string& url)
|
HttpReq::HttpReq(const std::string& url) : mStatus(REQ_IN_PROGRESS), mHandle(NULL)
|
||||||
: mStatus(REQ_IN_PROGRESS), mHandle(NULL)
|
|
||||||
{
|
{
|
||||||
mHandle = curl_easy_init();
|
mHandle = curl_easy_init();
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,7 @@
|
||||||
// Once one of those calls complete, the request is ready.
|
// Once one of those calls complete, the request is ready.
|
||||||
//
|
//
|
||||||
// Do something like this to capture errors:
|
// Do something like this to capture errors:
|
||||||
// if(myRequest.status() != REQ_SUCCESS)
|
// if(myRequest.status() != REQ_SUCCESS) {
|
||||||
// {
|
|
||||||
// // An error occured.
|
// // An error occured.
|
||||||
// LOG(LogError) << "HTTP request error - " << myRequest.getErrorMessage();
|
// LOG(LogError) << "HTTP request error - " << myRequest.getErrorMessage();
|
||||||
// return;
|
// 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 "MameNames.h"
|
||||||
|
|
||||||
#include "resources/ResourceManager.h"
|
#include "resources/ResourceManager.h"
|
||||||
#include "utils/FileSystemUtil.h"
|
#include "utils/FileSystemUtil.h"
|
||||||
|
#include "utils/StringUtil.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include <pugixml/src/pugixml.hpp>
|
#include <pugixml/src/pugixml.hpp>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
@ -10,35 +20,31 @@ MameNames* MameNames::sInstance = nullptr;
|
||||||
|
|
||||||
void MameNames::init()
|
void MameNames::init()
|
||||||
{
|
{
|
||||||
if(!sInstance)
|
if (!sInstance)
|
||||||
sInstance = new MameNames();
|
sInstance = new MameNames();
|
||||||
|
}
|
||||||
} // init
|
|
||||||
|
|
||||||
void MameNames::deinit()
|
void MameNames::deinit()
|
||||||
{
|
{
|
||||||
if(sInstance)
|
if (sInstance) {
|
||||||
{
|
|
||||||
delete sInstance;
|
delete sInstance;
|
||||||
sInstance = nullptr;
|
sInstance = nullptr;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} // deinit
|
|
||||||
|
|
||||||
MameNames* MameNames::getInstance()
|
MameNames* MameNames::getInstance()
|
||||||
{
|
{
|
||||||
if(!sInstance)
|
if (!sInstance)
|
||||||
sInstance = new MameNames();
|
sInstance = new MameNames();
|
||||||
|
|
||||||
return sInstance;
|
return sInstance;
|
||||||
|
}
|
||||||
} // getInstance
|
|
||||||
|
|
||||||
MameNames::MameNames()
|
MameNames::MameNames()
|
||||||
{
|
{
|
||||||
std::string xmlpath = ResourceManager::getInstance()->getResourcePath(":/mamenames.xml");
|
std::string xmlpath = ResourceManager::getInstance()->getResourcePath(":/mamenames.xml");
|
||||||
|
|
||||||
if(!Utils::FileSystem::exists(xmlpath))
|
if (!Utils::FileSystem::exists(xmlpath))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"...";
|
LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"...";
|
||||||
|
@ -46,115 +52,123 @@ MameNames::MameNames()
|
||||||
pugi::xml_document doc;
|
pugi::xml_document doc;
|
||||||
pugi::xml_parse_result result = doc.load_file(xmlpath.c_str());
|
pugi::xml_parse_result result = doc.load_file(xmlpath.c_str());
|
||||||
|
|
||||||
if(!result)
|
if (!result) {
|
||||||
{
|
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n "
|
||||||
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description();
|
<< result.description();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(pugi::xml_node gameNode = doc.child("game"); gameNode; gameNode = gameNode.next_sibling("game"))
|
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() };
|
NamePair namePair = {
|
||||||
|
gameNode.child("mamename").text().get(),
|
||||||
|
gameNode.child("realname").text().get()
|
||||||
|
};
|
||||||
mNamePairs.push_back(namePair);
|
mNamePairs.push_back(namePair);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read bios
|
// Read BIOS file.
|
||||||
xmlpath = ResourceManager::getInstance()->getResourcePath(":/mamebioses.xml");
|
xmlpath = ResourceManager::getInstance()->getResourcePath(":/mamebioses.xml");
|
||||||
|
|
||||||
if(!Utils::FileSystem::exists(xmlpath))
|
if (!Utils::FileSystem::exists(xmlpath))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"...";
|
LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"...";
|
||||||
|
|
||||||
result = doc.load_file(xmlpath.c_str());
|
result = doc.load_file(xmlpath.c_str());
|
||||||
|
|
||||||
if(!result)
|
if (!result) {
|
||||||
{
|
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n "
|
||||||
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description();
|
<< result.description();
|
||||||
return;
|
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();
|
std::string bios = biosNode.text().get();
|
||||||
mMameBioses.push_back(bios);
|
mMameBioses.push_back(bios);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read devices
|
// Read devices file.
|
||||||
xmlpath = ResourceManager::getInstance()->getResourcePath(":/mamedevices.xml");
|
xmlpath = ResourceManager::getInstance()->getResourcePath(":/mamedevices.xml");
|
||||||
|
|
||||||
if(!Utils::FileSystem::exists(xmlpath))
|
if (!Utils::FileSystem::exists(xmlpath))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"...";
|
LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"...";
|
||||||
|
|
||||||
result = doc.load_file(xmlpath.c_str());
|
result = doc.load_file(xmlpath.c_str());
|
||||||
|
|
||||||
if(!result)
|
if (!result) {
|
||||||
{
|
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n "
|
||||||
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description();
|
<< result.description();
|
||||||
return;
|
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();
|
std::string device = deviceNode.text().get();
|
||||||
mMameDevices.push_back(device);
|
mMameDevices.push_back(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // MameNames
|
} // MameNames.
|
||||||
|
|
||||||
MameNames::~MameNames()
|
MameNames::~MameNames()
|
||||||
{
|
{
|
||||||
|
}
|
||||||
} // ~MameNames
|
|
||||||
|
|
||||||
std::string MameNames::getRealName(const std::string& _mameName)
|
std::string MameNames::getRealName(const std::string& _mameName)
|
||||||
{
|
{
|
||||||
size_t start = 0;
|
size_t start = 0;
|
||||||
size_t end = mNamePairs.size();
|
size_t end = mNamePairs.size();
|
||||||
|
|
||||||
while(start < end)
|
while (start < end) {
|
||||||
{
|
|
||||||
const size_t index = (start + end) / 2;
|
const size_t index = (start + end) / 2;
|
||||||
const int compare = strcmp(mNamePairs[index].mameName.c_str(), _mameName.c_str());
|
const int compare = strcmp(mNamePairs[index].mameName.c_str(), _mameName.c_str());
|
||||||
|
|
||||||
if(compare < 0) start = index + 1;
|
if (compare < 0)
|
||||||
else if( compare > 0) end = index;
|
start = index + 1;
|
||||||
else return mNamePairs[index].realName;
|
else if (compare > 0)
|
||||||
|
end = index;
|
||||||
|
else
|
||||||
|
return mNamePairs[index].realName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _mameName;
|
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)
|
const bool MameNames::isBios(const std::string& _biosName)
|
||||||
{
|
{
|
||||||
return MameNames::find(mMameBioses, _biosName);
|
return MameNames::find(mMameBioses, _biosName);
|
||||||
|
}
|
||||||
} // isBios
|
|
||||||
|
|
||||||
const bool MameNames::isDevice(const std::string& _deviceName)
|
const bool MameNames::isDevice(const std::string& _deviceName)
|
||||||
{
|
{
|
||||||
return MameNames::find(mMameDevices, _deviceName);
|
return MameNames::find(mMameDevices, _deviceName);
|
||||||
|
|
||||||
} // isDevice
|
}
|
||||||
|
|
||||||
const bool MameNames::find(std::vector<std::string> devices, const std::string& name)
|
const bool MameNames::find(std::vector<std::string> devices, const std::string& name)
|
||||||
{
|
{
|
||||||
size_t start = 0;
|
size_t start = 0;
|
||||||
size_t end = devices.size();
|
size_t end = devices.size();
|
||||||
|
|
||||||
while(start < end)
|
while (start < end) {
|
||||||
{
|
|
||||||
const size_t index = (start + end) / 2;
|
const size_t index = (start + end) / 2;
|
||||||
const int compare = strcmp(devices[index].c_str(), name.c_str());
|
const int compare = strcmp(devices[index].c_str(), name.c_str());
|
||||||
|
|
||||||
if(compare < 0) start = index + 1;
|
if (compare < 0)
|
||||||
else if( compare > 0) end = index;
|
start = index + 1;
|
||||||
else return true;
|
else if (compare > 0)
|
||||||
|
end = index;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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
|
#pragma once
|
||||||
#ifndef ES_CORE_MAMENAMES_H
|
#ifndef ES_CORE_MAMENAMES_H
|
||||||
#define ES_CORE_MAMENAMES_H
|
#define ES_CORE_MAMENAMES_H
|
||||||
|
@ -5,6 +14,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
// Expand MAME names to full game names.
|
||||||
class MameNames
|
class MameNames
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -13,13 +23,13 @@ public:
|
||||||
static void deinit ();
|
static void deinit ();
|
||||||
static MameNames* getInstance();
|
static MameNames* getInstance();
|
||||||
std::string getRealName(const std::string& _mameName);
|
std::string getRealName(const std::string& _mameName);
|
||||||
|
std::string getCleanName(const std::string& _mameName);
|
||||||
const bool isBios(const std::string& _biosName);
|
const bool isBios(const std::string& _biosName);
|
||||||
const bool isDevice(const std::string& _deviceName);
|
const bool isDevice(const std::string& _deviceName);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
struct NamePair
|
struct NamePair {
|
||||||
{
|
|
||||||
std::string mameName;
|
std::string mameName;
|
||||||
std::string realName;
|
std::string realName;
|
||||||
};
|
};
|
||||||
|
|
|
@ -132,7 +132,19 @@ void Settings::setDefaults()
|
||||||
|
|
||||||
// Scraper.
|
// Scraper.
|
||||||
mStringMap["Scraper"] = "ScreenScraper";
|
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["ScrapeRatings"] = true;
|
||||||
|
mBoolMap["Scrape3DBoxes"] = true;
|
||||||
|
mBoolMap["ScrapeCovers"] = true;
|
||||||
|
mBoolMap["ScrapeMarquees"] = true;
|
||||||
|
mBoolMap["ScrapeScreenshots"] = true;
|
||||||
|
|
||||||
// Other settings.
|
// Other settings.
|
||||||
#ifdef _RPI_
|
#ifdef _RPI_
|
||||||
|
@ -189,6 +201,16 @@ void Settings::setDefaults()
|
||||||
mIntMap["ScreenOffsetY"] = 0;
|
mIntMap["ScreenOffsetY"] = 0;
|
||||||
mIntMap["ScreenRotate"] = 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.
|
// Hardcoded or program-internal settings.
|
||||||
//
|
//
|
||||||
|
@ -197,10 +219,6 @@ void Settings::setDefaults()
|
||||||
mBoolMap["DebugGrid"] = false;
|
mBoolMap["DebugGrid"] = false;
|
||||||
mBoolMap["DebugText"] = false;
|
mBoolMap["DebugText"] = false;
|
||||||
mBoolMap["DebugImage"] = false;
|
mBoolMap["DebugImage"] = false;
|
||||||
mStringMap["DefaultSortOrder"] = "filename, ascending";
|
|
||||||
mStringMap["MediaDirectory"] = "";
|
|
||||||
mIntMap["ScraperResizeWidth"] = 400;
|
|
||||||
mIntMap["ScraperResizeHeight"] = 0;
|
|
||||||
mBoolMap["SplashScreenProgress"] = true;
|
mBoolMap["SplashScreenProgress"] = true;
|
||||||
mStringMap["UIMode_passkey"] = "uuddlrlrba";
|
mStringMap["UIMode_passkey"] = "uuddlrlrba";
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
#include "ThemeData.h"
|
#include "ThemeData.h"
|
||||||
|
|
||||||
|
NavigationSounds* NavigationSounds::sInstance = nullptr;
|
||||||
|
|
||||||
std::map< std::string, std::shared_ptr<Sound> > Sound::sMap;
|
std::map< std::string, std::shared_ptr<Sound> > Sound::sMap;
|
||||||
|
|
||||||
std::shared_ptr<Sound> Sound::get(const std::string& path)
|
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");
|
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "sound");
|
||||||
if(!elem || !elem->has("path"))
|
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("");
|
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"));
|
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)
|
void NavigationSounds::loadThemeNavigationSounds(const std::shared_ptr<ThemeData>& theme)
|
||||||
{
|
{
|
||||||
systembrowseSound = Sound::getFromTheme(theme, "all", "systembrowseSound");
|
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "systembrowseSound"));
|
||||||
quicksysselectSound = Sound::getFromTheme(theme, "all", "quicksysselectSound");
|
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "quicksysselectSound"));
|
||||||
selectSound = Sound::getFromTheme(theme, "all", "selectSound");
|
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "selectSound"));
|
||||||
backSound = Sound::getFromTheme(theme, "all", "backSound");
|
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "backSound"));
|
||||||
scrollSound = Sound::getFromTheme(theme, "all", "scrollSound");
|
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "scrollSound"));
|
||||||
favoriteSound = Sound::getFromTheme(theme, "all", "favoriteSound");
|
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "favoriteSound"));
|
||||||
launchSound = Sound::getFromTheme(theme, "all", "launchSound");
|
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "launchSound"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigationSounds::playThemeNavigationSound(NavigationSoundsID soundID)
|
void NavigationSounds::playThemeNavigationSound(NavigationSoundsID soundID)
|
||||||
{
|
{
|
||||||
|
NavigationSounds::getInstance()->navigationSounds[soundID]->play();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NavigationSounds::isPlayingThemeNavigationSound(NavigationSoundsID soundID)
|
bool NavigationSounds::isPlayingThemeNavigationSound(NavigationSoundsID soundID)
|
||||||
{
|
{
|
||||||
switch(soundID)
|
return NavigationSounds::getInstance()->navigationSounds[soundID]->isPlaying();
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Sound::Sound(const std::string & path) : mSampleData(NULL), mSamplePos(0), mSampleLength(0), playing(false)
|
Sound::Sound(const std::string & path) : mSampleData(NULL), mSamplePos(0), mSampleLength(0), playing(false)
|
||||||
|
@ -130,7 +94,7 @@ void Sound::init()
|
||||||
Uint8 * data = NULL;
|
Uint8 * data = NULL;
|
||||||
Uint32 dlen = 0;
|
Uint32 dlen = 0;
|
||||||
if (SDL_LoadWAV(mPath.c_str(), &wave, &data, &dlen) == NULL) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
//build conversion buffer
|
//build conversion buffer
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "SDL_audio.h"
|
#include "SDL_audio.h"
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class ThemeData;
|
class ThemeData;
|
||||||
|
|
||||||
|
@ -57,18 +58,15 @@ enum NavigationSoundsID
|
||||||
class NavigationSounds
|
class NavigationSounds
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static NavigationSounds* getInstance();
|
||||||
|
|
||||||
void loadThemeNavigationSounds(const std::shared_ptr<ThemeData>& theme);
|
void loadThemeNavigationSounds(const std::shared_ptr<ThemeData>& theme);
|
||||||
void playThemeNavigationSound(NavigationSoundsID soundID);
|
void playThemeNavigationSound(NavigationSoundsID soundID);
|
||||||
bool isPlayingThemeNavigationSound(NavigationSoundsID soundID);
|
bool isPlayingThemeNavigationSound(NavigationSoundsID soundID);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Sound> systembrowseSound;
|
static NavigationSounds* sInstance;
|
||||||
std::shared_ptr<Sound> quicksysselectSound;
|
std::vector<std::shared_ptr<Sound>> navigationSounds;
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern NavigationSounds navigationsounds;
|
extern NavigationSounds navigationsounds;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "components/MenuComponent.h"
|
#include "components/MenuComponent.h"
|
||||||
|
|
||||||
#include "components/ButtonComponent.h"
|
#include "components/ButtonComponent.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
|
||||||
#define BUTTON_GRID_VERT_PADDING 32
|
#define BUTTON_GRID_VERT_PADDING 32
|
||||||
#define BUTTON_GRID_HORIZ_PADDING 10
|
#define BUTTON_GRID_HORIZ_PADDING 10
|
||||||
|
@ -32,6 +33,22 @@ MenuComponent::MenuComponent(Window* window, const char* title, const std::share
|
||||||
mGrid.resetCursor();
|
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)
|
void MenuComponent::setTitle(const char* title, const std::shared_ptr<Font>& font)
|
||||||
{
|
{
|
||||||
mTitle->setText(Utils::String::toUpper(title));
|
mTitle->setText(Utils::String::toUpper(title));
|
||||||
|
|
|
@ -20,7 +20,9 @@ class MenuComponent : public GuiComponent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MenuComponent(Window* window, const char* title, const std::shared_ptr<Font>& titleFont = Font::get(FONT_SIZE_LARGE));
|
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;
|
void onSizeChanged() override;
|
||||||
|
|
||||||
inline void addRow(const ComponentListRow& row, bool setCursorHere = false) { mList->addRow(row, setCursorHere); updateSize(); }
|
inline void addRow(const ComponentListRow& row, bool setCursorHere = false) { mList->addRow(row, setCursorHere); updateSize(); }
|
||||||
|
@ -33,6 +35,8 @@ public:
|
||||||
addRow(row, setCursorHere);
|
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 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);
|
void setTitle(const char* title, const std::shared_ptr<Font>& font);
|
||||||
|
@ -54,6 +58,7 @@ private:
|
||||||
std::shared_ptr<ComponentList> mList;
|
std::shared_ptr<ComponentList> mList;
|
||||||
std::shared_ptr<ComponentGrid> mButtonGrid;
|
std::shared_ptr<ComponentGrid> mButtonGrid;
|
||||||
std::vector< std::shared_ptr<ButtonComponent> > mButtons;
|
std::vector< std::shared_ptr<ButtonComponent> > mButtons;
|
||||||
|
std::vector< std::function<void()> > mSaveFuncs;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ES_CORE_COMPONENTS_MENU_COMPONENT_H
|
#endif // ES_CORE_COMPONENTS_MENU_COMPONENT_H
|
||||||
|
|
|
@ -250,6 +250,30 @@ public:
|
||||||
onSelectedChanged();
|
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()
|
void selectAll()
|
||||||
{
|
{
|
||||||
for(unsigned int i = 0; i < mEntries.size(); i++)
|
for(unsigned int i = 0; i < mEntries.size(); i++)
|
||||||
|
|
Loading…
Reference in a new issue