New generic metadata backend.

This commit is contained in:
Aloshi 2013-08-14 07:16:49 -05:00
parent dbcb9aed37
commit 421797929d
14 changed files with 350 additions and 265 deletions

View file

@ -127,6 +127,7 @@ set(ES_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.h ${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Log.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Log.h
${CMAKE_CURRENT_SOURCE_DIR}/src/MathExp.h ${CMAKE_CURRENT_SOURCE_DIR}/src/MathExp.h
${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.h
${CMAKE_CURRENT_SOURCE_DIR}/src/platform.h ${CMAKE_CURRENT_SOURCE_DIR}/src/platform.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Renderer.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Renderer.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Settings.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Settings.h
@ -169,6 +170,7 @@ set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/Log.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Log.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/MathExp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/MathExp.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/platform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/platform.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Renderer_draw_gl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Renderer_draw_gl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Renderer_init.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Renderer_init.cpp

View file

@ -10,8 +10,8 @@ class FileData
public: public:
virtual ~FileData() { }; virtual ~FileData() { };
virtual bool isFolder() const = 0; virtual bool isFolder() const = 0;
virtual const std::string & getName() const = 0; virtual const std::string& getName() const = 0;
virtual const std::string & getPath() const = 0; virtual const std::string& getPath() const = 0;
}; };
#endif #endif

View file

@ -82,7 +82,7 @@ bool FolderData::compareRating(const FileData* file1, const FileData* file2)
const GameData * game1 = dynamic_cast<const GameData*>(file1); const GameData * game1 = dynamic_cast<const GameData*>(file1);
const GameData * game2 = dynamic_cast<const GameData*>(file2); const GameData * game2 = dynamic_cast<const GameData*>(file2);
if (game1 != nullptr && game2 != nullptr) { if (game1 != nullptr && game2 != nullptr) {
return game1->getRating() < game2->getRating(); return const_cast<GameData*>(game1)->metadata()->getFloat("rating") < const_cast<GameData*>(game2)->metadata()->getFloat("rating");
} }
return false; return false;
} }
@ -93,7 +93,7 @@ bool FolderData::compareUserRating(const FileData* file1, const FileData* file2)
const GameData * game1 = dynamic_cast<const GameData*>(file1); const GameData * game1 = dynamic_cast<const GameData*>(file1);
const GameData * game2 = dynamic_cast<const GameData*>(file2); const GameData * game2 = dynamic_cast<const GameData*>(file2);
if (game1 != nullptr && game2 != nullptr) { if (game1 != nullptr && game2 != nullptr) {
return game1->getUserRating() < game2->getUserRating(); return const_cast<GameData*>(game1)->metadata()->getFloat("userrating") < const_cast<GameData*>(game2)->metadata()->getFloat("userrating");
} }
return false; return false;
} }
@ -104,7 +104,7 @@ bool FolderData::compareTimesPlayed(const FileData* file1, const FileData* file2
const GameData * game1 = dynamic_cast<const GameData*>(file1); const GameData * game1 = dynamic_cast<const GameData*>(file1);
const GameData * game2 = dynamic_cast<const GameData*>(file2); const GameData * game2 = dynamic_cast<const GameData*>(file2);
if (game1 != nullptr && game2 != nullptr) { if (game1 != nullptr && game2 != nullptr) {
return game1->getTimesPlayed() < game2->getTimesPlayed(); return const_cast<GameData*>(game1)->metadata()->getInt("playcount") < const_cast<GameData*>(game2)->metadata()->getInt("playcount");
} }
return false; return false;
} }
@ -115,7 +115,7 @@ bool FolderData::compareLastPlayed(const FileData* file1, const FileData* file2)
const GameData * game1 = dynamic_cast<const GameData*>(file1); const GameData * game1 = dynamic_cast<const GameData*>(file1);
const GameData * game2 = dynamic_cast<const GameData*>(file2); const GameData * game2 = dynamic_cast<const GameData*>(file2);
if (game1 != nullptr && game2 != nullptr) { if (game1 != nullptr && game2 != nullptr) {
return game1->getLastPlayed() < game2->getLastPlayed(); return const_cast<GameData*>(game1)->metadata()->getTime("lastplayed") < const_cast<GameData*>(game2)->metadata()->getTime("lastplayed");
} }
return false; return false;
} }

View file

@ -1,23 +1,14 @@
#include "GameData.h" #include "GameData.h"
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <iostream> #include <iostream>
#include <ctime>
#include <sstream>
GameData::GameData(SystemData* system, std::string path)
const std::string GameData::xmlTagGameList = "gameList"; : mSystem(system), mPath(path), mBaseName(boost::filesystem::path(path).stem().string()), mMetaData(MetaDataList::getDefaultGameMDD())
const std::string GameData::xmlTagGame = "game";
const std::string GameData::xmlTagName = "name";
const std::string GameData::xmlTagPath = "path";
const std::string GameData::xmlTagDescription = "desc";
const std::string GameData::xmlTagImagePath = "image";
const std::string GameData::xmlTagRating = "rating";
const std::string GameData::xmlTagUserRating = "userrating";
const std::string GameData::xmlTagTimesPlayed = "timesplayed";
const std::string GameData::xmlTagLastPlayed = "lastplayed";
GameData::GameData(SystemData* system, std::string path, std::string name)
: mSystem(system), mPath(path), mName(name), mRating(0.0f), mUserRating(0.0f), mTimesPlayed(0), mLastPlayed(0)
{ {
if(mMetaData.get("name").empty())
mMetaData.set("name", mBaseName);
} }
bool GameData::isFolder() const bool GameData::isFolder() const
@ -25,90 +16,20 @@ bool GameData::isFolder() const
return false; return false;
} }
const std::string & GameData::getName() const const std::string& GameData::getName() const
{ {
return mName; return mMetaData.get("name");
} }
void GameData::setName(const std::string & name) const std::string& GameData::getPath() const
{
mName = name;
}
const std::string & GameData::getPath() const
{ {
return mPath; return mPath;
} }
void GameData::setPath(const std::string & path)
{
mPath = path;
}
const std::string & GameData::getDescription() const
{
return mDescription;
}
void GameData::setDescription(const std::string & description)
{
mDescription = description;
}
const std::string & GameData::getImagePath() const
{
return mImagePath;
}
void GameData::setImagePath(const std::string & imagePath)
{
mImagePath = imagePath;
}
float GameData::getRating() const
{
return mRating;
}
void GameData::setRating(float rating)
{
mRating = rating;
}
float GameData::getUserRating() const
{
return mUserRating;
}
void GameData::setUserRating(float rating)
{
mUserRating = rating;
}
size_t GameData::getTimesPlayed() const
{
return mTimesPlayed;
}
void GameData::setTimesPlayed(size_t timesPlayed)
{
mTimesPlayed = timesPlayed;
}
std::time_t GameData::getLastPlayed() const
{
return mLastPlayed;
}
void GameData::setLastPlayed(std::time_t lastPlayed)
{
mLastPlayed = lastPlayed;
}
std::string GameData::getBashPath() const std::string GameData::getBashPath() const
{ {
//a quick and dirty way to insert a backslash before most characters that would mess up a bash path //a quick and dirty way to insert a backslash before most characters that would mess up a bash path
std::string path = mPath; std::string path = getPath();
const char* invalidChars = " '\"\\!$^&*(){}[]?;<>"; const char* invalidChars = " '\"\\!$^&*(){}[]?;<>";
for(unsigned int i = 0; i < path.length(); i++) for(unsigned int i = 0; i < path.length(); i++)
@ -133,6 +54,26 @@ std::string GameData::getBashPath() const
//returns the boost::filesystem stem of our path - e.g. for "/foo/bar.rom" returns "bar" //returns the boost::filesystem stem of our path - e.g. for "/foo/bar.rom" returns "bar"
std::string GameData::getBaseName() const std::string GameData::getBaseName() const
{ {
boost::filesystem::path path(mPath); return mBaseName;
return path.stem().string(); }
void GameData::incTimesPlayed()
{
int timesPlayed = metadata()->getInt("playcount");
timesPlayed++;
std::stringstream ss();
metadata()->set("playcount", std::to_string(static_cast<long long>(timesPlayed)));
}
void GameData::lastPlayedNow()
{
std::stringstream ss;
ss << std::time(nullptr);
metadata()->set("lastplayed", ss.str());
}
MetaDataList* GameData::metadata()
{
return &mMetaData;
} }

View file

@ -2,70 +2,44 @@
#define _GAMEDATA_H_ #define _GAMEDATA_H_
#include <string> #include <string>
#include <ctime>
#include "FileData.h" #include "FileData.h"
#include "SystemData.h" #include "SystemData.h"
#include "MetaData.h"
//This class holds information about a game: at the least, its name, system, and path. Additional information is optional and read by other classes. //This class holds information about a game: at the least, its name, system, and path. Additional information is optional and read by other classes.
class GameData : public FileData class GameData : public FileData
{ {
public: public:
//static tag names for reading/writing XML documents. This might fail in PUGIXML_WCHAR_MODE GameData(SystemData* system, std::string path);
//TODO: The class should have member to read fromXML() and write toXML() probably...
static const std::string xmlTagGameList;
static const std::string xmlTagGame;
static const std::string xmlTagName;
static const std::string xmlTagPath;
static const std::string xmlTagDescription;
static const std::string xmlTagImagePath;
static const std::string xmlTagRating;
static const std::string xmlTagUserRating;
static const std::string xmlTagTimesPlayed;
static const std::string xmlTagLastPlayed;
GameData(SystemData* system, std::string path, std::string name); const std::string& getName() const override;
const std::string& getPath() const override;
const std::string & getName() const; void incTimesPlayed();
void setName(const std::string & name); void lastPlayedNow();
const std::string & getPath() const;
void setPath(const std::string & path);
const std::string & getDescription() const;
void setDescription(const std::string & description);
const std::string & getImagePath() const;
void setImagePath(const std::string & imagePath);
float getRating() const;
void setRating(float rating);
float getUserRating() const;
void setUserRating(float rating);
size_t getTimesPlayed() const;
void setTimesPlayed(size_t timesPlayed);
std::time_t getLastPlayed() const;
void setLastPlayed(std::time_t lastPlayed);
std::string getBashPath() const; std::string getBashPath() const;
std::string getBaseName() const; std::string getBaseName() const;
bool isFolder() const; bool isFolder() const override;
MetaDataList* metadata();
private: private:
SystemData* mSystem; SystemData* mSystem;
std::string mPath; const std::string mPath;
std::string mName; const std::string mBaseName;
//extra data //extra data
std::string mDescription; /*std::string mDescription;
std::string mImagePath; std::string mImagePath;
float mRating; float mRating;
float mUserRating; float mUserRating;
size_t mTimesPlayed; size_t mTimesPlayed;
std::time_t mLastPlayed; std::time_t mLastPlayed;*/
MetaDataList mMetaData;
}; };
#endif #endif

View file

@ -157,3 +157,12 @@ const Eigen::Affine3f GuiComponent::getTransform()
mTransform.translate(mPosition); mTransform.translate(mPosition);
return mTransform; return mTransform;
} }
void GuiComponent::setValue(const std::string& value)
{
}
std::string GuiComponent::getValue() const
{
return "";
}

View file

@ -19,10 +19,10 @@ public:
//Called when time passes. Default implementation also calls update(deltaTime) on children - so you should probably call GuiComponent::update(deltaTime) at some point. //Called when time passes. Default implementation also calls update(deltaTime) on children - so you should probably call GuiComponent::update(deltaTime) at some point.
virtual void update(int deltaTime); virtual void update(int deltaTime);
//Called when it's time to render. By default, just calls renderChildren(transform). //Called when it's time to render. By default, just calls renderChildren(parentTrans * getTransform()).
//You probably want to override this like so: //You probably want to override this like so:
//1. Calculate the new transform that your control will draw at with Eigen::Affine3f t = parentTrans * getTransform(). //1. Calculate the new transform that your control will draw at with Eigen::Affine3f t = parentTrans * getTransform().
//2. Set the renderer to use that new transform as the model matrix - Renderer::setModelMatrix(t.data()); //2. Set the renderer to use that new transform as the model matrix - Renderer::setMatrix(t);
//3. Draw your component. //3. Draw your component.
//4. Tell your children to render, based on your component's transform - renderChildren(t). //4. Tell your children to render, based on your component's transform - renderChildren(t).
virtual void render(const Eigen::Affine3f& parentTrans); virtual void render(const Eigen::Affine3f& parentTrans);
@ -51,6 +51,9 @@ public:
const Eigen::Affine3f getTransform(); const Eigen::Affine3f getTransform();
virtual std::string getValue() const;
virtual void setValue(const std::string& value);
protected: protected:
void renderChildren(const Eigen::Affine3f& transform) const; void renderChildren(const Eigen::Affine3f& transform) const;

126
src/MetaData.cpp Normal file
View file

@ -0,0 +1,126 @@
#include "MetaData.h"
#include "components/TextComponent.h"
#include "Log.h"
MetaDataList::MetaDataList()
{
}
MetaDataList::MetaDataList(const std::vector<MetaDataDecl>& mdd)
{
for(auto iter = mdd.begin(); iter != mdd.end(); iter++)
set(iter->key, iter->defaultValue);
}
std::vector<MetaDataDecl> MetaDataList::getDefaultGameMDD()
{
MetaDataDecl decls[] = {
{"name", MD_STRING, ""},
{"desc", MD_STRING, ""},
{"image", MD_IMAGE_PATH, ""},
{"rating", MD_RATING, "0"},
{"timesplayed", MD_INT, "0"},
{"playcount", MD_TIME, "0"}
};
std::vector<MetaDataDecl> mdd(decls, decls + sizeof(decls) / sizeof(decls[0]));
return mdd;
}
MetaDataList MetaDataList::createFromXML(const std::vector<MetaDataDecl>& mdd, pugi::xml_node node)
{
MetaDataList mdl;
for(auto iter = mdd.begin(); iter != mdd.end(); iter++)
{
pugi::xml_node md = node.child(iter->key.c_str());
if(md)
{
mdl.set(iter->key, md.text().get());
}else{
mdl.set(iter->key, iter->defaultValue);
}
}
return mdl;
}
void MetaDataList::appendToXML(pugi::xml_node parent, const std::vector<MetaDataDecl>& ignoreDefaults) const
{
for(auto iter = mMap.begin(); iter != mMap.end(); iter++)
{
bool write = true;
for(auto mddIter = ignoreDefaults.begin(); mddIter != ignoreDefaults.end(); mddIter++)
{
if(mddIter->key == iter->first)
{
if(iter->second == mddIter->defaultValue)
write = false;
break;
}
}
if(write)
parent.append_child(iter->first.c_str()).text().set(iter->second.c_str());
}
}
void MetaDataList::set(const std::string& key, const std::string& value)
{
mMap[key] = value;
}
const std::string& MetaDataList::get(const std::string& key) const
{
return mMap.at(key);
}
int MetaDataList::getInt(const std::string& key) const
{
return atoi(get(key).c_str());
}
float MetaDataList::getFloat(const std::string& key) const
{
return (float)atof(get(key).c_str());
}
std::time_t MetaDataList::getTime(const std::string& key) const
{
return (std::time_t) atoi(get(key).c_str());
}
GuiComponent* MetaDataList::makeDisplay(Window* window, MetaDataType as)
{
switch(as)
{
default:
TextComponent* comp = new TextComponent(window);
return comp;
}
}
GuiComponent* MetaDataList::makeEditor(Window* window, MetaDataType as)
{
switch(as)
{
default:
TextComponent* comp = new TextComponent(window);
return comp;
}
}
GuiComponent* MetaDataList::makeDisplay(Window* window, MetaDataDecl from)
{
GuiComponent* comp = makeDisplay(window, from.type);
comp->setValue(get(from.key));
return comp;
}
GuiComponent* MetaDataList::makeEditor(Window* window, MetaDataDecl from)
{
GuiComponent* comp = makeEditor(window, from.type);
comp->setValue(get(from.key));
return comp;
}

73
src/MetaData.h Normal file
View file

@ -0,0 +1,73 @@
#pragma once
#include "pugiXML/pugixml.hpp"
#include <string>
#include <map>
#include "GuiComponent.h"
#include <ctime>
enum MetaDataType
{
//generic types
MD_STRING,
MD_INT,
MD_FLOAT,
//specialized types
MD_IMAGE_PATH,
MD_RATING,
MD_TIME
};
struct MetaDataDecl
{
std::string key;
MetaDataType type;
std::string defaultValue;
};
class MetaDataList
{
public:
static std::vector<MetaDataDecl> getDefaultGameMDD();
static MetaDataList createFromXML(const std::vector<MetaDataDecl>& mdd, pugi::xml_node node);
//MetaDataDecl required to set our defaults.
MetaDataList(const std::vector<MetaDataDecl>& mdd);
void set(const std::string& key, const std::string& value);
const std::string& get(const std::string& key) const;
int getInt(const std::string& key) const;
float getFloat(const std::string& key) const;
std::time_t getTime(const std::string& key) const;
GuiComponent* makeDisplay(Window* window, MetaDataType as);
GuiComponent* makeDisplay(Window* window, MetaDataDecl from);
GuiComponent* makeEditor(Window* window, MetaDataType as);
GuiComponent* makeEditor(Window* window, MetaDataDecl from);
void appendToXML(pugi::xml_node parent, const std::vector<MetaDataDecl>& ignoreDefaults = std::vector<MetaDataDecl>()) const;
private:
MetaDataList();
std::map<std::string, std::string> mMap;
};
//options for storing metadata...
//store internally everything as a string - this is all going to be read to/from XML anyway, after all
//store using individual get/set functions ala Settings - this is a fair amount of work but the most explicit, for better or worse
//let's think about some of the special types we would like to support...
//image paths, sound paths, ratings, play counts
//these get represented behind-the-scenes as strings, floats, and integers, and are eventually saved as strings
//the only specialty is how they're edited and viewed, really
//so we need...
//to be able to iterate through the available metadata
//create components designed to either DISPLAY or EDIT a given piece of metadata
//save and load metadata

View file

@ -96,8 +96,8 @@ void SystemData::launchGame(Window* window, GameData* game)
window->normalizeNextUpdate(); window->normalizeNextUpdate();
//update number of times the game has been launched and the time //update number of times the game has been launched and the time
game->setTimesPlayed(game->getTimesPlayed() + 1); game->incTimesPlayed();
game->setLastPlayed(std::time(nullptr)); game->lastPlayedNow();
} }
void SystemData::populateFolder(FolderData* folder) void SystemData::populateFolder(FolderData* folder)
@ -145,7 +145,7 @@ void SystemData::populateFolder(FolderData* folder)
//if it matches, add it //if it matches, add it
if(chkExt == extension) if(chkExt == extension)
{ {
GameData* newGame = new GameData(this, filePath.generic_string(), filePath.stem().string()); GameData* newGame = new GameData(this, filePath.generic_string());
folder->pushFileData(newGame); folder->pushFileData(newGame);
isGame = true; isGame = true;
break; break;

View file

@ -90,11 +90,7 @@ GameData* createGameFromPath(std::string gameAbsPath, SystemData* system)
loops++; loops++;
} }
GameData* game = new GameData(system, gameAbsPath);
//find gameName
std::string gameName = gamePath.substr(separator + 1, gamePath.find(".", separator) - separator - 1);
GameData* game = new GameData(system, gameAbsPath, gameName);
folder->pushFileData(game); folder->pushFileData(game);
return game; return game;
} }
@ -117,19 +113,19 @@ void parseGamelist(SystemData* system)
return; return;
} }
pugi::xml_node root = doc.child(GameData::xmlTagGameList.c_str()); pugi::xml_node root = doc.child("gameList");
if(!root) if(!root)
{ {
LOG(LogError) << "Could not find <" << GameData::xmlTagGameList << "> node in gamelist \"" << xmlpath << "\"!"; LOG(LogError) << "Could not find <gameList> node in gamelist \"" << xmlpath << "\"!";
return; return;
} }
for(pugi::xml_node gameNode = root.child(GameData::xmlTagGame.c_str()); gameNode; gameNode = gameNode.next_sibling(GameData::xmlTagGame.c_str())) for(pugi::xml_node gameNode = root.child("game"); gameNode; gameNode = gameNode.next_sibling("game"))
{ {
pugi::xml_node pathNode = gameNode.child(GameData::xmlTagPath.c_str()); pugi::xml_node pathNode = gameNode.child("path");
if(!pathNode) if(!pathNode)
{ {
LOG(LogError) << "<" << GameData::xmlTagGame << "> node contains no <" << GameData::xmlTagPath << "> child!"; LOG(LogError) << "<game> node contains no <path> child!";
continue; continue;
} }
@ -137,11 +133,18 @@ void parseGamelist(SystemData* system)
boost::filesystem::path gamePath(pathNode.text().get()); boost::filesystem::path gamePath(pathNode.text().get());
std::string path = gamePath.generic_string(); std::string path = gamePath.generic_string();
//expand "." //expand '.'
if(path[0] == '.') if(path[0] == '.')
{ {
path.erase(0, 1); path.erase(0, 1);
path.insert(0, system->getRootFolder()->getPath()); path.insert(0, boost::filesystem::path(xmlpath).parent_path().generic_string());
}
//expand '~'
if(path[0] == '~')
{
path.erase(0, 1);
path.insert(0, getHomePath());
} }
if(boost::filesystem::exists(path)) if(boost::filesystem::exists(path))
@ -151,103 +154,36 @@ void parseGamelist(SystemData* system)
if(game == NULL) if(game == NULL)
game = createGameFromPath(path, system); game = createGameFromPath(path, system);
//actually gather the information in the XML doc, then pass it to the game's set method //load the metadata
std::string newName, newDesc, newImage; *(game->metadata()) = MetaDataList::createFromXML(MetaDataList::getDefaultGameMDD(), gameNode);
if(gameNode.child(GameData::xmlTagName.c_str())) //make sure name gets set if one didn't exist
{ if(game->metadata()->get("name").empty())
game->setName(gameNode.child(GameData::xmlTagName.c_str()).text().get()); game->metadata()->set("name", game->getBaseName());
} }else{
if(gameNode.child(GameData::xmlTagDescription.c_str()))
{
game->setDescription(gameNode.child(GameData::xmlTagDescription.c_str()).text().get());
}
if(gameNode.child(GameData::xmlTagImagePath.c_str()))
{
newImage = gameNode.child(GameData::xmlTagImagePath.c_str()).text().get();
//expand "."
if(newImage[0] == '.')
{
newImage.erase(0, 1);
boost::filesystem::path pathname(xmlpath);
newImage.insert(0, pathname.parent_path().generic_string() );
}
//if the image exist, set it
if(boost::filesystem::exists(newImage))
{
game->setImagePath(newImage);
}
}
//get rating and the times played from the XML doc
if(gameNode.child(GameData::xmlTagRating.c_str()))
{
float rating;
std::istringstream(gameNode.child(GameData::xmlTagRating.c_str()).text().get()) >> rating;
game->setRating(rating);
}
if(gameNode.child(GameData::xmlTagUserRating.c_str()))
{
float userRating;
std::istringstream(gameNode.child(GameData::xmlTagUserRating.c_str()).text().get()) >> userRating;
game->setUserRating(userRating);
}
if(gameNode.child(GameData::xmlTagTimesPlayed.c_str()))
{
size_t timesPlayed;
std::istringstream(gameNode.child(GameData::xmlTagTimesPlayed.c_str()).text().get()) >> timesPlayed;
game->setTimesPlayed(timesPlayed);
}
if(gameNode.child(GameData::xmlTagLastPlayed.c_str()))
{
std::time_t lastPlayed;
std::istringstream(gameNode.child(GameData::xmlTagLastPlayed.c_str()).text().get()) >> lastPlayed;
game->setLastPlayed(lastPlayed);
}
}
else{
LOG(LogWarning) << "Game at \"" << path << "\" does not exist!"; LOG(LogWarning) << "Game at \"" << path << "\" does not exist!";
} }
} }
} }
void addGameDataNode(pugi::xml_node & parent, const GameData * game) void addGameDataNode(pugi::xml_node& parent, const GameData* game)
{ {
//create game and add to parent node //create game and add to parent node
pugi::xml_node newGame = parent.append_child(GameData::xmlTagGame.c_str()); pugi::xml_node newGame = parent.append_child("game");
//add values
if (!game->getPath().empty()) {
pugi::xml_node pathNode = newGame.append_child(GameData::xmlTagPath.c_str());
//store path with generic directory seperators
boost::filesystem::path gamePath(game->getPath());
pathNode.text().set(gamePath.generic_string().c_str());
}
if (!game->getName().empty()) {
pugi::xml_node nameNode = newGame.append_child(GameData::xmlTagName.c_str());
nameNode.text().set(game->getName().c_str());
}
if (!game->getDescription().empty()) {
pugi::xml_node descriptionNode = newGame.append_child(GameData::xmlTagDescription.c_str());
descriptionNode.text().set(game->getDescription().c_str());
}
if (!game->getImagePath().empty()) {
pugi::xml_node imagePathNode = newGame.append_child(GameData::xmlTagImagePath.c_str());
imagePathNode.text().set(game->getImagePath().c_str());
}
//all other values are added regardless of their value
pugi::xml_node ratingNode = newGame.append_child(GameData::xmlTagRating.c_str());
ratingNode.text().set(std::to_string((long double)game->getRating()).c_str());
pugi::xml_node userRatingNode = newGame.append_child(GameData::xmlTagUserRating.c_str()); //write metadata
userRatingNode.text().set(std::to_string((long double)game->getUserRating()).c_str()); const_cast<GameData*>(game)->metadata()->appendToXML(newGame, MetaDataList::getDefaultGameMDD());
pugi::xml_node timesPlayedNode = newGame.append_child(GameData::xmlTagTimesPlayed.c_str()); if(newGame.children().begin() == newGame.child("name") //first element is name
timesPlayedNode.text().set(std::to_string((unsigned long long)game->getTimesPlayed()).c_str()); && ++newGame.children().begin() == newGame.children().end() //theres only one element
&& newGame.child("name").text().get() == game->getBaseName()) //the name is the default
pugi::xml_node lastPlayedNode = newGame.append_child(GameData::xmlTagLastPlayed.c_str()); {
lastPlayedNode.text().set(std::to_string((unsigned long long)game->getLastPlayed()).c_str()); //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
newGame.prepend_child("path").text().set(game->getPath().c_str());
}
} }
void updateGamelist(SystemData* system) void updateGamelist(SystemData* system)
@ -258,56 +194,64 @@ void updateGamelist(SystemData* system)
//we already have in the system from the XML, and then add it back from its GameData information... //we already have in the system from the XML, and then add it back from its GameData information...
std::string xmlpath = system->getGamelistPath(); std::string xmlpath = system->getGamelistPath();
if(xmlpath.empty()) { if(xmlpath.empty())
return; return;
}
LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\" before writing..."; LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\" before writing...";
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 " << result.description(); LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description();
return; return;
} }
pugi::xml_node root = doc.child(GameData::xmlTagGameList.c_str()); pugi::xml_node root = doc.child("gameList");
if(!root) { if(!root)
LOG(LogError) << "Could not find <" << GameData::xmlTagGameList << "> node in gamelist \"" << xmlpath << "\"!"; {
LOG(LogError) << "Could not find <gameList> node in gamelist \"" << xmlpath << "\"!";
return; return;
} }
//now we have all the information from the XML. now iterate through all our games and add information from there //now we have all the information from the XML. now iterate through all our games and add information from there
FolderData * rootFolder = system->getRootFolder(); FolderData * rootFolder = system->getRootFolder();
if (rootFolder != nullptr) { if (rootFolder != nullptr)
{
//get only files, no folders //get only files, no folders
std::vector<FileData*> files = rootFolder->getFilesRecursive(true); std::vector<FileData*> files = rootFolder->getFilesRecursive(true);
//iterate through all files, checking if they're already in the XML //iterate through all files, checking if they're already in the XML
std::vector<FileData*>::const_iterator fit = files.cbegin(); std::vector<FileData*>::const_iterator fit = files.cbegin();
while(fit != files.cend()) { while(fit != files.cend())
{
//try to cast to gamedata //try to cast to gamedata
const GameData * game = dynamic_cast<const GameData*>(*fit); const GameData * game = dynamic_cast<const GameData*>(*fit);
if (game != nullptr) { if (game != nullptr)
{
//worked. check if this games' path can be found somewhere in the XML //worked. check if this games' path can be found somewhere in the XML
for(pugi::xml_node gameNode = root.child(GameData::xmlTagGame.c_str()); gameNode; gameNode = gameNode.next_sibling(GameData::xmlTagGame.c_str())) { for(pugi::xml_node gameNode = root.child("game"); gameNode; gameNode = gameNode.next_sibling("game"))
{
//get path from game node //get path from game node
pugi::xml_node pathNode = gameNode.child(GameData::xmlTagPath.c_str()); pugi::xml_node pathNode = gameNode.child("path");
if(!pathNode) if(!pathNode)
{ {
LOG(LogError) << "<" << GameData::xmlTagGame << "> node contains no <" << GameData::xmlTagPath << "> child!"; LOG(LogError) << "<game> node contains no <path> child!";
continue; continue;
} }
//check paths. use the same directory separators //check paths. use the same directory separators
boost::filesystem::path nodePath(pathNode.text().get()); boost::filesystem::path nodePath(pathNode.text().get());
boost::filesystem::path gamePath(game->getPath()); boost::filesystem::path gamePath(game->getPath());
if (nodePath.generic_string() == gamePath.generic_string()) { if (nodePath.generic_string() == gamePath.generic_string())
{
//found the game. remove it. it will be added again later with updated values //found the game. remove it. it will be added again later with updated values
root.remove_child(gameNode); root.remove_child(gameNode);
//break node search loop //break node search loop
break; break;
} }
} }
//either the game content was removed, because it needs to be updated, //either the game content was removed, because it needs to be updated,
//or didn't exist in the first place, so just add it //or didn't exist in the first place, so just add it
addGameDataNode(root, game); addGameDataNode(root, game);
@ -316,10 +260,9 @@ void updateGamelist(SystemData* system)
} }
//now write the file //now write the file
if (!doc.save_file(xmlpath.c_str())) { if (!doc.save_file(xmlpath.c_str())) {
LOG(LogError) << "Error saving XML file \"" << xmlpath << "\"!"; LOG(LogError) << "Error saving gamelist.xml file \"" << xmlpath << "\"!";
} }
} }else{
else {
LOG(LogError) << "Found no root folder for system \"" << system->getName() << "\"!"; LOG(LogError) << "Found no root folder for system \"" << system->getName() << "\"!";
} }
} }

View file

@ -353,11 +353,12 @@ void GuiGameList::updateDetailData()
//if we've selected a game //if we've selected a game
if(mList.getSelectedObject() && !mList.getSelectedObject()->isFolder()) if(mList.getSelectedObject() && !mList.getSelectedObject()->isFolder())
{ {
GameData* game = (GameData*)mList.getSelectedObject();
//set image to either "not found" image or metadata image //set image to either "not found" image or metadata image
if(((GameData*)mList.getSelectedObject())->getImagePath().empty()) if(game->metadata()->get("image").empty())
mScreenshot.setImage(mTheme->getString("imageNotFoundPath")); mScreenshot.setImage(mTheme->getString("imageNotFoundPath"));
else else
mScreenshot.setImage(((GameData*)mList.getSelectedObject())->getImagePath()); mScreenshot.setImage(game->metadata()->get("image"));
Eigen::Vector3f imgOffset = Eigen::Vector3f(Renderer::getScreenWidth() * 0.10f, 0, 0); Eigen::Vector3f imgOffset = Eigen::Vector3f(Renderer::getScreenWidth() * 0.10f, 0, 0);
mScreenshot.setPosition(getImagePos() - imgOffset); mScreenshot.setPosition(getImagePos() - imgOffset);
@ -372,7 +373,7 @@ void GuiGameList::updateDetailData()
mDescription.setPosition(0, 0); mDescription.setPosition(0, 0);
mDescription.setSize(Eigen::Vector2f(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03f), 0)); mDescription.setSize(Eigen::Vector2f(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03f), 0));
mDescription.setText(((GameData*)mList.getSelectedObject())->getDescription()); mDescription.setText(game->metadata()->get("desc"));
}else{ }else{
mScreenshot.setImage(""); mScreenshot.setImage("");
mDescription.setText(""); mDescription.setText("");

View file

@ -93,3 +93,13 @@ void TextComponent::calculateExtent()
} }
} }
} }
void TextComponent::setValue(const std::string& value)
{
setText(value);
}
std::string TextComponent::getValue() const
{
return mText;
}

View file

@ -18,6 +18,9 @@ public:
void render(const Eigen::Affine3f& parentTrans) override; void render(const Eigen::Affine3f& parentTrans) override;
std::string getValue() const override;
void setValue(const std::string& value) override;
private: private:
std::shared_ptr<Font> getFont() const; std::shared_ptr<Font> getFont() const;