Add functions for writing data to gamelist.xml

Also add a "rating" and "timePlayed" variable to GameData. Some cleanup
in GameData and FolderData. Added sorting functions for rating and
timesPlayed to FolderData. Testing and UI support still tbd.
This commit is contained in:
Bim Overbohm 2013-06-28 14:54:14 +02:00
parent 171ca9a657
commit 556b9fa3fe
8 changed files with 352 additions and 71 deletions

View file

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

View file

@ -1,13 +1,14 @@
#include "FolderData.h"
#include "SystemData.h"
#include "GameData.h"
#include <algorithm>
#include <iostream>
bool FolderData::isFolder() { return true; }
std::string FolderData::getName() { return mName; }
std::string FolderData::getPath() { return mPath; }
bool FolderData::isFolder() const { return true; }
const std::string & FolderData::getName() const { return mName; }
const std::string & FolderData::getPath() const { return mPath; }
unsigned int FolderData::getFileCount() { return mFileVector.size(); }
FileData* FolderData::getFile(unsigned int i) { return mFileVector.at(i); }
FolderData::FolderData(SystemData* system, std::string path, std::string name)
{
@ -32,7 +33,7 @@ void FolderData::pushFileData(FileData* file)
}
//returns if file1 should come before file2
bool filesort(FileData* file1, FileData* file2)
bool FolderData::compareFileName(const FileData* file1, const FileData* file2)
{
std::string name1 = file1->getName();
std::string name2 = file2->getName();
@ -43,25 +44,39 @@ bool filesort(FileData* file1, FileData* file2)
{
if(toupper(name1[i]) != toupper(name2[i]))
{
if(toupper(name1[i]) < toupper(name2[i]))
{
return true;
}else{
return false;
}
return toupper(name1[i]) < toupper(name2[i]);
}
}
if(name1.length() < name2.length())
return true;
else
return false;
return name1.length() < name2.length();
}
bool FolderData::compareRating(const FileData* file1, const FileData* file2)
{
//we need game data. try to cast
const GameData * game1 = dynamic_cast<const GameData*>(file1);
const GameData * game2 = dynamic_cast<const GameData*>(file2);
if (game1 != nullptr && game2 != nullptr) {
return game1->getRating() < game2->getRating();
}
return false;
}
bool FolderData::compareTimesPlayed(const FileData* file1, const FileData* file2)
{
//we need game data. try to cast
const GameData * game1 = dynamic_cast<const GameData*>(file1);
const GameData * game2 = dynamic_cast<const GameData*>(file2);
if (game1 != nullptr && game2 != nullptr) {
return game1->getTimesPlayed() < game2->getTimesPlayed();
}
return false;
}
//sort this folder and any subfolders
void FolderData::sort()
{
std::sort(mFileVector.begin(), mFileVector.end(), filesort);
std::sort(mFileVector.begin(), mFileVector.end(), compareFileName);
for(unsigned int i = 0; i < mFileVector.size(); i++)
{
@ -69,3 +84,56 @@ void FolderData::sort()
((FolderData*)mFileVector.at(i))->sort();
}
}
FileData* FolderData::getFile(unsigned int i) const
{
return mFileVector.at(i);
}
std::vector<FileData*> FolderData::getFiles(bool onlyFiles) const
{
std::vector<FileData*> temp;
//now check if a child is a folder and get those children in turn
std::vector<FileData*>::const_iterator fdit = mFileVector.cbegin();
while(fdit != mFileVector.cend()) {
//dynamically try to cast to FolderData type
FolderData * folder = dynamic_cast<FolderData*>(*fdit);
if (folder != nullptr) {
//add this only when user wanted it
if (!onlyFiles) {
temp.push_back(*fdit);
}
}
else {
temp.push_back(*fdit);
}
++fdit;
}
return temp;
}
std::vector<FileData*> FolderData::getFilesRecursive(bool onlyFiles) const
{
std::vector<FileData*> temp;
//now check if a child is a folder and get those children in turn
std::vector<FileData*>::const_iterator fdit = mFileVector.cbegin();
while(fdit != mFileVector.cend()) {
//dynamically try to cast to FolderData type
FolderData * folder = dynamic_cast<FolderData*>(*fdit);
if (folder != nullptr) {
//add this onyl when user wanted it
if (!onlyFiles) {
temp.push_back(*fdit);
}
//worked. Is actual folder data. recurse
std::vector<FileData*> children = folder->getFilesRecursive(onlyFiles);
//insert children into return vector
temp.insert(temp.end(), children.cbegin(), children.cend());
}
else {
temp.push_back(*fdit);
}
++fdit;
}
return temp;
}

View file

@ -13,16 +13,22 @@ public:
FolderData(SystemData* system, std::string path, std::string name);
~FolderData();
bool isFolder();
std::string getName();
std::string getPath();
bool isFolder() const;
const std::string & getName() const;
const std::string & getPath() const;
unsigned int getFileCount();
FileData* getFile(unsigned int i);
FileData* getFile(unsigned int i) const;
std::vector<FileData*> getFiles(bool onlyFiles = false) const;
std::vector<FileData*> getFilesRecursive(bool onlyFiles = false) const;
void pushFileData(FileData* file);
void sort();
static bool compareFileName(const FileData* file1, const FileData* file2);
static bool compareRating(const FileData* file1, const FileData* file2);
static bool compareTimesPlayed(const FileData* file1, const FileData* file2);
private:
SystemData* mSystem;
std::string mPath;

View file

@ -2,23 +2,88 @@
#include <boost/filesystem.hpp>
#include <iostream>
bool GameData::isFolder() { return false; }
std::string GameData::getName() { return mName; }
std::string GameData::getPath() { return mPath; }
std::string GameData::getDescription() { return mDescription; }
std::string GameData::getImagePath() { return mImagePath; }
const std::string GameData::xmlTagGameList = "gameList";
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::xmlTagTimesPlayed = "timesplayed";
GameData::GameData(SystemData* system, std::string path, std::string name)
: mSystem(system), mPath(path), mName(name), mRating(0), mTimesPlayed(0)
{
mSystem = system;
mPath = path;
mName = name;
mDescription = "";
mImagePath = "";
}
std::string GameData::getBashPath()
bool GameData::isFolder() const
{
return false;
}
const std::string & GameData::getName() const
{
return mName;
}
void GameData::setName(const std::string & name)
{
mName = name;
}
const std::string & GameData::getPath() const
{
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;
}
size_t GameData::getRating() const
{
return mRating;
}
void GameData::setRating(size_t rating)
{
mRating = rating;
}
size_t GameData::getTimesPlayed() const
{
return mTimesPlayed;
}
void GameData::setTimesPlayed(size_t timesPlayed)
{
mTimesPlayed = timesPlayed;
}
std::string GameData::getBashPath() const
{
//a quick and dirty way to insert a backslash before most characters that would mess up a bash path
std::string path = mPath;
@ -44,18 +109,8 @@ std::string GameData::getBashPath()
}
//returns the boost::filesystem stem of our path - e.g. for "/foo/bar.rom" returns "bar"
std::string GameData::getBaseName()
std::string GameData::getBaseName() const
{
boost::filesystem::path path(mPath);
return path.stem().string();
}
void GameData::set(std::string name, std::string description, std::string imagePath)
{
if(!name.empty())
mName = name;
if(!description.empty())
mDescription = description;
if(!imagePath.empty() && boost::filesystem::exists(imagePath))
mImagePath = imagePath;
}

View file

@ -9,19 +9,41 @@
class GameData : public FileData
{
public:
//static tag names for reading/writing XML documents. This might fail in PUGIXML_WCHAR_MODE
//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 xmlTagTimesPlayed;
GameData(SystemData* system, std::string path, std::string name);
void set(std::string name = "", std::string description = "", std::string imagePath = "");
const std::string & getName() const;
void setName(const std::string & name);
std::string getName();
std::string getPath();
std::string getBashPath();
std::string getBaseName();
const std::string & getPath() const;
void setPath(const std::string & path);
std::string getDescription();
std::string getImagePath();
const std::string & getDescription() const;
void setDescription(const std::string & description);
bool isFolder();
const std::string & getImagePath() const;
void setImagePath(const std::string & imagePath);
size_t getRating() const;
void setRating(size_t rating);
size_t getTimesPlayed() const;
void setTimesPlayed(size_t timesPlayed);
std::string getBashPath() const;
std::string getBaseName() const;
bool isFolder() const;
private:
SystemData* mSystem;
std::string mPath;
@ -30,6 +52,8 @@ private:
//extra data
std::string mDescription;
std::string mImagePath;
size_t mRating;
size_t mTimesPlayed;
};
#endif

View file

@ -50,6 +50,10 @@ SystemData::SystemData(std::string name, std::string descName, std::string start
SystemData::~SystemData()
{
//save changed game data back to xml
if(!Settings::getInstance()->getBool("IGNOREGAMELIST")) {
updateGamelist(this);
}
delete mRootFolder;
}
@ -90,6 +94,9 @@ void SystemData::launchGame(Window* window, GameData* game)
window->init();
VolumeControl::getInstance()->init();
AudioManager::getInstance()->init();
//update number of times the game has been launched
game->setTimesPlayed(game->getTimesPlayed() + 1);
}
void SystemData::populateFolder(FolderData* folder)

View file

@ -117,19 +117,19 @@ void parseGamelist(SystemData* system)
return;
}
pugi::xml_node root = doc.child("gameList");
pugi::xml_node root = doc.child(GameData::xmlTagGameList.c_str());
if(!root)
{
LOG(LogError) << "Could not find <gameList> node in gamelist \"" << xmlpath << "\"!";
LOG(LogError) << "Could not find <" << GameData::xmlTagGameList << "> node in gamelist \"" << xmlpath << "\"!";
return;
}
for(pugi::xml_node gameNode = root.child("game"); gameNode; gameNode = gameNode.next_sibling("game"))
for(pugi::xml_node gameNode = root.child(GameData::xmlTagGame.c_str()); gameNode; gameNode = gameNode.next_sibling(GameData::xmlTagGame.c_str()))
{
pugi::xml_node pathNode = gameNode.child("path");
pugi::xml_node pathNode = gameNode.child(GameData::xmlTagPath.c_str());
if(!pathNode)
{
LOG(LogError) << "<game> node contains no <path> child!";
LOG(LogError) << "<" << GameData::xmlTagGame << "> node contains no <" << GameData::xmlTagPath << "> child!";
continue;
}
@ -152,13 +152,17 @@ void parseGamelist(SystemData* system)
//actually gather the information in the XML doc, then pass it to the game's set method
std::string newName, newDesc, newImage;
if(gameNode.child("name"))
newName = gameNode.child("name").text().get();
if(gameNode.child("desc"))
newDesc = gameNode.child("desc").text().get();
if(gameNode.child("image"))
if(gameNode.child(GameData::xmlTagName.c_str()))
{
newImage = gameNode.child("image").text().get();
game->setName(gameNode.child(GameData::xmlTagName.c_str()).text().get());
}
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] == '.')
@ -168,15 +172,129 @@ void parseGamelist(SystemData* system)
newImage.insert(0, pathname.parent_path().string() );
}
//if the image doesn't exist, forget it
if(!boost::filesystem::exists(newImage))
newImage = "";
//if the image exist, set it
if(boost::filesystem::exists(newImage))
{
game->setImagePath(newImage);
}
}
game->set(newName, newDesc, newImage);
}else{
//get rating and the times played from the XML doc
if(gameNode.child(GameData::xmlTagRating.c_str()))
{
size_t rating;
std::istringstream(gameNode.child(GameData::xmlTagRating.c_str()).text().get()) >> rating;
game->setRating(rating);
}
if(gameNode.child(GameData::xmlTagTimesPlayed.c_str()))
{
size_t timesPlayed;
std::istringstream(gameNode.child(GameData::xmlTagTimesPlayed.c_str()).text().get()) >> timesPlayed;
game->setRating(timesPlayed);
}
}
else{
LOG(LogWarning) << "Game at \"" << path << "\" does not exist!";
}
}
}
void addGameDataNode(pugi::xml_node & parent, const GameData * game)
{
//create game and add to parent node
pugi::xml_node newGame = parent.append_child(GameData::xmlTagGame.c_str());
//add values
if (!game->getPath().empty()) {
pugi::xml_node pathNode = newGame.append_child(GameData::xmlTagPath.c_str());
pathNode.text().set(game->getPath().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((unsigned long long)game->getRating()).c_str());
pugi::xml_node timesPlayedNode = newGame.append_child(GameData::xmlTagTimesPlayed.c_str());
timesPlayedNode.text().set(std::to_string((unsigned long long)game->getTimesPlayed()).c_str());
}
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...
std::string xmlpath = system->getGamelistPath();
if(xmlpath.empty()) {
return;
}
LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\" before writing...";
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(xmlpath.c_str());
if(!result) {
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description();
return;
}
pugi::xml_node root = doc.child(GameData::xmlTagGameList.c_str());
if(!root) {
LOG(LogError) << "Could not find <" << GameData::xmlTagGameList << "> node in gamelist \"" << xmlpath << "\"!";
return;
}
//now we have all the information from the XML. now iterate through all our games and add information from there
FolderData * rootFolder = system->getRootFolder();
if (rootFolder != nullptr) {
//get only files, no folders
std::vector<FileData*> files = rootFolder->getFilesRecursive(true);
//iterate through all files, checking if they're already in the XML
std::vector<FileData*>::const_iterator fit = files.cbegin();
while(fit != files.cend()) {
//try to cast to gamedata
const GameData * game = dynamic_cast<const GameData*>(*fit);
if (game != nullptr) {
//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())) {
//get path from game node
pugi::xml_node pathNode = gameNode.child(GameData::xmlTagPath.c_str());
if(!pathNode)
{
LOG(LogError) << "<" << GameData::xmlTagGame << "> node contains no <" << GameData::xmlTagPath << "> child!";
continue;
}
//check path
if (pathNode.text().get() == game->getPath()) {
//found the game. remove it. it will be added again later with updated values
root.remove_child(gameNode);
//break node search loop
break;
}
}
//either the game content was removed, because it needs to be updated,
//or didn't exist in the first place, so just add it
addGameDataNode(root, game);
}
++fit;
}
//now write the file
if (!doc.save_file(xmlpath.c_str())) {
LOG(LogError) << "Error saving XML file \"" << xmlpath << "\"!";
}
}
else {
LOG(LogError) << "Found no root folder for system \"" << system->getName() << "\"!";
}
}

View file

@ -7,4 +7,7 @@ class SystemData;
//Loads gamelist.xml data into a SystemData.
void parseGamelist(SystemData* system);
//Writes changes to SystemData back to a previously loaded gamelist.xml.
void updateGamelist(SystemData* system);
#endif