mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-25 23:55:38 +00:00
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:
parent
171ca9a657
commit
556b9fa3fe
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
101
src/GameData.cpp
101
src/GameData.cpp
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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() << "\"!";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue