diff --git a/src/FileData.h b/src/FileData.h index a0c7abde8..ebb44b9b9 100644 --- a/src/FileData.h +++ b/src/FileData.h @@ -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 diff --git a/src/FolderData.cpp b/src/FolderData.cpp index 2bc937cca..5ea2cd8e5 100644 --- a/src/FolderData.cpp +++ b/src/FolderData.cpp @@ -1,13 +1,14 @@ #include "FolderData.h" #include "SystemData.h" +#include "GameData.h" #include #include -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(file1); + const GameData * game2 = dynamic_cast(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(file1); + const GameData * game2 = dynamic_cast(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 FolderData::getFiles(bool onlyFiles) const +{ + std::vector temp; + //now check if a child is a folder and get those children in turn + std::vector::const_iterator fdit = mFileVector.cbegin(); + while(fdit != mFileVector.cend()) { + //dynamically try to cast to FolderData type + FolderData * folder = dynamic_cast(*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 FolderData::getFilesRecursive(bool onlyFiles) const +{ + std::vector temp; + //now check if a child is a folder and get those children in turn + std::vector::const_iterator fdit = mFileVector.cbegin(); + while(fdit != mFileVector.cend()) { + //dynamically try to cast to FolderData type + FolderData * folder = dynamic_cast(*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 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; +} \ No newline at end of file diff --git a/src/FolderData.h b/src/FolderData.h index e453ac5e7..d91d69c64 100644 --- a/src/FolderData.h +++ b/src/FolderData.h @@ -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 getFiles(bool onlyFiles = false) const; + std::vector 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; diff --git a/src/GameData.cpp b/src/GameData.cpp index bb656be01..f72d9f9a4 100644 --- a/src/GameData.cpp +++ b/src/GameData.cpp @@ -2,23 +2,88 @@ #include #include -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; -} diff --git a/src/GameData.h b/src/GameData.h index ad0db2038..bd34be4ea 100644 --- a/src/GameData.h +++ b/src/GameData.h @@ -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 diff --git a/src/SystemData.cpp b/src/SystemData.cpp index 2e983e5e4..d77bfb024 100644 --- a/src/SystemData.cpp +++ b/src/SystemData.cpp @@ -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) diff --git a/src/XMLReader.cpp b/src/XMLReader.cpp index d1e19b8ae..928fbd8ff 100644 --- a/src/XMLReader.cpp +++ b/src/XMLReader.cpp @@ -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 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) << " node contains no 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 files = rootFolder->getFilesRecursive(true); + //iterate through all files, checking if they're already in the XML + std::vector::const_iterator fit = files.cbegin(); + while(fit != files.cend()) { + //try to cast to gamedata + const GameData * game = dynamic_cast(*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() << "\"!"; + } +} diff --git a/src/XMLReader.h b/src/XMLReader.h index 403c96fcf..ea3b4399b 100644 --- a/src/XMLReader.h +++ b/src/XMLReader.h @@ -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