2012-08-02 01:43:55 +00:00
|
|
|
#include "XMLReader.h"
|
|
|
|
#include "SystemData.h"
|
|
|
|
#include "pugiXML/pugixml.hpp"
|
|
|
|
#include <boost/filesystem.hpp>
|
2013-01-04 23:31:51 +00:00
|
|
|
#include "Log.h"
|
2013-10-13 19:07:48 +00:00
|
|
|
#include "Settings.h"
|
2012-08-02 01:43:55 +00:00
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
namespace fs = boost::filesystem;
|
|
|
|
|
|
|
|
// example: removeCommonPath("/home/pi/roms/nes/foo/bar.nes", "/home/pi/roms/nes/") returns "foo/bar.nes"
|
|
|
|
fs::path removeCommonPath(const fs::path& path, const fs::path& relativeTo, bool& contains)
|
2012-08-02 01:43:55 +00:00
|
|
|
{
|
2013-12-12 22:04:15 +00:00
|
|
|
fs::path p = fs::canonical(path);
|
|
|
|
fs::path r = fs::canonical(relativeTo);
|
|
|
|
|
|
|
|
if(p.root_path() != r.root_path())
|
2012-08-02 01:43:55 +00:00
|
|
|
{
|
2013-12-12 22:04:15 +00:00
|
|
|
contains = false;
|
|
|
|
return p;
|
2012-08-02 01:43:55 +00:00
|
|
|
}
|
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
fs::path result;
|
2012-08-08 00:50:45 +00:00
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
// find point of divergence
|
|
|
|
auto itr_path = p.begin();
|
|
|
|
auto itr_relative_to = r.begin();
|
|
|
|
while(*itr_path == *itr_relative_to && itr_path != p.end() && itr_relative_to != r.end())
|
|
|
|
{
|
|
|
|
++itr_path;
|
|
|
|
++itr_relative_to;
|
|
|
|
}
|
2013-06-14 15:16:16 +00:00
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
if(itr_relative_to != r.end())
|
|
|
|
{
|
|
|
|
contains = false;
|
|
|
|
return p;
|
|
|
|
}
|
2012-08-08 00:50:45 +00:00
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
while(itr_path != p.end())
|
|
|
|
{
|
|
|
|
if(*itr_path != fs::path("."))
|
|
|
|
result = result / *itr_path;
|
2012-08-08 00:50:45 +00:00
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
++itr_path;
|
|
|
|
}
|
2012-08-08 00:50:45 +00:00
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
contains = true;
|
|
|
|
return result;
|
|
|
|
}
|
2012-08-08 00:50:45 +00:00
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
FileData* findOrCreateFile(SystemData* system, const boost::filesystem::path& path, FileType type)
|
|
|
|
{
|
|
|
|
// first, verify that path is within the system's root folder
|
|
|
|
FileData* root = system->getRootFolder();
|
|
|
|
|
|
|
|
bool contains = false;
|
|
|
|
fs::path relative = removeCommonPath(path, root->getPath(), contains);
|
|
|
|
if(!contains)
|
2012-08-08 00:50:45 +00:00
|
|
|
{
|
2013-12-12 22:04:15 +00:00
|
|
|
LOG(LogError) << "File path \"" << path << "\" is outside system path \"" << system->getStartPath() << "\"";
|
|
|
|
return NULL;
|
|
|
|
}
|
2012-08-08 00:50:45 +00:00
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
auto path_it = relative.begin();
|
|
|
|
FileData* treeNode = root;
|
|
|
|
bool found = false;
|
|
|
|
while(path_it != relative.end())
|
|
|
|
{
|
|
|
|
const std::vector<FileData*>& children = treeNode->getChildren();
|
|
|
|
found = false;
|
|
|
|
for(auto child_it = children.begin(); child_it != children.end(); child_it++)
|
2012-08-08 00:50:45 +00:00
|
|
|
{
|
2013-12-12 22:04:15 +00:00
|
|
|
if((*child_it)->getPath().filename() == *path_it)
|
2012-08-08 00:50:45 +00:00
|
|
|
{
|
2013-12-12 22:04:15 +00:00
|
|
|
treeNode = *child_it;
|
|
|
|
found = true;
|
2012-08-08 00:50:45 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
// this is the end
|
|
|
|
if(path_it == --relative.end())
|
2012-08-08 00:50:45 +00:00
|
|
|
{
|
2013-12-12 22:04:15 +00:00
|
|
|
if(found)
|
|
|
|
return treeNode;
|
|
|
|
|
|
|
|
FileData* file = new FileData(type, path, system);
|
|
|
|
treeNode->addChild(file);
|
|
|
|
return file;
|
2012-08-08 00:50:45 +00:00
|
|
|
}
|
2013-12-12 22:04:15 +00:00
|
|
|
|
|
|
|
if(!found)
|
|
|
|
{
|
|
|
|
// don't create folders unless it's leading up to a game
|
|
|
|
// if type is a folder it's gonna be empty, so don't bother
|
|
|
|
if(type == FOLDER)
|
|
|
|
{
|
|
|
|
LOG(LogWarning) << "folder doesn't already exist, won't create metadata for folder";
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create missing folder
|
|
|
|
FileData* folder = new FileData(FOLDER, treeNode->getPath().stem() / *path_it, system);
|
|
|
|
treeNode->addChild(folder);
|
|
|
|
treeNode = folder;
|
|
|
|
}
|
|
|
|
|
|
|
|
path_it++;
|
2012-08-08 00:50:45 +00:00
|
|
|
}
|
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
return NULL;
|
2012-08-08 00:50:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void parseGamelist(SystemData* system)
|
|
|
|
{
|
2012-12-17 19:29:43 +00:00
|
|
|
std::string xmlpath = system->getGamelistPath();
|
2012-08-08 00:50:45 +00:00
|
|
|
|
2013-10-13 21:40:36 +00:00
|
|
|
if(!boost::filesystem::exists(xmlpath))
|
2012-12-17 19:29:43 +00:00
|
|
|
return;
|
2012-08-08 00:50:45 +00:00
|
|
|
|
2013-01-06 20:33:50 +00:00
|
|
|
LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"...";
|
2012-08-02 01:43:55 +00:00
|
|
|
|
|
|
|
pugi::xml_document doc;
|
|
|
|
pugi::xml_parse_result result = doc.load_file(xmlpath.c_str());
|
|
|
|
|
|
|
|
if(!result)
|
|
|
|
{
|
2013-01-06 20:33:50 +00:00
|
|
|
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description();
|
2012-08-02 01:43:55 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:16:49 +00:00
|
|
|
pugi::xml_node root = doc.child("gameList");
|
2012-08-02 01:43:55 +00:00
|
|
|
if(!root)
|
|
|
|
{
|
2013-08-14 12:16:49 +00:00
|
|
|
LOG(LogError) << "Could not find <gameList> node in gamelist \"" << xmlpath << "\"!";
|
2012-08-02 01:43:55 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
const char* tagList[2] = { "game", "folder" };
|
|
|
|
FileType typeList[2] = { GAME, FOLDER };
|
|
|
|
for(int i = 0; i < 2; i++)
|
2012-08-02 01:43:55 +00:00
|
|
|
{
|
2013-12-12 22:04:15 +00:00
|
|
|
const char* tag = tagList[i];
|
|
|
|
FileType type = typeList[i];
|
|
|
|
for(pugi::xml_node fileNode = root.child(tag); fileNode; fileNode = fileNode.next_sibling(tag))
|
2012-08-02 01:43:55 +00:00
|
|
|
{
|
2013-12-12 22:04:15 +00:00
|
|
|
boost::filesystem::path path = boost::filesystem::path(fileNode.child("path").text().get());
|
|
|
|
|
|
|
|
if(!boost::filesystem::exists(path))
|
|
|
|
{
|
|
|
|
LOG(LogWarning) << "File \"" << path << "\" does not exist! Ignoring.";
|
|
|
|
continue;
|
|
|
|
}
|
2012-08-08 00:50:45 +00:00
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
FileData* file = findOrCreateFile(system, path, type);
|
|
|
|
if(!file)
|
|
|
|
{
|
|
|
|
LOG(LogError) << "Error finding/creating FileData for \"" << path << "\", skipping.";
|
|
|
|
continue;
|
|
|
|
}
|
2012-08-02 01:43:55 +00:00
|
|
|
|
2013-08-14 12:16:49 +00:00
|
|
|
//load the metadata
|
2013-12-12 22:04:15 +00:00
|
|
|
std::string defaultName = file->metadata.get("name");
|
|
|
|
file->metadata = MetaDataList::createFromXML(GAME_METADATA, fileNode);
|
2012-08-02 01:43:55 +00:00
|
|
|
|
2013-08-14 12:16:49 +00:00
|
|
|
//make sure name gets set if one didn't exist
|
2013-12-12 22:04:15 +00:00
|
|
|
if(file->metadata.get("name").empty())
|
|
|
|
file->metadata.set("name", defaultName);
|
2012-08-02 01:43:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-06-28 12:54:14 +00:00
|
|
|
|
2013-11-06 01:41:49 +00:00
|
|
|
void addGameDataNode(pugi::xml_node& parent, const FileData* game, SystemData* system)
|
2013-06-28 12:54:14 +00:00
|
|
|
{
|
|
|
|
//create game and add to parent node
|
2013-08-14 12:16:49 +00:00
|
|
|
pugi::xml_node newGame = parent.append_child("game");
|
|
|
|
|
|
|
|
//write metadata
|
2013-11-06 01:41:49 +00:00
|
|
|
game->metadata.appendToXML(newGame, true);
|
2013-08-14 12:16:49 +00:00
|
|
|
|
|
|
|
if(newGame.children().begin() == newGame.child("name") //first element is name
|
|
|
|
&& ++newGame.children().begin() == newGame.children().end() //theres only one element
|
2013-11-06 01:41:49 +00:00
|
|
|
&& newGame.child("name").text().get() == getCleanFileName(game->getPath())) //the name is the default
|
2013-08-14 12:16:49 +00:00
|
|
|
{
|
|
|
|
//if the only info is the default name, don't bother with this node
|
|
|
|
parent.remove_child(newGame);
|
|
|
|
}else{
|
|
|
|
//there's something useful in there so we'll keep the node, add the path
|
2013-11-06 01:41:49 +00:00
|
|
|
newGame.prepend_child("path").text().set(game->getPath().generic_string().c_str());
|
2013-06-28 12:54:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
void addFileDataNode(pugi::xml_node& parent, const FileData* file, const char* tag, SystemData* system)
|
|
|
|
{
|
|
|
|
//create game and add to parent node
|
|
|
|
pugi::xml_node newNode = parent.append_child(tag);
|
|
|
|
|
|
|
|
//write metadata
|
|
|
|
file->metadata.appendToXML(newNode, true);
|
|
|
|
|
|
|
|
if(newNode.children().begin() == newNode.child("name") //first element is name
|
|
|
|
&& ++newNode.children().begin() == newNode.children().end() //theres only one element
|
|
|
|
&& newNode.child("name").text().get() == getCleanFileName(file->getPath())) //the name is the default
|
|
|
|
{
|
|
|
|
//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
|
|
|
|
newNode.prepend_child("path").text().set(file->getPath().generic_string().c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-28 12:54:14 +00:00
|
|
|
void updateGamelist(SystemData* system)
|
|
|
|
{
|
|
|
|
//We do this by reading the XML again, adding changes and then writing it back,
|
|
|
|
//because there might be information missing in our systemdata which would then miss in the new XML.
|
|
|
|
//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...
|
|
|
|
|
2013-10-26 19:07:30 +00:00
|
|
|
if(Settings::getInstance()->getBool("DisableGamelistWrites") || Settings::getInstance()->getBool("IGNOREGAMELIST"))
|
2013-10-13 19:07:48 +00:00
|
|
|
return;
|
|
|
|
|
2013-06-28 12:54:14 +00:00
|
|
|
std::string xmlpath = system->getGamelistPath();
|
|
|
|
|
|
|
|
pugi::xml_document doc;
|
|
|
|
|
2013-10-16 22:05:02 +00:00
|
|
|
if(boost::filesystem::exists(xmlpath))
|
2013-08-14 12:16:49 +00:00
|
|
|
{
|
2013-10-16 22:05:02 +00:00
|
|
|
//parse an existing file first
|
|
|
|
pugi::xml_parse_result result = doc.load_file(xmlpath.c_str());
|
2013-12-12 22:04:15 +00:00
|
|
|
|
2013-10-16 22:05:02 +00:00
|
|
|
if(!result)
|
|
|
|
{
|
|
|
|
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
//set up an empty gamelist to append to
|
|
|
|
doc.append_child("gameList");
|
2013-10-16 22:49:14 +00:00
|
|
|
|
|
|
|
//make sure the folders leading up to this path exist (or the XML file write will fail later on)
|
|
|
|
boost::filesystem::path path(xmlpath);
|
|
|
|
boost::filesystem::create_directories(path.parent_path());
|
2013-06-28 12:54:14 +00:00
|
|
|
}
|
|
|
|
|
2013-10-16 22:05:02 +00:00
|
|
|
|
2013-08-14 12:16:49 +00:00
|
|
|
pugi::xml_node root = doc.child("gameList");
|
|
|
|
if(!root)
|
|
|
|
{
|
|
|
|
LOG(LogError) << "Could not find <gameList> node in gamelist \"" << xmlpath << "\"!";
|
2013-06-28 12:54:14 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//now we have all the information from the XML. now iterate through all our games and add information from there
|
2013-11-06 01:41:49 +00:00
|
|
|
FileData* rootFolder = system->getRootFolder();
|
2013-08-14 12:16:49 +00:00
|
|
|
if (rootFolder != nullptr)
|
|
|
|
{
|
2013-06-28 12:54:14 +00:00
|
|
|
//get only files, no folders
|
2013-12-12 22:04:15 +00:00
|
|
|
std::vector<FileData*> files = rootFolder->getFilesRecursive(GAME | FOLDER);
|
2013-06-28 12:54:14 +00:00
|
|
|
//iterate through all files, checking if they're already in the XML
|
|
|
|
std::vector<FileData*>::const_iterator fit = files.cbegin();
|
2013-08-14 12:16:49 +00:00
|
|
|
while(fit != files.cend())
|
|
|
|
{
|
2013-12-12 22:04:15 +00:00
|
|
|
const char* tag = ((*fit)->getType() == GAME) ? "game" : "folder";
|
|
|
|
|
|
|
|
// check if the file already exists in the XML
|
|
|
|
// if it does, remove it before adding
|
|
|
|
for(pugi::xml_node fileNode = root.child(tag); fileNode; fileNode = fileNode.next_sibling(tag))
|
2013-08-14 12:16:49 +00:00
|
|
|
{
|
2013-12-12 22:04:15 +00:00
|
|
|
pugi::xml_node pathNode = fileNode.child("path");
|
|
|
|
if(!pathNode)
|
2013-08-14 12:16:49 +00:00
|
|
|
{
|
2013-12-12 22:04:15 +00:00
|
|
|
LOG(LogError) << "<" << tag << "> node contains no <path> child!";
|
|
|
|
continue;
|
2013-06-28 12:54:14 +00:00
|
|
|
}
|
2013-08-14 12:16:49 +00:00
|
|
|
|
2013-12-12 22:04:15 +00:00
|
|
|
boost::filesystem::path nodePath(pathNode.text().get());
|
|
|
|
boost::filesystem::path gamePath((*fit)->getPath());
|
|
|
|
if(fs::canonical(nodePath) == fs::canonical(gamePath))
|
|
|
|
{
|
|
|
|
// found it
|
|
|
|
root.remove_child(fileNode);
|
|
|
|
break;
|
|
|
|
}
|
2013-06-28 12:54:14 +00:00
|
|
|
}
|
2013-12-12 22:04:15 +00:00
|
|
|
|
|
|
|
// it was either removed or never existed to begin with; either way, we can add it now
|
|
|
|
addFileDataNode(root, *fit, tag, system);
|
|
|
|
|
2013-06-28 12:54:14 +00:00
|
|
|
++fit;
|
|
|
|
}
|
|
|
|
//now write the file
|
|
|
|
if (!doc.save_file(xmlpath.c_str())) {
|
2013-08-14 12:16:49 +00:00
|
|
|
LOG(LogError) << "Error saving gamelist.xml file \"" << xmlpath << "\"!";
|
2013-06-28 12:54:14 +00:00
|
|
|
}
|
2013-08-14 12:16:49 +00:00
|
|
|
}else{
|
2013-06-28 12:54:14 +00:00
|
|
|
LOG(LogError) << "Found no root folder for system \"" << system->getName() << "\"!";
|
|
|
|
}
|
|
|
|
}
|