Added proper error handling for resource files and improved overall logging.

This commit is contained in:
Leon Styhre 2020-07-08 17:01:47 +02:00
parent 04d4658fc9
commit 5a7fb828a6
14 changed files with 147 additions and 63 deletions

View file

@ -24,6 +24,7 @@ v1.0.0
* Moved all resources to a subdirectory structure and enabled the CMake install prefix variable to generate the resources search path
* Changed theme directory to the install prefix (e.g. /usr/local/share/emulationstation/themes) with themes in the home directory taking precedence
* No more attempts to open files directly under /etc, instead only the install prefix directory and the home directory are used
* Added proper error handling for missing resource files and improved overall logging
* Refactoring, cleanup and documentation of the source code, removal of deprecated files etc.
* All required fonts bundled with the application, no dependencies on the OS to provide them any longer
* Made pugixml an external dependency instead of bundling it

View file

@ -94,8 +94,14 @@ void SystemData::setIsGameSystemStatus()
bool SystemData::populateFolder(FileData* folder)
{
const std::string& folderPath = folder->getPath();
if (!Utils::FileSystem::exists(folderPath)) {
LOG(LogInfo) << "Info - Folder with path \"" <<
folderPath << "\" does not exist.";
return false;
}
if (!Utils::FileSystem::isDirectory(folderPath)) {
LOG(LogWarning) << "Error - folder with path \"" << folderPath << "\" is not a directory!";
LOG(LogWarning) << "Warning - Folder with path \"" <<
folderPath << "\" is not a directory!";
return false;
}
@ -104,7 +110,8 @@ bool SystemData::populateFolder(FileData* folder)
// If this symlink resolves to somewhere that's at the beginning of our
// path, it's going to create a recursive loop. Make sure to avoid this.
if (folderPath.find(Utils::FileSystem::getCanonicalPath(folderPath)) == 0) {
LOG(LogWarning) << "Skipping infinitely recursive symlink \"" << folderPath << "\"";
LOG(LogWarning) << "Warning - Skipping infinitely recursive symlink \"" <<
folderPath << "\"";
return false;
}
}
@ -211,7 +218,7 @@ bool SystemData::loadConfig()
LOG(LogInfo) << "Loading system config file " << path << "...";
if (!Utils::FileSystem::exists(path)) {
LOG(LogError) << "es_systems.cfg file does not exist!";
LOG(LogError) << "Error - es_systems.cfg file does not exist!";
writeExampleConfig(getConfigPath(true));
return false;
}
@ -220,7 +227,7 @@ bool SystemData::loadConfig()
pugi::xml_parse_result res = doc.load_file(path.c_str());
if (!res) {
LOG(LogError) << "Could not parse es_systems.cfg file!";
LOG(LogError) << "Error - Could not parse es_systems.cfg file!";
LOG(LogError) << res.description();
return false;
}
@ -229,7 +236,7 @@ bool SystemData::loadConfig()
pugi::xml_node systemList = doc.child("systemList");
if (!systemList) {
LOG(LogError) << "es_systems.cfg is missing the <systemList> tag!";
LOG(LogError) << "Error - es_systems.cfg is missing the <systemList> tag!";
return false;
}
@ -275,8 +282,8 @@ bool SystemData::loadConfig()
// If there appears to be an actual platform ID supplied
// but it didn't match the list, generate a warning.
if (str != nullptr && str[0] != '\0' && platformId == PlatformIds::PLATFORM_UNKNOWN)
LOG(LogWarning) << "Unknown platform for system \"" << name << "\" (platform \""
<< str << "\" from list \"" << platformList << "\")";
LOG(LogWarning) << "Warning - Unknown platform for system \"" << name <<
"\" (platform \"" << str << "\" from list \"" << platformList << "\")";
else if (platformId != PlatformIds::PLATFORM_UNKNOWN)
platformIds.push_back(platformId);
}
@ -286,7 +293,7 @@ bool SystemData::loadConfig()
// Validate.
if (name.empty() || path.empty() || extensions.empty() || cmd.empty()) {
LOG(LogError) << "System \"" << name <<
LOG(LogError) << "Error - System \"" << name <<
"\" is missing name, path, extension, or command!";
continue;
}
@ -310,7 +317,7 @@ bool SystemData::loadConfig()
SystemData* newSys = new SystemData(name, fullname, envData, themeFolder);
if (newSys->getRootFolder()->getChildrenByFilename().size() == 0) {
LOG(LogWarning) << "System \"" << name << "\" has no games! Ignoring it.";
LOG(LogInfo) << "Info - System \"" << name << "\" has no games, ignoring it.";
delete newSys;
}
else {

View file

@ -416,12 +416,18 @@ int main(int argc, char* argv[])
bool splashScreen = Settings::getInstance()->getBool("SplashScreen");
bool splashScreenProgress = Settings::getInstance()->getBool("SplashScreenProgress");
SDL_Event event;
if (!window.init()) {
LOG(LogError) << "Window failed to initialize!";
return 1;
}
InputManager::getInstance()->parseEvent(event, &window);
if (event.type == SDL_QUIT)
return 1;
if (splashScreen) {
std::string progressText = "Loading...";
if (splashScreenProgress)
@ -433,8 +439,7 @@ int main(int argc, char* argv[])
if (loadSystemConfigFile(errorMsg) != NO_ERRORS) {
// Something went terribly wrong.
if (errorMsg == "")
{
if (errorMsg == "") {
LOG(LogError) << "Unknown error occured while parsing system config file.";
Renderer::deinit();
return 1;

View file

@ -161,8 +161,8 @@ void thegamesdb_generate_json_scraper_requests(
first = false;
}
else {
LOG(LogWarning) << "TheGamesDB scraper warning - no support for platform "
<< getPlatformName(*platformIt);
LOG(LogWarning) << "Warning - TheGamesDB scraper warning - "
"no support for platform " << getPlatformName(*platformIt);
}
}
path += platformQueryParam;
@ -290,6 +290,7 @@ void processGame(const Value& game, std::vector<ScraperSearchResult>& results)
result.gameID = std::to_string(getIntOrThrow(game, "id"));
result.mdl.set("name", getStringOrThrow(game, "game_title"));
if (game.HasMember("overview") && game["overview"].IsString())
result.mdl.set("desc", game["overview"].GetString());
@ -309,6 +310,21 @@ void processGame(const Value& game, std::vector<ScraperSearchResult>& results)
if (game.HasMember("players") && game["players"].IsInt())
result.mdl.set("players", std::to_string(game["players"].GetInt()));
LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Name: " <<
result.mdl.get("name");
LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Release Date (unparsed): " <<
game["release_date"].GetString();
LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Release Date (parsed): " <<
result.mdl.get("releasedate");
LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Developer: " <<
result.mdl.get("developer");
LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Publisher: " <<
result.mdl.get("publisher");
LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Genre: " <<
result.mdl.get("genre");
LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Players: " <<
result.mdl.get("players");
result.mediaURLFetch = NOT_STARTED;
results.push_back(result);
}
@ -365,7 +381,7 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req,
if (doc.HasParseError()) {
std::string err =
std::string("TheGamesDBJSONRequest - Error parsing JSON. \n\t") +
std::string("Error - TheGamesDBJSONRequest - Error parsing JSON. \n\t") +
GetParseError_En(doc.GetParseError());
setError(err);
LOG(LogError) << err;
@ -384,7 +400,7 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req,
baseImageUrlLarge = base_url["large"].GetString();
}
else {
std::string warn = "TheGamesDBJSONRequest - No URL path for large images.\n";
std::string warn = "Warning - TheGamesDBJSONRequest - No URL path for large images.\n";
LOG(LogWarning) << warn;
return;
}
@ -393,7 +409,7 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req,
processMediaURLs(images, baseImageUrlLarge, results);
}
catch (std::runtime_error& e) {
LOG(LogError) << "Error while processing media URLs: " << e.what();
LOG(LogError) << "Error - Error while processing media URLs: " << e.what();
}
// Find how many more requests we can make before the scraper
@ -412,7 +428,7 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req,
// These process steps are for the initial scraping response.
if (!doc.HasMember("data") || !doc["data"].HasMember("games") ||
!doc["data"]["games"].IsArray()) {
std::string warn = "TheGamesDBJSONRequest - Response had no game data.\n";
std::string warn = "Warning - TheGamesDBJSONRequest - Response had no game data.\n";
LOG(LogWarning) << warn;
return;
}
@ -426,7 +442,7 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req,
processGame(v, results);
}
catch (std::runtime_error& e) {
LOG(LogError) << "Error while processing game: " << e.what();
LOG(LogError) << "Error - Error while processing game: " << e.what();
}
}
}

View file

@ -29,7 +29,8 @@ std::unique_ptr<ScraperSearchHandle> startScraperSearch(const ScraperSearchParam
// 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.";
LOG(LogWarning) << "Warning - Configured scraper (" << name <<
") unavailable, scraping aborted.";
else
scraper_request_funcs.at(name)(params, handle->mRequestQueue, handle->mResults);
@ -44,7 +45,8 @@ std::unique_ptr<ScraperSearchHandle> startMediaURLsFetch(const std::string& game
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.";
LOG(LogWarning) << "Warning - Configured scraper (" << name <<
") unavailable, scraping aborted.";
else
// Specifically use the TheGamesDB function as this type of request
// will never occur for ScreenScraper.
@ -140,7 +142,7 @@ void ScraperHttpRequest::update()
return;
// Everything else is some sort of error.
LOG(LogError) << "ScraperHttpRequest network error (status: " << status<< ") - "
LOG(LogError) << "Error - ScraperHttpRequest network error (status: " << status<< ") - "
<< mReq->getErrorMsg();
setError(mReq->getErrorMsg());
}
@ -367,7 +369,7 @@ bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
if (format == FIF_UNKNOWN)
format = FreeImage_GetFIFFromFilename(path.c_str());
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;
}
@ -376,7 +378,7 @@ bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
image = FreeImage_Load(format, path.c_str());
}
else {
LOG(LogError) << "Error - file format reading not supported for image \"" << path << "\"!";
LOG(LogError) << "Error - File format not supported for image \"" << path << "\"!";
return false;
}
@ -397,7 +399,7 @@ bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
FreeImage_Unload(image);
if (imageRescaled == nullptr) {
LOG(LogError) << "Could not resize image! (not enough memory? invalid bitdepth?)";
LOG(LogError) << "Error - Could not resize image! (not enough memory? invalid bitdepth?)";
return false;
}
@ -405,7 +407,7 @@ bool resizeImage(const std::string& path, int maxWidth, int maxHeight)
FreeImage_Unload(imageRescaled);
if (!saved) {
LOG(LogError) << "Failed to save resized image!";
LOG(LogError) << "Error - Failed to save resized image!";
}
return saved;

View file

@ -149,7 +149,7 @@ void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
p_ids.push_back(mapIt->second);
}
else {
LOG(LogWarning) << "ScreenScraper: no support for platform " <<
LOG(LogWarning) << "Warning - ScreenScraper: no support for platform " <<
getPlatformName(*platformIt);
// Add the scrape request without a platform/system ID.
requests.push(std::unique_ptr<ScraperRequest>
@ -180,8 +180,7 @@ void ScreenScraperRequest::process(const std::unique_ptr<HttpReq>& req,
if (!parseResult) {
std::stringstream ss;
ss << "ScreenScraperRequest - Error parsing XML." << std::endl <<
parseResult.description() << "";
ss << "Error - ScreenScraperRequest - Error parsing XML: " << parseResult.description();
std::string err = ss.str();
setError(err);
@ -232,14 +231,12 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
// Genre fallback language: EN. ( Xpath: Data/jeu[0]/genres/genre[*] ).
result.mdl.set("genre", find_child_by_attribute_list(game.child("genres"),
"genre", "langue", { language, "en" }).text().get());
LOG(LogDebug) << "Genre: " << result.mdl.get("genre");
// Get the date proper. The API returns multiple 'date' children nodes to the 'dates'
// main child of 'jeu'.
// Date fallback: WOR(LD), US, SS, JP, EU.
std::string _date = find_child_by_attribute_list(game.child("dates"), "date", "region",
{ region, "wor", "us", "ss", "jp", "eu" }).text().get();
LOG(LogDebug) << "Release Date (unparsed): " << _date;
// Date can be YYYY-MM-DD or just YYYY.
if (_date.length() > 4) {
@ -251,8 +248,6 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
Utils::Time::stringToTime(_date, "%Y")));
}
LOG(LogDebug) << "Release Date (parsed): " << result.mdl.get("releasedate");
/// Developer for the game( Xpath: Data/jeu[0]/developpeur ).
std::string developer = game.child("developpeur").text().get();
if (!developer.empty())
@ -276,6 +271,21 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
result.mdl.set("rating", ss.str());
}
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Name: " <<
result.mdl.get("name");
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Release Date (unparsed): " <<
_date;
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Release Date (parsed): " <<
result.mdl.get("releasedate");
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Developer: " <<
result.mdl.get("developer");
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Publisher: " <<
result.mdl.get("publisher");
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Genre: " <<
result.mdl.get("genre");
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Players: " <<
result.mdl.get("players");
// Media super-node.
pugi::xml_node media_list = game.child("medias");
@ -352,13 +362,13 @@ void ScreenScraperRequest::processList(const pugi::xml_document& xmldoc,
{
assert(mRequestQueue != nullptr);
LOG(LogDebug) << "Processing a list of results";
LOG(LogDebug) << "ScreenScraper: Processing a list of results";
pugi::xml_node data = xmldoc.child("Data");
pugi::xml_node game = data.child("jeu");
if (!game) {
LOG(LogDebug) << "Found nothing";
LOG(LogDebug) << "ScreenScraper: Found nothing";
}
ScreenScraperRequest::ScreenScraperConfig ssConfig;

View file

@ -65,7 +65,8 @@ std::vector<unsigned char> ImageIO::loadFromMemoryRGBA32(const unsigned char * d
}
}
else {
LOG(LogError) << "Error - File type " <<
LOG(LogError) << "Error - Couldn't load image, file is missing or the file type is " <<
// it's not existing or the file type " <<
(format == FIF_UNKNOWN ? "unknown" : "unsupported") << "!";
}
// Free fiMemory again

View file

@ -5,7 +5,12 @@
//
#include "Platform.h"
#include "renderers/Renderer.h"
#include "utils/StringUtil.h"
#include "AudioManager.h"
#include "Log.h"
#include "MameNames.h"
#if defined(__linux__) || defined(_WIN64)
#include <SDL2/SDL_events.h>
@ -22,8 +27,6 @@
#endif
#include <fcntl.h>
#include "Log.h"
int runRebootCommand()
{
#ifdef _WIN64 // Windows.
@ -141,6 +144,19 @@ int quitES(QuitMode mode)
return 0;
}
void emergencyShutdown()
{
LOG(LogError) << "Critical Error - Performing emergency shutdown...";
MameNames::deinit();
AudioManager::getInstance()->deinit();
// Most of the SDL deinitialization is done in Renderer.
Renderer::deinit();
Log::flush();
exit(EXIT_FAILURE);
}
void touch(const std::string& filename)
{
#ifdef _WIN64

View file

@ -29,7 +29,10 @@ int runSystemCommand(const std::wstring& cmd_utf16);
int launchEmulatorUnix(const std::string& cmd_utf8);
int launchEmulatorWindows(const std::wstring& cmd_utf16);
// Clean, normal shutdown.
int quitES(QuitMode mode = QuitMode::QUIT);
// Immediately shut down the application as cleanly as possible.
void emergencyShutdown();
void processQuitMode();
#endif // ES_CORE_PLATFORM_H

View file

@ -37,8 +37,8 @@ namespace Renderer
{
size_t width = 0;
size_t height = 0;
ResourceData resData =
ResourceManager::getInstance()->getFileData(":/graphics/window_icon_256.png");
ResourceData resData = ResourceManager::getInstance()->
getFileData(":/graphics/window_icon_256.png");
std::vector<unsigned char> rawData =
ImageIO::loadFromMemoryRGBA32(resData.ptr.get(), resData.length, width, height);

View file

@ -213,6 +213,13 @@ std::vector<std::string> getFallbackFontPaths()
{
std::vector<std::string> fontPaths;
// Standard fonts, let's include them here for error checking purposes even though that's
// not really the correct location. (The application will crash if they are missing.)
ResourceManager::getInstance()->
getResourcePath(":/fonts/opensans_hebrew_condensed_light.ttf");
ResourceManager::getInstance()->
getResourcePath(":/fonts/opensans_hebrew_condensed_regular.ttf");
// Vera sans Unicode:
fontPaths.push_back(ResourceManager::getInstance()->
getResourcePath(":/fonts/DejaVuSans.ttf"));

View file

@ -8,6 +8,10 @@
#include "ResourceManager.h"
#include "utils/FileSystemUtil.h"
#include "Log.h"
#include "Platform.h"
#include "Scripting.h"
#include <fstream>
auto array_deleter = [](unsigned char* p) { delete[] p; };
@ -31,23 +35,36 @@ std::string ResourceManager::getResourcePath(const std::string& path) const
{
// Check if this is a resource file.
if ((path[0] == ':') && (path[1] == '/')) {
std::string test;
std::string testHome;
std::string testDataPath;
// Check under the home directory.
test = Utils::FileSystem::getHomePath() + "/.emulationstation/resources/" + &path[2];
if (Utils::FileSystem::exists(test))
return test;
testHome = Utils::FileSystem::getHomePath() + "/.emulationstation/resources/" + &path[2];
if (Utils::FileSystem::exists(testHome))
return testHome;
// Check for the resource under the data installation directory for Unix or under
// the executable directory for Windows.
#ifdef _WIN64
test = Utils::FileSystem::getExePath() + "/resources/" + &path[2];
testDataPath = Utils::FileSystem::getExePath() + "/resources/" + &path[2];
#else
test = Utils::FileSystem::getProgramDataPath() + "/resources/" + &path[2];
testDataPath = Utils::FileSystem::getProgramDataPath() + "/resources/" + &path[2];
#endif
if (Utils::FileSystem::exists(test))
return test;
if (Utils::FileSystem::exists(testDataPath)) {
return testDataPath;
}
// For missing resources, log an error and terminate the application. This should
// indicate that we have a broken EmulationStation installation.
else {
LOG(LogError) << "Error - Program resource missing: " << path;
LOG(LogError) << "Tried to find the resource in the following locations:";
LOG(LogError) << testHome;
LOG(LogError) << testDataPath;
LOG(LogError) << "Has EmulationStation been properly installed?";
Scripting::fireEvent("quit");
emergencyShutdown();
}
}
// Not a resource, return unmodified path.

View file

@ -490,20 +490,19 @@ namespace Utils
#if defined(_WIN64)
HANDLE hFile = CreateFile(path.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ,
0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
/*
if (hFile != INVALID_HANDLE_VALUE) {
resolved.resize(GetFinalPathNameByHandle(hFile, nullptr, 0,
resolved.resize(GetFinalPathNameByHandle(hFile, nullptr, 0,
FILE_NAME_NORMALIZED) + 1);
if (GetFinalPathNameByHandle(hFile, (LPSTR)resolved.data(),
if (GetFinalPathNameByHandle(hFile, (LPSTR)resolved.data(),
(DWORD)resolved.size(), FILE_NAME_NORMALIZED) > 0) {
resolved.resize(resolved.size() - 1);
resolved = getGenericPath(resolved);
}
CloseHandle(hFile);
}
*/
// TEMPORARY, will need to fix this later.
// if (hFile != INVALID_HANDLE_VALUE) {
// resolved.resize(GetFinalPathNameByHandle(hFile, nullptr, 0,
// resolved.resize(GetFinalPathNameByHandle(hFile, nullptr, 0,
// FILE_NAME_NORMALIZED) + 1);
// if (GetFinalPathNameByHandle(hFile, (LPSTR)resolved.data(),
// if (GetFinalPathNameByHandle(hFile, (LPSTR)resolved.data(),
// (DWORD)resolved.size(), FILE_NAME_NORMALIZED) > 0) {
// resolved.resize(resolved.size() - 1);
// resolved = getGenericPath(resolved);
// }
// CloseHandle(hFile);
// }
#else
struct stat info;