mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-25 07:35:38 +00:00
Renamed GamelistFileParse to GamelistFileParser and moved it to its own namespace.
This commit is contained in:
parent
b43c991a9d
commit
97e08922a0
|
@ -15,7 +15,7 @@ set(ES_HEADERS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/GamelistFileParse.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/GamelistFileParser.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.h
|
||||
|
@ -64,7 +64,7 @@ set(ES_SOURCES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/GamelistFileParse.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/GamelistFileParser.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.cpp
|
||||
|
|
|
@ -1,425 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// EmulationStation Desktop Edition
|
||||
// GamelistFileParse.cpp
|
||||
//
|
||||
// Parses and updates the gamelist.xml files.
|
||||
//
|
||||
|
||||
#include "GamelistFileParse.h"
|
||||
|
||||
#include "FileData.h"
|
||||
#include "Log.h"
|
||||
#include "Settings.h"
|
||||
#include "SystemData.h"
|
||||
#include "utils/FileSystemUtil.h"
|
||||
#include "utils/StringUtil.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType type)
|
||||
{
|
||||
// First, verify that path is within the system's root folder.
|
||||
FileData* root = system->getRootFolder();
|
||||
bool contains = false;
|
||||
std::string relative = Utils::FileSystem::removeCommonPath(path, root->getPath(), contains);
|
||||
|
||||
if (!contains) {
|
||||
LOG(LogError) << "Path \"" << path << "\" is outside system path \""
|
||||
<< system->getStartPath() << "\"";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Utils::FileSystem::StringList pathList = Utils::FileSystem::getPathList(relative);
|
||||
auto path_it = pathList.begin();
|
||||
FileData* treeNode = root;
|
||||
bool found = false;
|
||||
while (path_it != pathList.end()) {
|
||||
const std::unordered_map<std::string, FileData*>& children =
|
||||
treeNode->getChildrenByFilename();
|
||||
|
||||
std::string key = *path_it;
|
||||
found = children.find(key) != children.cend();
|
||||
if (found) {
|
||||
treeNode = children.at(key);
|
||||
}
|
||||
|
||||
// This is the end.
|
||||
if (path_it == --pathList.end()) {
|
||||
if (found)
|
||||
return treeNode;
|
||||
|
||||
if (type == FOLDER) {
|
||||
LOG(LogWarning) << "A folder defined in the gamelist file does not exist:";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Handle the special situation where a file exists and has an entry in the
|
||||
// gamelist.xml file but the file extension is not configured in es_systems.xml.
|
||||
const std::vector<std::string> extensions =
|
||||
system->getSystemEnvData()->mSearchExtensions;
|
||||
|
||||
if (std::find(extensions.cbegin(), extensions.cend(),
|
||||
Utils::FileSystem::getExtension(path)) == extensions.cend()) {
|
||||
LOG(LogWarning) << "File \"" << path
|
||||
<< "\" is present in gamelist.xml but the extension is not "
|
||||
"configured in es_systems.xml";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileData* file = new FileData(type, path, system->getSystemEnvData(), system);
|
||||
|
||||
// Skipping arcade assets from gamelist.
|
||||
if (!file->isArcadeAsset())
|
||||
treeNode->addChild(file);
|
||||
return file;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// Don't create folders unless they're including any games.
|
||||
// If the type is FOLDER it's going to be empty, so don't bother.
|
||||
if (type == FOLDER) {
|
||||
LOG(LogWarning) << "A folder defined in the gamelist file does not exist:";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create missing folder.
|
||||
FileData* folder = new FileData(
|
||||
FOLDER, Utils::FileSystem::getStem(treeNode->getPath()) + "/" + *path_it,
|
||||
system->getSystemEnvData(), system);
|
||||
treeNode->addChild(folder);
|
||||
treeNode = folder;
|
||||
}
|
||||
|
||||
++path_it;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void parseGamelist(SystemData* system)
|
||||
{
|
||||
bool trustGamelist = Settings::getInstance()->getBool("ParseGamelistOnly");
|
||||
std::string xmlpath = system->getGamelistPath(false);
|
||||
|
||||
if (!Utils::FileSystem::exists(xmlpath)) {
|
||||
LOG(LogDebug) << "Gamelist::parseGamelist(): System \"" << system->getName()
|
||||
<< "\" does not have a gamelist.xml file";
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(LogInfo) << "Parsing gamelist file \"" << xmlpath << "\"...";
|
||||
|
||||
pugi::xml_document doc;
|
||||
#if defined(_WIN64)
|
||||
pugi::xml_parse_result result =
|
||||
doc.load_file(Utils::String::stringToWideString(xmlpath).c_str());
|
||||
#else
|
||||
pugi::xml_parse_result result = doc.load_file(xmlpath.c_str());
|
||||
#endif
|
||||
|
||||
if (!result) {
|
||||
LOG(LogError) << "Error parsing gamelist file \"" << xmlpath
|
||||
<< "\": " << result.description();
|
||||
return;
|
||||
}
|
||||
|
||||
pugi::xml_node root = doc.child("gameList");
|
||||
if (!root) {
|
||||
LOG(LogError) << "Couldn't find <gameList> node in gamelist \"" << xmlpath << "\"";
|
||||
return;
|
||||
}
|
||||
|
||||
pugi::xml_node alternativeEmulator = doc.child("alternativeEmulator");
|
||||
if (alternativeEmulator) {
|
||||
std::string label = alternativeEmulator.child("label").text().get();
|
||||
if (label != "") {
|
||||
bool validLabel = false;
|
||||
for (auto command : system->getSystemEnvData()->mLaunchCommands) {
|
||||
if (command.second == label)
|
||||
validLabel = true;
|
||||
}
|
||||
if (validLabel) {
|
||||
system->setAlternativeEmulator(label);
|
||||
LOG(LogDebug) << "Gamelist::parseGamelist(): System \"" << system->getName()
|
||||
<< "\" has a valid alternativeEmulator entry: \"" << label << "\"";
|
||||
}
|
||||
else {
|
||||
system->setAlternativeEmulator("<INVALID>" + label);
|
||||
LOG(LogWarning) << "System \"" << system->getName()
|
||||
<< "\" has an invalid alternativeEmulator entry that does "
|
||||
"not match any command tag in es_systems.xml: \""
|
||||
<< label << "\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string relativeTo = system->getStartPath();
|
||||
bool showHiddenFiles = Settings::getInstance()->getBool("ShowHiddenFiles");
|
||||
|
||||
std::vector<std::string> tagList = {"game", "folder"};
|
||||
FileType typeList[2] = {GAME, FOLDER};
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
std::string tag = tagList[i];
|
||||
FileType type = typeList[i];
|
||||
for (pugi::xml_node fileNode = root.child(tag.c_str()); fileNode;
|
||||
fileNode = fileNode.next_sibling(tag.c_str())) {
|
||||
const std::string path = Utils::FileSystem::resolveRelativePath(
|
||||
fileNode.child("path").text().get(), relativeTo, false);
|
||||
|
||||
if (!trustGamelist && !Utils::FileSystem::exists(path)) {
|
||||
LOG(LogWarning) << (type == GAME ? "File \"" : "Folder \"") << path
|
||||
<< "\" does not exist, ignoring entry";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip hidden files, check both the file itself and the directory in which
|
||||
// it is located.
|
||||
if (!showHiddenFiles &&
|
||||
(Utils::FileSystem::isHidden(path) ||
|
||||
Utils::FileSystem::isHidden(Utils::FileSystem::getParent(path)))) {
|
||||
LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping hidden file \"" << path
|
||||
<< "\"";
|
||||
continue;
|
||||
}
|
||||
|
||||
FileData* file = findOrCreateFile(system, path, type);
|
||||
if (!file) {
|
||||
LOG(LogError) << "Couldn't find or create \"" << path << "\", skipping entry";
|
||||
continue;
|
||||
}
|
||||
else if (!file->isArcadeAsset()) {
|
||||
std::string defaultName = file->metadata.get("name");
|
||||
file->metadata = MetaDataList::createFromXML(GAME_METADATA, fileNode, relativeTo);
|
||||
|
||||
// Make sure a name gets set if one doesn't exist.
|
||||
if (file->metadata.get("name").empty())
|
||||
file->metadata.set("name", defaultName);
|
||||
|
||||
file->metadata.resetChangedFlag();
|
||||
}
|
||||
else {
|
||||
// Skip arcade asset entries as these will not be used in any way inside
|
||||
// the application.
|
||||
LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping arcade asset \""
|
||||
<< file->getName() << "\"";
|
||||
delete file;
|
||||
continue;
|
||||
}
|
||||
// If the game is flagged as hidden and the option has not been set to show hidden
|
||||
// games, then delete the entry. This leaves no trace of the entry at all in ES
|
||||
// but that is fine as the option to show hidden files is defined as requiring an
|
||||
// application restart.
|
||||
if (!Settings::getInstance()->getBool("ShowHiddenGames")) {
|
||||
if (file->getHidden()) {
|
||||
LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping hidden "
|
||||
<< (type == GAME ? "file" : "folder") << " entry \""
|
||||
<< file->getName() << "\""
|
||||
<< " (\"" << file->getPath() << "\")";
|
||||
delete file;
|
||||
}
|
||||
// Also delete any folders which are empty, i.e. all their entries are hidden.
|
||||
else if (file->getType() == FOLDER && file->getChildren().size() == 0) {
|
||||
delete file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addFileDataNode(pugi::xml_node& parent,
|
||||
const FileData* file,
|
||||
const std::string& tag,
|
||||
SystemData* system)
|
||||
{
|
||||
// Create game and add to parent node.
|
||||
pugi::xml_node newNode = parent.append_child(tag.c_str());
|
||||
|
||||
// Write metadata.
|
||||
file->metadata.appendToXML(newNode, true, system->getStartPath());
|
||||
|
||||
// First element is "name", there's only one element and the name is the default.
|
||||
if (newNode.children().begin() == newNode.child("name") &&
|
||||
++newNode.children().begin() == newNode.children().end() &&
|
||||
newNode.child("name").text().get() == file->getDisplayName()) {
|
||||
|
||||
// If the only info is the default name, don't bother
|
||||
// with this node, delete it and ultimately do nothing.
|
||||
parent.remove_child(newNode);
|
||||
}
|
||||
else {
|
||||
// There's something useful in there so we'll keep the node, add the path.
|
||||
|
||||
// Try and make the path relative if we can so things still
|
||||
// work if we change the ROM folder location in the future.
|
||||
newNode.prepend_child("path").text().set(
|
||||
Utils::FileSystem::createRelativePath(file->getPath(), system->getStartPath(), false)
|
||||
.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void updateGamelist(SystemData* system, bool updateAlternativeEmulator)
|
||||
{
|
||||
// We do this by reading the XML again, adding changes and then writing them back,
|
||||
// because there might be information missing in our systemdata which we would otherwise
|
||||
// miss in the new XML file. We have the complete information for every game though, so
|
||||
// we can simply remove a game we already have in the system from the XML, and then add
|
||||
// it back from its GameData information...
|
||||
if (Settings::getInstance()->getBool("IgnoreGamelist"))
|
||||
return;
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_node root;
|
||||
std::string xmlReadPath = system->getGamelistPath(false);
|
||||
bool hasAlternativeEmulatorTag = false;
|
||||
|
||||
if (Utils::FileSystem::exists(xmlReadPath)) {
|
||||
// Parse an existing file first.
|
||||
|
||||
#if defined(_WIN64)
|
||||
pugi::xml_parse_result result =
|
||||
doc.load_file(Utils::String::stringToWideString(xmlReadPath).c_str());
|
||||
#else
|
||||
pugi::xml_parse_result result = doc.load_file(xmlReadPath.c_str());
|
||||
#endif
|
||||
|
||||
if (!result) {
|
||||
LOG(LogError) << "Error parsing gamelist file \"" << xmlReadPath
|
||||
<< "\": " << result.description();
|
||||
return;
|
||||
}
|
||||
|
||||
root = doc.child("gameList");
|
||||
if (!root) {
|
||||
LOG(LogError) << "Couldn't find <gameList> node in gamelist \"" << xmlReadPath << "\"";
|
||||
return;
|
||||
}
|
||||
if (updateAlternativeEmulator) {
|
||||
pugi::xml_node alternativeEmulator = doc.child("alternativeEmulator");
|
||||
|
||||
if (alternativeEmulator)
|
||||
hasAlternativeEmulatorTag = true;
|
||||
|
||||
if (system->getAlternativeEmulator() != "") {
|
||||
if (!alternativeEmulator) {
|
||||
doc.prepend_child("alternativeEmulator");
|
||||
alternativeEmulator = doc.child("alternativeEmulator");
|
||||
}
|
||||
|
||||
pugi::xml_node label = alternativeEmulator.child("label");
|
||||
|
||||
if (label && system->getAlternativeEmulator() !=
|
||||
alternativeEmulator.child("label").text().get()) {
|
||||
alternativeEmulator.remove_child(label);
|
||||
alternativeEmulator.prepend_child("label").text().set(
|
||||
system->getAlternativeEmulator().c_str());
|
||||
}
|
||||
else if (!label) {
|
||||
alternativeEmulator.prepend_child("label").text().set(
|
||||
system->getAlternativeEmulator().c_str());
|
||||
}
|
||||
}
|
||||
else if (alternativeEmulator) {
|
||||
doc.remove_child("alternativeEmulator");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (updateAlternativeEmulator && system->getAlternativeEmulator() != "") {
|
||||
pugi::xml_node alternativeEmulator = doc.prepend_child("alternativeEmulator");
|
||||
alternativeEmulator.prepend_child("label").text().set(
|
||||
system->getAlternativeEmulator().c_str());
|
||||
}
|
||||
// Set up an empty gamelist to append to.
|
||||
root = doc.append_child("gameList");
|
||||
}
|
||||
|
||||
// Now we have all the information from the XML file, so iterate
|
||||
// through all our games and add the information from there.
|
||||
FileData* rootFolder = system->getRootFolder();
|
||||
if (rootFolder != nullptr) {
|
||||
int numUpdated = 0;
|
||||
|
||||
// Get only files, no folders.
|
||||
std::vector<FileData*> files = rootFolder->getFilesRecursive(GAME | FOLDER);
|
||||
// Iterate through all files, checking if they're already in the XML file.
|
||||
for (std::vector<FileData*>::const_iterator fit = files.cbegin(); // Line break.
|
||||
fit != files.cend(); ++fit) {
|
||||
const std::string tag = ((*fit)->getType() == GAME) ? "game" : "folder";
|
||||
|
||||
// Do not touch if it wasn't changed and is not flagged for deletion.
|
||||
if (!(*fit)->metadata.wasChanged() && !(*fit)->getDeletionFlag())
|
||||
continue;
|
||||
|
||||
// Check if the file already exists in the XML file.
|
||||
// If it does, remove the entry before adding it back.
|
||||
for (pugi::xml_node fileNode = root.child(tag.c_str()); fileNode;
|
||||
fileNode = fileNode.next_sibling(tag.c_str())) {
|
||||
pugi::xml_node pathNode = fileNode.child("path");
|
||||
if (!pathNode) {
|
||||
LOG(LogError) << "<" << tag << "> node contains no <path> child";
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string nodePath =
|
||||
Utils::FileSystem::getCanonicalPath(Utils::FileSystem::resolveRelativePath(
|
||||
pathNode.text().get(), system->getStartPath(), true));
|
||||
std::string gamePath = Utils::FileSystem::getCanonicalPath((*fit)->getPath());
|
||||
|
||||
if (nodePath == gamePath) {
|
||||
// Found it
|
||||
root.remove_child(fileNode);
|
||||
if ((*fit)->getDeletionFlag())
|
||||
++numUpdated;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the game to the file, unless it's flagged for deletion.
|
||||
if (!(*fit)->getDeletionFlag()) {
|
||||
addFileDataNode(root, *fit, tag, system);
|
||||
(*fit)->metadata.resetChangedFlag();
|
||||
++numUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
// Now write the file.
|
||||
if (numUpdated > 0 || updateAlternativeEmulator) {
|
||||
// Make sure the folders leading up to this path exist (or the write will fail).
|
||||
std::string xmlWritePath(system->getGamelistPath(true));
|
||||
Utils::FileSystem::createDirectory(Utils::FileSystem::getParent(xmlWritePath));
|
||||
|
||||
if (updateAlternativeEmulator) {
|
||||
if (hasAlternativeEmulatorTag && system->getAlternativeEmulator() == "") {
|
||||
LOG(LogDebug) << "Gamelist::updateGamelist(): Removed the "
|
||||
"alternativeEmulator tag for system \""
|
||||
<< system->getName() << "\" as the default emulator \""
|
||||
<< system->getSystemEnvData()->mLaunchCommands.front().second
|
||||
<< "\" was selected";
|
||||
}
|
||||
else if (system->getAlternativeEmulator() != "") {
|
||||
LOG(LogDebug) << "Gamelist::updateGamelist(): "
|
||||
"Added/updated the alternativeEmulator tag for system \""
|
||||
<< system->getName() << "\" to \""
|
||||
<< system->getAlternativeEmulator() << "\"";
|
||||
}
|
||||
}
|
||||
if (numUpdated > 0) {
|
||||
LOG(LogDebug) << "Gamelist::updateGamelist(): Added/updated " << numUpdated
|
||||
<< (numUpdated == 1 ? " entity in \"" : " entities in \"")
|
||||
<< xmlWritePath << "\"";
|
||||
}
|
||||
#if defined(_WIN64)
|
||||
if (!doc.save_file(Utils::String::stringToWideString(xmlWritePath).c_str())) {
|
||||
#else
|
||||
if (!doc.save_file(xmlWritePath.c_str())) {
|
||||
#endif
|
||||
LOG(LogError) << "Error saving gamelist.xml to \"" << xmlWritePath
|
||||
<< "\" (for system " << system->getName() << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG(LogError) << "Found no root folder for system \"" << system->getName() << "\"";
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// EmulationStation Desktop Edition
|
||||
// GamelistFileParse.h
|
||||
//
|
||||
// Parses and updates the gamelist.xml files.
|
||||
//
|
||||
|
||||
#ifndef ES_APP_GAMELIST_FILE_PARSE_H
|
||||
#define ES_APP_GAMELIST_FILE_PARSE_H
|
||||
|
||||
class SystemData;
|
||||
|
||||
// Loads gamelist.xml data into a SystemData.
|
||||
void parseGamelist(SystemData* system);
|
||||
|
||||
// Writes currently loaded metadata for a SystemData to gamelist.xml.
|
||||
void updateGamelist(SystemData* system, bool updateAlternativeEmulator = false);
|
||||
|
||||
#endif // ES_APP_GAMELIST_FILE_PARSE_H
|
433
es-app/src/GamelistFileParser.cpp
Normal file
433
es-app/src/GamelistFileParser.cpp
Normal file
|
@ -0,0 +1,433 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// EmulationStation Desktop Edition
|
||||
// GamelistFileParser.cpp
|
||||
//
|
||||
// Parses and updates the gamelist.xml files.
|
||||
//
|
||||
|
||||
#include "GamelistFileParser.h"
|
||||
|
||||
#include "FileData.h"
|
||||
#include "Log.h"
|
||||
#include "Settings.h"
|
||||
#include "SystemData.h"
|
||||
#include "utils/FileSystemUtil.h"
|
||||
#include "utils/StringUtil.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace GamelistFileParser
|
||||
{
|
||||
FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType type)
|
||||
{
|
||||
// First, verify that path is within the system's root folder.
|
||||
FileData* root = system->getRootFolder();
|
||||
bool contains = false;
|
||||
std::string relative = Utils::FileSystem::removeCommonPath(path, root->getPath(), contains);
|
||||
|
||||
if (!contains) {
|
||||
LOG(LogError) << "Path \"" << path << "\" is outside system path \""
|
||||
<< system->getStartPath() << "\"";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Utils::FileSystem::StringList pathList = Utils::FileSystem::getPathList(relative);
|
||||
auto path_it = pathList.begin();
|
||||
FileData* treeNode = root;
|
||||
bool found = false;
|
||||
while (path_it != pathList.end()) {
|
||||
const std::unordered_map<std::string, FileData*>& children =
|
||||
treeNode->getChildrenByFilename();
|
||||
|
||||
std::string key = *path_it;
|
||||
found = children.find(key) != children.cend();
|
||||
if (found) {
|
||||
treeNode = children.at(key);
|
||||
}
|
||||
|
||||
// This is the end.
|
||||
if (path_it == --pathList.end()) {
|
||||
if (found)
|
||||
return treeNode;
|
||||
|
||||
if (type == FOLDER) {
|
||||
LOG(LogWarning) << "A folder defined in the gamelist file does not exist:";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Handle the special situation where a file exists and has an entry in the
|
||||
// gamelist.xml file but the file extension is not configured in es_systems.xml.
|
||||
const std::vector<std::string> extensions =
|
||||
system->getSystemEnvData()->mSearchExtensions;
|
||||
|
||||
if (std::find(extensions.cbegin(), extensions.cend(),
|
||||
Utils::FileSystem::getExtension(path)) == extensions.cend()) {
|
||||
LOG(LogWarning) << "File \"" << path
|
||||
<< "\" is present in gamelist.xml but the extension is not "
|
||||
"configured in es_systems.xml";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileData* file = new FileData(type, path, system->getSystemEnvData(), system);
|
||||
|
||||
// Skipping arcade assets from gamelist.
|
||||
if (!file->isArcadeAsset())
|
||||
treeNode->addChild(file);
|
||||
return file;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// Don't create folders unless they're including any games.
|
||||
// If the type is FOLDER it's going to be empty, so don't bother.
|
||||
if (type == FOLDER) {
|
||||
LOG(LogWarning) << "A folder defined in the gamelist file does not exist:";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create missing folder.
|
||||
FileData* folder = new FileData(
|
||||
FOLDER, Utils::FileSystem::getStem(treeNode->getPath()) + "/" + *path_it,
|
||||
system->getSystemEnvData(), system);
|
||||
treeNode->addChild(folder);
|
||||
treeNode = folder;
|
||||
}
|
||||
|
||||
++path_it;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void parseGamelist(SystemData* system)
|
||||
{
|
||||
bool trustGamelist = Settings::getInstance()->getBool("ParseGamelistOnly");
|
||||
std::string xmlpath = system->getGamelistPath(false);
|
||||
|
||||
if (!Utils::FileSystem::exists(xmlpath)) {
|
||||
LOG(LogDebug) << "Gamelist::parseGamelist(): System \"" << system->getName()
|
||||
<< "\" does not have a gamelist.xml file";
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(LogInfo) << "Parsing gamelist file \"" << xmlpath << "\"...";
|
||||
|
||||
pugi::xml_document doc;
|
||||
#if defined(_WIN64)
|
||||
pugi::xml_parse_result result =
|
||||
doc.load_file(Utils::String::stringToWideString(xmlpath).c_str());
|
||||
#else
|
||||
pugi::xml_parse_result result = doc.load_file(xmlpath.c_str());
|
||||
#endif
|
||||
|
||||
if (!result) {
|
||||
LOG(LogError) << "Error parsing gamelist file \"" << xmlpath
|
||||
<< "\": " << result.description();
|
||||
return;
|
||||
}
|
||||
|
||||
pugi::xml_node root = doc.child("gameList");
|
||||
if (!root) {
|
||||
LOG(LogError) << "Couldn't find <gameList> node in gamelist \"" << xmlpath << "\"";
|
||||
return;
|
||||
}
|
||||
|
||||
pugi::xml_node alternativeEmulator = doc.child("alternativeEmulator");
|
||||
if (alternativeEmulator) {
|
||||
std::string label = alternativeEmulator.child("label").text().get();
|
||||
if (label != "") {
|
||||
bool validLabel = false;
|
||||
for (auto command : system->getSystemEnvData()->mLaunchCommands) {
|
||||
if (command.second == label)
|
||||
validLabel = true;
|
||||
}
|
||||
if (validLabel) {
|
||||
system->setAlternativeEmulator(label);
|
||||
LOG(LogDebug) << "Gamelist::parseGamelist(): System \"" << system->getName()
|
||||
<< "\" has a valid alternativeEmulator entry: \"" << label
|
||||
<< "\"";
|
||||
}
|
||||
else {
|
||||
system->setAlternativeEmulator("<INVALID>" + label);
|
||||
LOG(LogWarning) << "System \"" << system->getName()
|
||||
<< "\" has an invalid alternativeEmulator entry that does "
|
||||
"not match any command tag in es_systems.xml: \""
|
||||
<< label << "\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string relativeTo = system->getStartPath();
|
||||
bool showHiddenFiles = Settings::getInstance()->getBool("ShowHiddenFiles");
|
||||
|
||||
std::vector<std::string> tagList = {"game", "folder"};
|
||||
FileType typeList[2] = {GAME, FOLDER};
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
std::string tag = tagList[i];
|
||||
FileType type = typeList[i];
|
||||
for (pugi::xml_node fileNode = root.child(tag.c_str()); fileNode;
|
||||
fileNode = fileNode.next_sibling(tag.c_str())) {
|
||||
const std::string path = Utils::FileSystem::resolveRelativePath(
|
||||
fileNode.child("path").text().get(), relativeTo, false);
|
||||
|
||||
if (!trustGamelist && !Utils::FileSystem::exists(path)) {
|
||||
LOG(LogWarning) << (type == GAME ? "File \"" : "Folder \"") << path
|
||||
<< "\" does not exist, ignoring entry";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip hidden files, check both the file itself and the directory in which
|
||||
// it is located.
|
||||
if (!showHiddenFiles &&
|
||||
(Utils::FileSystem::isHidden(path) ||
|
||||
Utils::FileSystem::isHidden(Utils::FileSystem::getParent(path)))) {
|
||||
LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping hidden file \"" << path
|
||||
<< "\"";
|
||||
continue;
|
||||
}
|
||||
|
||||
FileData* file = findOrCreateFile(system, path, type);
|
||||
if (!file) {
|
||||
LOG(LogError) << "Couldn't find or create \"" << path << "\", skipping entry";
|
||||
continue;
|
||||
}
|
||||
else if (!file->isArcadeAsset()) {
|
||||
std::string defaultName = file->metadata.get("name");
|
||||
file->metadata =
|
||||
MetaDataList::createFromXML(GAME_METADATA, fileNode, relativeTo);
|
||||
|
||||
// Make sure a name gets set if one doesn't exist.
|
||||
if (file->metadata.get("name").empty())
|
||||
file->metadata.set("name", defaultName);
|
||||
|
||||
file->metadata.resetChangedFlag();
|
||||
}
|
||||
else {
|
||||
// Skip arcade asset entries as these will not be used in any way inside
|
||||
// the application.
|
||||
LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping arcade asset \""
|
||||
<< file->getName() << "\"";
|
||||
delete file;
|
||||
continue;
|
||||
}
|
||||
// If the game is flagged as hidden and the option has not been set to show hidden
|
||||
// games, then delete the entry. This leaves no trace of the entry at all in ES
|
||||
// but that is fine as the option to show hidden files is defined as requiring an
|
||||
// application restart.
|
||||
if (!Settings::getInstance()->getBool("ShowHiddenGames")) {
|
||||
if (file->getHidden()) {
|
||||
LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping hidden "
|
||||
<< (type == GAME ? "file" : "folder") << " entry \""
|
||||
<< file->getName() << "\""
|
||||
<< " (\"" << file->getPath() << "\")";
|
||||
delete file;
|
||||
}
|
||||
// Also delete any folders which are empty, i.e. all their entries are hidden.
|
||||
else if (file->getType() == FOLDER && file->getChildren().size() == 0) {
|
||||
delete file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addFileDataNode(pugi::xml_node& parent,
|
||||
const FileData* file,
|
||||
const std::string& tag,
|
||||
SystemData* system)
|
||||
{
|
||||
// Create game and add to parent node.
|
||||
pugi::xml_node newNode = parent.append_child(tag.c_str());
|
||||
|
||||
// Write metadata.
|
||||
file->metadata.appendToXML(newNode, true, system->getStartPath());
|
||||
|
||||
// First element is "name", there's only one element and the name is the default.
|
||||
if (newNode.children().begin() == newNode.child("name") &&
|
||||
++newNode.children().begin() == newNode.children().end() &&
|
||||
newNode.child("name").text().get() == file->getDisplayName()) {
|
||||
|
||||
// If the only info is the default name, don't bother
|
||||
// with this node, delete it and ultimately do nothing.
|
||||
parent.remove_child(newNode);
|
||||
}
|
||||
else {
|
||||
// There's something useful in there so we'll keep the node, add the path.
|
||||
|
||||
// Try and make the path relative if we can so things still
|
||||
// work if we change the ROM folder location in the future.
|
||||
newNode.prepend_child("path").text().set(
|
||||
Utils::FileSystem::createRelativePath(file->getPath(), system->getStartPath(),
|
||||
false)
|
||||
.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void updateGamelist(SystemData* system, bool updateAlternativeEmulator)
|
||||
{
|
||||
// We do this by reading the XML again, adding changes and then writing them back,
|
||||
// because there might be information missing in our systemdata which we would otherwise
|
||||
// miss in the new XML file. We have the complete information for every game though, so
|
||||
// we can simply remove a game we already have in the system from the XML, and then add
|
||||
// it back from its GameData information...
|
||||
if (Settings::getInstance()->getBool("IgnoreGamelist"))
|
||||
return;
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_node root;
|
||||
std::string xmlReadPath = system->getGamelistPath(false);
|
||||
bool hasAlternativeEmulatorTag = false;
|
||||
|
||||
if (Utils::FileSystem::exists(xmlReadPath)) {
|
||||
// Parse an existing file first.
|
||||
|
||||
#if defined(_WIN64)
|
||||
pugi::xml_parse_result result =
|
||||
doc.load_file(Utils::String::stringToWideString(xmlReadPath).c_str());
|
||||
#else
|
||||
pugi::xml_parse_result result = doc.load_file(xmlReadPath.c_str());
|
||||
#endif
|
||||
|
||||
if (!result) {
|
||||
LOG(LogError) << "Error parsing gamelist file \"" << xmlReadPath
|
||||
<< "\": " << result.description();
|
||||
return;
|
||||
}
|
||||
|
||||
root = doc.child("gameList");
|
||||
if (!root) {
|
||||
LOG(LogError) << "Couldn't find <gameList> node in gamelist \"" << xmlReadPath
|
||||
<< "\"";
|
||||
return;
|
||||
}
|
||||
if (updateAlternativeEmulator) {
|
||||
pugi::xml_node alternativeEmulator = doc.child("alternativeEmulator");
|
||||
|
||||
if (alternativeEmulator)
|
||||
hasAlternativeEmulatorTag = true;
|
||||
|
||||
if (system->getAlternativeEmulator() != "") {
|
||||
if (!alternativeEmulator) {
|
||||
doc.prepend_child("alternativeEmulator");
|
||||
alternativeEmulator = doc.child("alternativeEmulator");
|
||||
}
|
||||
|
||||
pugi::xml_node label = alternativeEmulator.child("label");
|
||||
|
||||
if (label && system->getAlternativeEmulator() !=
|
||||
alternativeEmulator.child("label").text().get()) {
|
||||
alternativeEmulator.remove_child(label);
|
||||
alternativeEmulator.prepend_child("label").text().set(
|
||||
system->getAlternativeEmulator().c_str());
|
||||
}
|
||||
else if (!label) {
|
||||
alternativeEmulator.prepend_child("label").text().set(
|
||||
system->getAlternativeEmulator().c_str());
|
||||
}
|
||||
}
|
||||
else if (alternativeEmulator) {
|
||||
doc.remove_child("alternativeEmulator");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (updateAlternativeEmulator && system->getAlternativeEmulator() != "") {
|
||||
pugi::xml_node alternativeEmulator = doc.prepend_child("alternativeEmulator");
|
||||
alternativeEmulator.prepend_child("label").text().set(
|
||||
system->getAlternativeEmulator().c_str());
|
||||
}
|
||||
// Set up an empty gamelist to append to.
|
||||
root = doc.append_child("gameList");
|
||||
}
|
||||
|
||||
// Now we have all the information from the XML file, so iterate
|
||||
// through all our games and add the information from there.
|
||||
FileData* rootFolder = system->getRootFolder();
|
||||
if (rootFolder != nullptr) {
|
||||
int numUpdated = 0;
|
||||
|
||||
// Get only files, no folders.
|
||||
std::vector<FileData*> files = rootFolder->getFilesRecursive(GAME | FOLDER);
|
||||
// Iterate through all files, checking if they're already in the XML file.
|
||||
for (std::vector<FileData*>::const_iterator fit = files.cbegin(); // Line break.
|
||||
fit != files.cend(); ++fit) {
|
||||
const std::string tag = ((*fit)->getType() == GAME) ? "game" : "folder";
|
||||
|
||||
// Do not touch if it wasn't changed and is not flagged for deletion.
|
||||
if (!(*fit)->metadata.wasChanged() && !(*fit)->getDeletionFlag())
|
||||
continue;
|
||||
|
||||
// Check if the file already exists in the XML file.
|
||||
// If it does, remove the entry before adding it back.
|
||||
for (pugi::xml_node fileNode = root.child(tag.c_str()); fileNode;
|
||||
fileNode = fileNode.next_sibling(tag.c_str())) {
|
||||
pugi::xml_node pathNode = fileNode.child("path");
|
||||
if (!pathNode) {
|
||||
LOG(LogError) << "<" << tag << "> node contains no <path> child";
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string nodePath =
|
||||
Utils::FileSystem::getCanonicalPath(Utils::FileSystem::resolveRelativePath(
|
||||
pathNode.text().get(), system->getStartPath(), true));
|
||||
std::string gamePath = Utils::FileSystem::getCanonicalPath((*fit)->getPath());
|
||||
|
||||
if (nodePath == gamePath) {
|
||||
// Found it
|
||||
root.remove_child(fileNode);
|
||||
if ((*fit)->getDeletionFlag())
|
||||
++numUpdated;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the game to the file, unless it's flagged for deletion.
|
||||
if (!(*fit)->getDeletionFlag()) {
|
||||
addFileDataNode(root, *fit, tag, system);
|
||||
(*fit)->metadata.resetChangedFlag();
|
||||
++numUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
// Now write the file.
|
||||
if (numUpdated > 0 || updateAlternativeEmulator) {
|
||||
// Make sure the folders leading up to this path exist (or the write will fail).
|
||||
std::string xmlWritePath(system->getGamelistPath(true));
|
||||
Utils::FileSystem::createDirectory(Utils::FileSystem::getParent(xmlWritePath));
|
||||
|
||||
if (updateAlternativeEmulator) {
|
||||
if (hasAlternativeEmulatorTag && system->getAlternativeEmulator() == "") {
|
||||
LOG(LogDebug) << "Gamelist::updateGamelist(): Removed the "
|
||||
"alternativeEmulator tag for system \""
|
||||
<< system->getName() << "\" as the default emulator \""
|
||||
<< system->getSystemEnvData()->mLaunchCommands.front().second
|
||||
<< "\" was selected";
|
||||
}
|
||||
else if (system->getAlternativeEmulator() != "") {
|
||||
LOG(LogDebug) << "Gamelist::updateGamelist(): "
|
||||
"Added/updated the alternativeEmulator tag for system \""
|
||||
<< system->getName() << "\" to \""
|
||||
<< system->getAlternativeEmulator() << "\"";
|
||||
}
|
||||
}
|
||||
if (numUpdated > 0) {
|
||||
LOG(LogDebug) << "Gamelist::updateGamelist(): Added/updated " << numUpdated
|
||||
<< (numUpdated == 1 ? " entity in \"" : " entities in \"")
|
||||
<< xmlWritePath << "\"";
|
||||
}
|
||||
#if defined(_WIN64)
|
||||
if (!doc.save_file(Utils::String::stringToWideString(xmlWritePath).c_str())) {
|
||||
#else
|
||||
if (!doc.save_file(xmlWritePath.c_str())) {
|
||||
#endif
|
||||
LOG(LogError) << "Error saving gamelist.xml to \"" << xmlWritePath
|
||||
<< "\" (for system " << system->getName() << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG(LogError) << "Found no root folder for system \"" << system->getName() << "\"";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace GamelistFileParser
|
24
es-app/src/GamelistFileParser.h
Normal file
24
es-app/src/GamelistFileParser.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// EmulationStation Desktop Edition
|
||||
// GamelistFileParser.h
|
||||
//
|
||||
// Parses and updates the gamelist.xml files.
|
||||
//
|
||||
|
||||
#ifndef ES_APP_GAMELIST_FILE_PARSER_H
|
||||
#define ES_APP_GAMELIST_FILE_PARSER_H
|
||||
|
||||
class SystemData;
|
||||
|
||||
namespace GamelistFileParser
|
||||
{
|
||||
// Loads gamelist.xml data into a SystemData.
|
||||
void parseGamelist(SystemData* system);
|
||||
|
||||
// Writes currently loaded metadata for a SystemData to gamelist.xml.
|
||||
void updateGamelist(SystemData* system, bool updateAlternativeEmulator = false);
|
||||
|
||||
} // namespace GamelistFileParser
|
||||
|
||||
#endif // ES_APP_GAMELIST_FILE_PARSER_H
|
|
@ -14,7 +14,7 @@
|
|||
#include "CollectionSystemsManager.h"
|
||||
#include "FileFilterIndex.h"
|
||||
#include "FileSorts.h"
|
||||
#include "GamelistFileParse.h"
|
||||
#include "GamelistFileParser.h"
|
||||
#include "Log.h"
|
||||
#include "Settings.h"
|
||||
#include "ThemeData.h"
|
||||
|
@ -212,7 +212,7 @@ SystemData::SystemData(const std::string& name,
|
|||
}
|
||||
|
||||
if (!Settings::getInstance()->getBool("IgnoreGamelist"))
|
||||
parseGamelist(this);
|
||||
GamelistFileParser::parseGamelist(this);
|
||||
|
||||
setupSystemSortType(mRootFolder);
|
||||
|
||||
|
@ -1255,7 +1255,7 @@ void SystemData::writeMetaData()
|
|||
return;
|
||||
|
||||
// Save changed game data back to xml.
|
||||
updateGamelist(this);
|
||||
GamelistFileParser::updateGamelist(this);
|
||||
}
|
||||
|
||||
void SystemData::onMetaDataSavePoint()
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
#include "guis/GuiAlternativeEmulators.h"
|
||||
|
||||
#include "GamelistFileParse.h"
|
||||
#include "GamelistFileParser.h"
|
||||
#include "SystemData.h"
|
||||
#include "views/ViewController.h"
|
||||
|
||||
|
@ -160,7 +160,7 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system)
|
|||
system->setAlternativeEmulator("");
|
||||
else
|
||||
system->setAlternativeEmulator(entry.second);
|
||||
updateGamelist(system, true);
|
||||
GamelistFileParser::updateGamelist(system, true);
|
||||
|
||||
if (entry.second == system->getSystemEnvData()->mLaunchCommands.front().second) {
|
||||
if (system->getSystemEnvData()->mLaunchCommands.front().second == "") {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include "CollectionSystemsManager.h"
|
||||
#include "FileData.h"
|
||||
#include "FileFilterIndex.h"
|
||||
#include "GamelistFileParse.h"
|
||||
#include "GamelistFileParser.h"
|
||||
#include "MameNames.h"
|
||||
#include "SystemData.h"
|
||||
#include "Window.h"
|
||||
|
@ -675,7 +675,7 @@ void GuiMetaDataEd::save()
|
|||
// not really good as the gamelist will be written twice, but it's a very special and
|
||||
// hopefully rare situation.
|
||||
if (hideGameWhileHidden)
|
||||
updateGamelist(mScraperParams.system);
|
||||
GamelistFileParser::updateGamelist(mScraperParams.system);
|
||||
|
||||
// Enter game in index.
|
||||
mScraperParams.system->getIndex()->addToIndex(mScraperParams.game);
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
#include "CollectionSystemsManager.h"
|
||||
#include "FileFilterIndex.h"
|
||||
#include "GamelistFileParse.h"
|
||||
#include "GamelistFileParser.h"
|
||||
#include "MameNames.h"
|
||||
#include "SystemData.h"
|
||||
#include "Window.h"
|
||||
|
@ -273,7 +273,7 @@ void GuiScraperMulti::acceptResult(const ScraperSearchResult& result)
|
|||
search.system->getIndex()->removeFromIndex(search.game);
|
||||
|
||||
GuiScraperSearch::saveMetadata(result, search.game->metadata, search.game);
|
||||
updateGamelist(search.system);
|
||||
GamelistFileParser::updateGamelist(search.system);
|
||||
|
||||
search.system->getIndex()->addToIndex(search.game);
|
||||
|
||||
|
|
Loading…
Reference in a new issue