Commit 3a3471cf authored by Aloshi's avatar Aloshi

Combined FolderData and GameData into one class, FileData.

You don't need to dynamic_cast everywhere to check things anymore.
Folders can have metadata now (currently not set up).
Metadata is now a public member variable instead of a function that
returns a pointer to make actually using const possible.
parent 5c657475
......@@ -138,8 +138,7 @@ set(ES_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/AudioManager.h
${CMAKE_CURRENT_SOURCE_DIR}/src/EmulationStation.h
${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.h
${CMAKE_CURRENT_SOURCE_DIR}/src/FolderData.h
${CMAKE_CURRENT_SOURCE_DIR}/src/GameData.h
${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.h
${CMAKE_CURRENT_SOURCE_DIR}/src/GuiComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/HttpReq.h
${CMAKE_CURRENT_SOURCE_DIR}/src/ImageIO.h
......@@ -198,8 +197,8 @@ set(ES_HEADERS
)
set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/AudioManager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/FolderData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/GameData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/GuiComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/HttpReq.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/ImageIO.cpp
......
#include "FileData.h"
#include <boost/regex/v4/regex.hpp>
namespace fs = boost::filesystem;
std::string getCleanFileName(const fs::path& path)
{
return regex_replace(path.stem().generic_string(), boost::regex("\\((.*)\\)|\\[(.*)\\]"), "");
}
FileData::FileData(FileType type, const fs::path& path)
: mType(type), mPath(path), mParent(NULL), metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA) // metadata is REALLY set in the constructor!
{
// metadata needs at least a name field (since that's what getName() will return)
if(metadata.get("name").empty())
metadata.set("name", getCleanFileName(mPath));
}
FileData::~FileData()
{
if(mParent)
mParent->removeChild(this);
}
const std::string& FileData::getThumbnailPath() const
{
if(!metadata.get("thumbnail").empty())
return metadata.get("thumbnail");
else
return metadata.get("image");
}
std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask) const
{
std::vector<FileData*> out;
for(auto it = mChildren.begin(); it != mChildren.end(); it++)
{
if((*it)->getType() & typeMask)
out.push_back(*it);
if((*it)->getChildren().size() > 0)
{
std::vector<FileData*> subchildren = (*it)->getFilesRecursive(typeMask);
out.insert(out.end(), subchildren.cbegin(), subchildren.cend());
}
}
return out;
}
void FileData::addChild(FileData* file)
{
assert(mType == FOLDER);
assert(file->getParent() == NULL);
mChildren.push_back(file);
file->mParent = this;
}
void FileData::removeChild(FileData* file)
{
assert(mType == FOLDER);
assert(file->getParent() == this);
for(auto it = mChildren.begin(); it != mChildren.end(); it++)
{
if(*it == file)
{
mChildren.erase(it);
return;
}
}
// File somehow wasn't in our children.
assert(false);
}
void FileData::sort(ComparisonFunction& comparator, bool ascending)
{
std::sort(mChildren.begin(), mChildren.end(), comparator);
for(auto it = mChildren.begin(); it != mChildren.end(); it++)
{
if((*it)->getChildren().size() > 0)
(*it)->sort(comparator, ascending);
}
if(!ascending)
std::reverse(mChildren.begin(), mChildren.end());
}
void FileData::sort(const SortType& type)
{
sort(*type.comparisonFunction, type.ascending);
}
#ifndef _FILEDATA_H_
#define _FILEDATA_H_
#pragma once
#include <vector>
#include <string>
#include <boost/filesystem.hpp>
#include "MetaData.h"
//This is a really basic class that the GameData and FolderData subclass from.
//This lets us keep everything in one vector and not have to differentiate between files and folders when we just want to check the name, etc.
enum FileType
{
GAME = 1, // Cannot have children.
FOLDER = 2
};
// Used for loading/saving gamelist.xml.
const char* fileTypeToString(FileType type);
FileType stringToFileType(const char* str);
std::string getCleanFileName(const boost::filesystem::path& path);
// A tree node that holds information for a file.
class FileData
{
public:
virtual ~FileData() { };
virtual bool isFolder() const = 0;
virtual const std::string& getName() const = 0;
virtual const std::string& getPath() const = 0;
};
FileData(FileType type, const boost::filesystem::path& path);
virtual ~FileData();
inline const std::string& getName() const { return metadata.get("name"); }
inline FileType getType() const { return mType; }
inline const boost::filesystem::path& getPath() const { return mPath; }
inline FileData* getParent() const { return mParent; }
inline const std::vector<FileData*>& getChildren() const { return mChildren; }
virtual const std::string& getThumbnailPath() const;
std::vector<FileData*> getFilesRecursive(unsigned int typeMask) const;
#endif
void addChild(FileData* file); // Error if mType != FOLDER
void removeChild(FileData* file); //Error if mType != FOLDER
typedef bool ComparisonFunction(const FileData* a, const FileData* b);
struct SortType
{
ComparisonFunction* comparisonFunction;
bool ascending;
std::string description;
SortType(ComparisonFunction* sortFunction, bool sortAscending, const std::string & sortDescription)
: comparisonFunction(sortFunction), ascending(sortAscending), description(sortDescription) {}
};
void sort(ComparisonFunction& comparator, bool ascending = true);
void sort(const SortType& type);
MetaDataList metadata;
private:
FileType mType;
boost::filesystem::path mPath;
FileData* mParent;
std::vector<FileData*> mChildren;
};
#include "FileSorts.h"
namespace FileSorts
{
const FileData::SortType typesArr[] = {
FileData::SortType(&compareFileName, true, "file name, ascending")
};
const std::vector<FileData::SortType> SortTypes(typesArr, typesArr + sizeof(typesArr)/sizeof(typesArr[0]));
//returns if file1 should come before file2
bool compareFileName(const FileData* file1, const FileData* file2)
{
std::string name1 = file1->getName();
std::string name2 = file2->getName();
//min of name1/name2 .length()s
unsigned int count = name1.length() > name2.length() ? name2.length() : name1.length();
for(unsigned int i = 0; i < count; i++)
{
if(toupper(name1[i]) != toupper(name2[i]))
{
return toupper(name1[i]) < toupper(name2[i]);
}
}
return name1.length() < name2.length();
}
bool compareRating(const FileData* file1, const FileData* file2)
{
//only games have rating metadata
if(file1->metadata.getType() == GAME_METADATA && file2->metadata.getType() == GAME_METADATA)
{
return file1->metadata.getFloat("rating") < file2->metadata.getFloat("rating");
}
return false;
}
bool compareTimesPlayed(const FileData* file1, const FileData* file2)
{
//only games have playcount metadata
if(file1->metadata.getType() == GAME_METADATA && file2->metadata.getType() == GAME_METADATA)
{
return (file1)->metadata.getInt("playcount") < (file2)->metadata.getInt("playcount");
}
return false;
}
bool compareLastPlayed(const FileData* file1, const FileData* file2)
{
//only games have lastplayed metadata
if(file1->metadata.getType() == GAME_METADATA && file2->metadata.getType() == GAME_METADATA)
{
return (file1)->metadata.getTime("lastplayed") < (file2)->metadata.getTime("lastplayed");
}
return false;
}
};
#pragma once
#include <vector>
#include "FileData.h"
namespace FileSorts
{
bool compareFileName(const FileData* file1, const FileData* file2);
bool compareRating(const FileData* file1, const FileData* file2);
bool compareTimesPlayed(const FileData* file1, const FileData* fil2);
bool compareLastPlayed(const FileData* file1, const FileData* file2);
extern const std::vector<FileData::SortType> SortTypes;
};
#include "FolderData.h"
#include "SystemData.h"
#include "GameData.h"
#include <algorithm>
#include <iostream>
std::map<FolderData::ComparisonFunction*, std::string> FolderData::sortStateNameMap;
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(); }
FolderData::FolderData(SystemData* system, std::string path, std::string name)
: mSystem(system), mPath(path), mName(name)
{
//first created folder data initializes the list
if (sortStateNameMap.empty()) {
sortStateNameMap[compareFileName] = "file name";
sortStateNameMap[compareRating] = "rating";
sortStateNameMap[compareTimesPlayed] = "times played";
sortStateNameMap[compareLastPlayed] = "last time played";
}
}
FolderData::~FolderData()
{
for(unsigned int i = 0; i < mFileVector.size(); i++)
{
delete mFileVector.at(i);
}
mFileVector.clear();
}
void FolderData::pushFileData(FileData* file)
{
mFileVector.push_back(file);
}
//sort this folder and any subfolders
void FolderData::sort(ComparisonFunction & comparisonFunction, bool ascending)
{
std::sort(mFileVector.begin(), mFileVector.end(), comparisonFunction);
for(unsigned int i = 0; i < mFileVector.size(); i++)
{
if(mFileVector.at(i)->isFolder())
((FolderData*)mFileVector.at(i))->sort(comparisonFunction, ascending);
}
if (!ascending) {
std::reverse(mFileVector.begin(), mFileVector.end());
}
}
//returns if file1 should come before file2
bool FolderData::compareFileName(const FileData* file1, const FileData* file2)
{
std::string name1 = file1->getName();
std::string name2 = file2->getName();
//min of name1/name2 .length()s
unsigned int count = name1.length() > name2.length() ? name2.length() : name1.length();
for(unsigned int i = 0; i < count; i++)
{
if(toupper(name1[i]) != toupper(name2[i]))
{
return toupper(name1[i]) < toupper(name2[i]);
}
}
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 const_cast<GameData*>(game1)->metadata()->getFloat("rating") < const_cast<GameData*>(game2)->metadata()->getFloat("rating");
}
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 const_cast<GameData*>(game1)->metadata()->getInt("playcount") < const_cast<GameData*>(game2)->metadata()->getInt("playcount");
}
return false;
}
bool FolderData::compareLastPlayed(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 const_cast<GameData*>(game1)->metadata()->getTime("lastplayed") < const_cast<GameData*>(game2)->metadata()->getTime("lastplayed");
}
return false;
}
std::string FolderData::getSortStateName(ComparisonFunction & comparisonFunction, bool ascending)
{
std::string temp = sortStateNameMap[comparisonFunction];
if (ascending) {
temp.append(" (ascending)");
}
else {
temp.append(" (descending)");
}
return temp;
}
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;
}
void FolderData::removeFileRecursive(FileData* f)
{
auto iter = mFileVector.begin();
while(iter != mFileVector.end())
{
if(*iter == f)
{
delete *iter;
iter = mFileVector.erase(iter);
}else{
FolderData* folder = dynamic_cast<FolderData*>(*iter);
if(folder)
{
folder->removeFileRecursive(f);
}
iter++;
}
}
}
#ifndef _FOLDER_H_
#define _FOLDER_H_
#include <map>
#include <vector>
#include "FileData.h"
class SystemData;
//This class lets us hold a vector of FileDatas within under a common name.
class FolderData : public FileData
{
public:
typedef bool ComparisonFunction(const FileData* a, const FileData* b);
struct SortState
{
ComparisonFunction & comparisonFunction;
bool ascending;
std::string description;
SortState(ComparisonFunction & sortFunction, bool sortAscending, const std::string & sortDescription) : comparisonFunction(sortFunction), ascending(sortAscending), description(sortDescription) {}
};
private:
static std::map<ComparisonFunction*, std::string> sortStateNameMap;
public:
FolderData(SystemData* system, std::string path, std::string name);
~FolderData();
bool isFolder() const;
const std::string & getName() const;
const std::string & getPath() const;
unsigned int getFileCount();
FileData* getFile(unsigned int i) const;
std::vector<FileData*> getFiles(bool onlyFiles = false) const;
std::vector<FileData*> getFilesRecursive(bool onlyFiles = false) const;
void removeFileRecursive(FileData* file);
void pushFileData(FileData* file);
void sort(ComparisonFunction & comparisonFunction = compareFileName, bool ascending = true);
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);
static bool compareLastPlayed(const FileData* file1, const FileData* file2);
static std::string getSortStateName(ComparisonFunction & comparisonFunction = compareFileName, bool ascending = true);
private:
SystemData* mSystem;
std::string mPath;
std::string mName;
std::vector<FileData*> mFileVector;
};
#endif
#include "GameData.h"
#include <boost/filesystem.hpp>
#include <boost/regex/v4/regex.hpp>
#include <iostream>
#include <ctime>
#include <sstream>
GameData::GameData(const std::string& path, const MetaDataList& metadata)
: mPath(path), mBaseName(boost::filesystem::path(path).stem().string()), mMetaData(metadata)
{
if(mMetaData.get("name").empty())
mMetaData.set("name", mBaseName);
}
bool GameData::isFolder() const
{
return false;
}
const std::string& GameData::getName() const
{
return mMetaData.get("name");
}
const std::string& GameData::getPath() const
{
return mPath;
}
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 = getPath();
const char* invalidChars = " '\"\\!$^&*(){}[]?;<>";
for(unsigned int i = 0; i < path.length(); i++)
{
char c;
unsigned int charNum = 0;
do {
c = invalidChars[charNum];
if(path[i] == c)
{
path.insert(i, "\\");
i++;
break;
}
charNum++;
} while(c != '\0');
}
return path;
}
//returns the boost::filesystem stem of our path - e.g. for "/foo/bar.rom" returns "bar"
std::string GameData::getBaseName() const
{
return mBaseName;
}
std::string GameData::getCleanName() const
{
return regex_replace(mBaseName, boost::regex("\\((.*)\\)|\\[(.*)\\]"), "");
}
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()
{
boost::posix_time::ptime time = boost::posix_time::second_clock::universal_time();
metadata()->setTime("lastplayed", time);
}
MetaDataList* GameData::metadata()
{
return &mMetaData;
}
#ifndef _GAMEDATA_H_
#define _GAMEDATA_H_
#include <string>
#include "FileData.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.
class GameData : public FileData
{
public:
GameData(const std::string& path, const MetaDataList& metadata);
const std::string& getName() const override;
const std::string& getPath() const override;
void incTimesPlayed();
void lastPlayedNow();
std::string getBashPath() const;
std::string getBaseName() const;
std::string getCleanName() const;
bool isFolder() const override;
MetaDataList* metadata();
private:
const std::string mPath;
const std::string mBaseName;
MetaDataList mMetaData;
};
#endif
......@@ -141,7 +141,7 @@ int run_scraper_cmdline()
std::shared_ptr<Scraper> scraper = Settings::getInstance()->getScraper();
for(auto sysIt = systems.begin(); sysIt != systems.end(); sysIt++)
{
std::vector<FileData*> files = (*sysIt)->getRootFolder()->getFilesRecursive(true);
std::vector<FileData*> files = (*sysIt)->getRootFolder()->getFilesRecursive(GAME);
ScraperSearchParams params;
params.system = (*sysIt);
......@@ -149,15 +149,15 @@ int run_scraper_cmdline()
for(auto gameIt = files.begin(); gameIt != files.end(); gameIt++)
{
params.nameOverride = "";
params.game = (GameData*)(*gameIt);
params.game = *gameIt;
//print original search term
out << params.game->getCleanName() << "...\n";
out << getCleanFileName(params.game->getPath()) << "...\n";
//need to take into account filter_choice
if(filter_choice == FILTER_MISSING_IMAGES)
{
if(!params.game->metadata()->get("image").empty()) //maybe should also check if the image file exists/is a URL
if(!params.game->metadata.get("image").empty()) //maybe should also check if the image file exists/is a URL
{
out << " Skipping, metadata \"image\" entry is not empty.\n";
continue;
......@@ -212,7 +212,7 @@ int run_scraper_cmdline()
if(choice >= 0 && choice < (int)mdls.size())
{
*params.game->metadata() = mdls.at(choice);
params.game->metadata = mdls.at(choice);
break;
}else{
out << "Invalid choice.\n";
......@@ -223,7 +223,7 @@ int run_scraper_cmdline()
//automatic mode
//always choose the first choice
out << " name -> " << mdls.at(0).get("name") << "\n";
*params.game->metadata() = mdls.at(0);
params.game->metadata = mdls.at(0);
break;
}
......@@ -238,32 +238,32 @@ int run_scraper_cmdline()
for(auto sysIt = systems.begin(); sysIt != systems.end(); sysIt++)
{
std::vector<FileData*> files = (*sysIt)->getRootFolder()->getFilesRecursive(true);
std::vector<FileData*> files = (*sysIt)->getRootFolder()->getFilesRecursive(GAME);
for(auto gameIt = files.begin(); gameIt != files.end(); gameIt++)
{
GameData* game = (GameData*)(*gameIt);
const std::vector<MetaDataDecl>& mdd = game->metadata()->getMDD();
FileData* game = *gameIt;
const std::vector<MetaDataDecl>& mdd = game->metadata.getMDD();
for(auto i = mdd.begin(); i != mdd.end(); i++)
{
std::string key = i->key;
std::string url = game->metadata()->get(key);
std::string url = game->metadata.get(key);
if(i->type == MD_IMAGE_PATH && HttpReq::isUrl(url))
{
std::string urlShort = url.substr(0, url.length() > 35 ? 35 : url.length());
if(url.length() != urlShort.length()) urlShort += "...";