Commit 5c4ef0f0 authored by Bkg2k's avatar Bkg2k Committed by OyyoDams

Resolve "Improve/Optimize the way ES store & manage games & folders"

parent 6cef69c3
......@@ -82,8 +82,8 @@ if(CMAKE_COMPILER_IS_GNUCXX)
#set up compiler flags for GCC
if (CMAKE_BUILD_TYPE MATCHES Debug)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-attributes -Wswitch-enum -Wswitch -O3 -g -Wall -Wextra -ffunction-sections -fdata-sections -Wl,--gc-sections") #support C++11 for std::, optimize
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wswitch-enum -Wswitch -O3 -g -Wall -Wextra -ffunction-sections -fdata-sections -Wl,--gc-sections")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-attributes -Wswitch-enum -Wswitch -O0 -g -Wall -Wextra -ffunction-sections -fdata-sections -Wl,--gc-sections") #support C++11 for std::, optimize
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wswitch-enum -Wswitch -O0 -g -Wall -Wextra -ffunction-sections -fdata-sections -Wl,--gc-sections")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-attributes -O3 -Wall -Wextra -ffunction-sections -fdata-sections -Wl,--gc-sections") #support C++11 for std::, optimize
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O3 -Wall -Wextra -ffunction-sections -fdata-sections -Wl,--gc-sections") #-s = strip binary
......
project("emulationstation")
set(ES_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/MameNameMap.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/RootFolderData.h
${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.h
${CMAKE_CURRENT_SOURCE_DIR}/src/MetadataDescriptor.h
${CMAKE_CURRENT_SOURCE_DIR}/src/MetadataFieldDescriptor.h
......@@ -62,6 +65,8 @@ set(ES_HEADERS
set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/FolderData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/RootFolderData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/MameNameMap.cpp
......
......@@ -4,7 +4,6 @@
#include "SystemData.h"
#include "Log.h"
#include "views/ViewController.h"
#include <boost/asio.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
using boost::asio::ip::udp;
......@@ -12,13 +11,20 @@ using boost::asio::ip::udp;
const int max_length = 2048;
CommandThread::CommandThread(Window* window) : mWindow(window) {
mRunning = true;
CommandThread::CommandThread(Window* window)
: mWindow(window),
mRunning(true),
mIOService(),
mSocket(mIOService, udp::endpoint(udp::v4(), 1337))
{
mThreadHandle = new boost::thread(boost::bind(&CommandThread::run, this));
}
CommandThread::~CommandThread() {
mThreadHandle->join();
CommandThread::~CommandThread()
{
mRunning = false;
mSocket.close();
mThreadHandle->interrupt();
}
void CommandThread::run() {
......@@ -26,16 +32,14 @@ void CommandThread::run() {
LOG(LogInfo) << "CommandThread started";
boost::asio::io_service io_service;
udp::socket sock(io_service, udp::endpoint(udp::v4(), 1337));
char buf[max_length];
while (mRunning) {
while (mRunning)
{
udp::endpoint sender_endpoint;
size_t length = sock.receive_from(boost::asio::buffer(buf, max_length), sender_endpoint);
if (length <= 0) {
continue;
}
boost::system::error_code ec;
size_t length = mSocket.receive_from(boost::asio::buffer(buf, max_length), sender_endpoint, 0, ec);
if (ec != 0) break;
if (length <= 0) continue;
buf[length + 1] = '\0';
std::string str(buf, length);
......@@ -57,48 +61,27 @@ void CommandThread::run() {
}
// The system is not a valid one
if (system == NULL) {
if (system == NULL)
{
LOG(LogError) << "Invalid system on network command: " << tokens[1];
continue;
}
std::vector<FileData*> games = system->getRootFolder()->getChildren();
FileData* result = findRecursive(games, tokens[2]);
if (result != NULL) {
FileData* result = system->getRootFolder()->LookupGame(tokens[2], FileData::SearchAttributes::ByNameWithExt);
if (result != NULL)
{
LOG(LogInfo) << "Starting game " << tokens[2] << " for system " << tokens[1];
runGame(result);
} else {
LOG(LogError) << "Couldn't find game " << tokens[2] << " for system " << tokens[1];
}
}
}
FileData* CommandThread::findRecursive(const std::vector<FileData*> gameFolder, const std::string& gameName, const std::string& relativePath) {
// Recursively look for the game in subfolders too
for (auto game = gameFolder.begin(); game != gameFolder.end(); ++game) {
std::string gameAndPath;
if (relativePath.empty()) {
gameAndPath = (*game)->getPath().filename().c_str();
} else {
gameAndPath = relativePath + '/' + (*game)->getPath().filename().c_str();
}
LOG(LogInfo) << "Checking game " << gameName << " in path: " << gameAndPath;
if ((*game)->getType() == FOLDER) {
FileData* foundGame = findRecursive((*game)->getChildren(), gameName, gameAndPath);
if ( foundGame != NULL ) {
LOG(LogInfo) << "Game found !";
return foundGame;
}
}
if ((*game)->getType() == GAME and gameAndPath == gameName) {
LOG(LogInfo) << "Game found !";
return *game;
else
{
LOG(LogError) << "Couldn't find game " << tokens[2] << " for system " << tokens[1];
}
}
return NULL;
}
void CommandThread::runGame (FileData* game) {
void CommandThread::runGame (FileData* game)
{
ViewController *view = ViewController::get();
Eigen::Vector3f target(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f, 0);
mWindow->doWake();
......
#include <Window.h>
#include "FileData.h"
#include <boost/thread/thread.hpp>
#include <boost/asio.hpp>
class CommandThread {
class CommandThread
{
public:
CommandThread(Window* window);
explicit CommandThread(Window* window);
~CommandThread();
void run();
FileData* findRecursive(const std::vector<FileData*> gameFolder, const std::string& gameName, const std::string& relativePath = "");
void runGame (FileData* game);
private:
Window* mWindow;
bool mRunning;
boost::asio::io_service mIOService;
boost::asio::ip::udp::socket mSocket;
boost::thread* mThreadHandle;
};
......@@ -3,279 +3,29 @@
#include "SystemData.h"
#include "Log.h"
extern std::vector<std::string> mameBioses;
extern std::vector<std::string> mameDevices;
#include <string.h>
namespace fs = boost::filesystem;
std::string removeParenthesis(const std::string& str)
{
// remove anything in parenthesis or brackets
// should be roughly equivalent to the regex replace "\((.*)\)|\[(.*)\]" with ""
// I would love to just use regex, but it's not worth pulling in another boost lib for one function that is used once
std::string ret = str;
size_t start, end;
static const int NUM_TO_REPLACE = 2;
static const char toReplace[NUM_TO_REPLACE*2] = { '(', ')', '[', ']' };
bool done = false;
while(!done)
{
done = true;
for(int i = 0; i < NUM_TO_REPLACE; i++)
{
end = ret.find_first_of(toReplace[i*2+1]);
start = ret.find_last_of(toReplace[i*2], end);
if(start != std::string::npos && end != std::string::npos)
{
ret.erase(start, end - start + 1);
done = false;
}
}
}
// also strip whitespace
end = ret.find_last_not_of(' ');
if(end != std::string::npos)
end++;
ret = ret.substr(0, end);
return ret;
}
FileData::FileData(FileType type, const fs::path& path, SystemData* system)
: mType(type),
mPath(path),
mSystem(system),
mParent(NULL),
metadata(getCleanName()) // TODO: Move clean name into metadata
FileData::FileData(FileData::FileType type, const fs::path& path, SystemData* system)
: mSystem(system),
mParent(NULL),
mType(type),
mPath(path),
mMetadata(getCleanName()) // TODO: Move clean name into metadata
{
}
FileData::~FileData()
FileData::FileData(const fs::path& path, SystemData* system) : FileData(FileData::FileType::Game, path, system)
{
if(mParent)
mParent->removeChild(this);
clear();
}
std::string FileData::getCleanName() const
{
std::string stem = mPath.stem().generic_string();
if(mSystem && (mSystem->hasPlatformId(PlatformIds::ARCADE) || mSystem->hasPlatformId(PlatformIds::NEOGEO)))
stem = PlatformIds::getCleanMameName(stem.c_str());
return stem;
}
const std::string& FileData::getThumbnailPath() const
{
return (!metadata.Thumbnail().empty()) ? metadata.Thumbnail() : metadata.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;
}
std::vector<FileData*> FileData::getFavoritesRecursive(unsigned int typeMask) const
{
std::vector<FileData*> out;
std::vector<FileData*> files = getFilesRecursive(typeMask);
for (auto it = files.begin(); it != files.end(); it++)
{
if ((*it)->metadata.Favorite())
{
out.push_back(*it);
}
}
return out;
}
std::vector<FileData*> FileData::getHiddenRecursive(unsigned int typeMask) const
{
std::vector<FileData*> out;
std::vector<FileData*> files = getFilesRecursive(typeMask);
for (auto it = files.begin(); it != files.end(); it++)
{
if ((*it)->metadata.Hidden())
{
out.push_back(*it);
}
}
return out;
}
void FileData::addChild(FileData* file)
{
assert(mType == FOLDER);
assert(file->getParent() == NULL);
const std::string key = file->getPath().filename().string();
if (mChildrenByFilename.find(key) == mChildrenByFilename.end())
{
mChildrenByFilename[key] = file;
mChildren.push_back(file);
file->mParent = this;
}
if (mSystem != nullptr)
if ((mSystem->hasPlatformId(PlatformIds::ARCADE) || mSystem->hasPlatformId(PlatformIds::NEOGEO)))
stem = PlatformIds::getCleanMameName(stem.c_str());
}
void FileData::addAlreadyExistingChild(FileData* file)
{
assert(mType == FOLDER);
mChildren.push_back(file);
}
void FileData::removeAlreadyExistingChild(FileData* file)
{
assert(mType == FOLDER);
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::removeChild(FileData* file)
{
assert(mType == FOLDER);
assert(file->getParent() == this);
mChildrenByFilename.erase(file->getPath().filename().string());
for(auto it = mChildren.begin(); it != mChildren.end(); it++)
{
if(*it == file)
{
mChildren.erase(it);
return;
}
}
}
void FileData::clear()
{
mChildren.clear();
}
void FileData::populateRecursiveFolder(FileData* folder, const std::vector<std::string>& searchExtensions, SystemData* systemData)
{
const fs::path& folderPath = folder->getPath();
if(!fs::is_directory(folderPath))
{
LOG(LogWarning) << "Error - folder with path \"" << folderPath << "\" is not a directory!";
return;
}
const std::string folderStr = folderPath.generic_string();
//make sure that this isn't a symlink to a thing we already have
if(fs::is_symlink(folderPath))
{
//if this symlink resolves to somewhere that's at the beginning of our path, it's gonna recurse
if(folderStr.find(fs::canonical(folderPath).generic_string()) == 0)
{
LOG(LogWarning) << "Skipping infinitely recursive symlink \"" << folderPath << "\"";
return;
}
}
fs::path filePath;
std::string extension;
bool isGame;
for(fs::directory_iterator end, dir(folderPath); dir != end; ++dir)
{
filePath = (*dir).path();
if(filePath.stem().empty())
continue;
//this is a little complicated because we allow a list of extensions to be defined (delimited with a space)
//we first get the extension of the file itself:
extension = filePath.extension().string();
//fyi, folders *can* also match the extension and be added as games - this is mostly just to support higan
//see issue #75: https://github.com/Aloshi/EmulationStation/issues/75
isGame = false;
if((searchExtensions.empty() && !fs::is_directory(filePath)) || (std::find(searchExtensions.begin(), searchExtensions.end(), extension) != searchExtensions.end()
&& filePath.filename().string().compare(0, 1, ".") != 0)){
FileData* newGame = new FileData(GAME, filePath.generic_string(), systemData);
if (systemData->hasPlatformId(PlatformIds::ARCADE) || systemData->hasPlatformId(PlatformIds::NEOGEO)) {
if (std::find(mameBioses.begin(), mameBioses.end(), filePath.stem().generic_string()) != mameBioses.end()
|| std::find(mameDevices.begin(), mameDevices.end(), filePath.stem().generic_string()) != mameDevices.end()) {
continue;
}
}
folder->addChild(newGame);
isGame = true;
}
//add directories that also do not match an extension as folders
if(!isGame && fs::is_directory(filePath))
{
FileData* newFolder = new FileData(FOLDER, filePath.generic_string(), systemData);
populateRecursiveFolder(newFolder, searchExtensions, systemData);
//ignore folders that do not contain games
if(newFolder->getChildrenByFilename().size() == 0)
delete newFolder;
else
folder->addChild(newFolder);
}
}
}
std::vector<FileData *> FileData::getDisplayableRecursive(unsigned int typeMask) const
{
std::vector<FileData *> out;
std::vector<FileData *> files = getFilesRecursive(typeMask);
for (auto it = files.begin(); it != files.end(); it++)
{
if (!(*it)->metadata.Hidden())
{
out.push_back(*it);
}
}
return out;
}
bool FileData::isSingleGameFolder() const
{
assert(mType == FOLDER);
return (mChildren.size() == 1) && (mChildren.at(0)->getType() == GAME);
return stem;
}
\ No newline at end of file
......@@ -7,87 +7,141 @@
#include <components/IList.h>
#include "MetadataDescriptor.h"
// Bitflag operator for use on strongly-typed enums - Pass enum type and underlying cardinal type
#define DEFINE_BITFLAG_ENUM(enumtype, ut) \
inline enumtype operator | (enumtype lhs, enumtype rhs) { return (enumtype)((ut)lhs | (ut)rhs); } \
inline enumtype operator & (enumtype lhs, enumtype rhs) { return (enumtype)((ut)lhs & (ut)rhs); } \
inline enumtype operator ^ (enumtype lhs, enumtype rhs) { return (enumtype)((ut)lhs ^ (ut)rhs); } \
inline enumtype operator ~ (enumtype lhs) { return (enumtype)(~(ut)lhs); } \
inline enumtype& operator |= (enumtype& lhs, enumtype rhs) { lhs = (enumtype)((ut)lhs | (ut)rhs); return lhs; } \
inline enumtype& operator &= (enumtype& lhs, enumtype rhs) { lhs = (enumtype)((ut)lhs & (ut)rhs); return lhs; } \
inline enumtype& operator ^= (enumtype& lhs, enumtype rhs) { lhs = (enumtype)((ut)lhs ^ (ut)rhs); return lhs; } \
inline bool operator == (enumtype lhs, ut rhs) { return (ut)lhs == rhs; } \
inline bool operator != (enumtype lhs, ut rhs) { return (ut)lhs != rhs; }
// Forward declarations
class SystemData;
class FolderData;
enum FileType
// A tree node that holds information for a file.
class FileData
{
GAME = 1, // Cannot have children.
FOLDER = 2
};
public:
typedef std::unordered_map<std::string, FileData*> StringMap;
typedef std::vector<FileData*> List;
//! Item types
enum class FileType
{
Game, //!< Launchable game
Folder, //!< Subfolder
};
//! Game filters
enum class Filter
{
None = 0, //!< Include nothing
Normal = 1, //!< Include normal files (not hidden, not favorite)
Favorite = 2, //!< Include favorites
Hidden = 4, //!< Include hidden
All = 7, //!< Include all
};
//! Search attribute enumeration
enum class SearchAttributes
{
ByName = 1, //!< Search by name, excluding extension
ByNameWithExt = 2, //!< Search by name, including extension
ByHash = 4, //!< Search by hash
All = 7, //!< All attributes
};
protected:
//! System the current item is attached to
SystemData* mSystem;
//! Parent folder
FolderData* mParent;
//! Item type - Const ensure mType cannot be modified after being set by the constructor, so that it's alays safe to use c-style cast for FolderData sub-class.
const FileType mType;
private:
//! Item path on the filesystem
boost::filesystem::path mPath;
//! Metadata
MetadataDescriptor mMetadata;
protected:
/*!
* Constructor for subclasses only
* @param type
* @param path Item path
* @param system Parent system
*/
FileData(FileType type, const boost::filesystem::path& path, SystemData* system);
// todo: should be moved somewhere else because, it does no longer deals with file list
enum FileChangeType
{
FILE_ADDED,
FILE_RUN,
FILE_METADATA_CHANGED,
FILE_REMOVED,
FILE_SORTED,
FILE_DISPLAY_UPDATED
};
public:
/*!
* Constructor
* @param path Item path on filesystem
* @param system system to attach to
*/
FileData(const boost::filesystem::path& path, SystemData* system);
// Used for loading/saving gamelist.xml.
const char* fileTypeToString(FileType type);
FileType stringToFileType(const char* str);
/*
* Getters
*/
// Remove (.*) and [.*] from str
std::string removeParenthesis(const std::string& str);
inline const std::string& getName() const { return mMetadata.Name(); }
inline const std::string getHash() const { return mMetadata.RomCrc32AsString(); }
inline FileType getType() const { return mType; }
inline const boost::filesystem::path& getPath() const { return mPath; }
inline FolderData* getParent() const { return mParent; }
inline SystemData* getSystem() const { return mSystem; }
// A tree node that holds information for a file.
class FileData
{
public:
FileData(FileType type, const boost::filesystem::path& path, SystemData* system);
virtual ~FileData();
inline const std::string& getName() const { return metadata.Name(); }
inline const std::string getHash() const { return std::move(metadata.RomCrc32AsString()); }
inline FileType getType() const { return mType; }
inline const boost::filesystem::path& getPath() const { return mPath; }
inline FileData* getParent() const { return mParent; }
inline const std::unordered_map<std::string, FileData*>& getChildrenByFilename() const { return mChildrenByFilename; }
inline const std::vector<FileData*>& getChildren() const { return mChildren; }
inline SystemData* getSystem() const { return mSystem; }
virtual const std::string& getThumbnailPath() const;
std::vector<FileData*> getFilesRecursive(unsigned int typeMask) const;
std::vector<FileData*> getFavoritesRecursive(unsigned int typeMask) const;
std::vector<FileData*> getHiddenRecursive(unsigned int typeMask) const;
std::vector<FileData*> getDisplayableRecursive(unsigned int typeMask) const;
void addChild(FileData* file); // Error if mType != FOLDER
void removeChild(FileData* file); //Error if mType != FOLDER
void addAlreadyExistingChild(FileData *file);
void removeAlreadyExistingChild(FileData *file);
void clear();
// Returns our best guess at the "real" name for this file (will strip parenthesis and attempt to perform MAME name translation)
std::string getCleanName() const;
bool isSingleGameFolder() const;
static void populateRecursiveFolder(FileData* folder, const std::vector<std::string>& searchExtensions = std::vector<std::string>(), SystemData* systemData = nullptr);
/*!
* const Metadata accessor for Read operations
* @return const Metadata object
*/
const MetadataDescriptor& Metadata() const { return metadata; }
/*!
/*
* Booleans
*/
inline bool isGame() const { return mType == FileType::Game; }
inline bool isFolder() const { return mType == FileType::Folder; }
/*
* Setters
*/
inline void setParent(FolderData* parent) { mParent = parent; }
/*!
* Get Thumbnail path if there is one, or Image path.
* @return file path (may be empty)
*/
inline const std::string& getThumbnailOrImagePath() const { return mMetadata.Thumbnail().empty() ? mMetadata.Image() : mMetadata.Thumbnail(); }
/*!
* Return true if at least one image is available (thumbnail or regular image)
* @return Boolean result
*/
inline bool hasThumbnailOrImage() const { return !(mMetadata.Thumbnail().empty() | mMetadata.Image().empty()); }
/*!
* const Metadata accessor for Read operations
* @return const Metadata object
*/
const MetadataDescriptor& Metadata() const { return mMetadata; }
/*!
* Metadata accessor for Write operations only
* @return Writable Metadata object
*/
MetadataDescriptor& Metadata() { return metadata; }
MetadataDescriptor& Metadata() { return mMetadata; }
private:
FileType mType;
boost::filesystem::path mPath;
SystemData* mSystem;
FileData* mParent;
std::unordered_map<std::string,FileData*> mChildrenByFilename;
std::vector<FileData*> mChildren;
MetadataDescriptor metadata;
/*!
* Get smart default name, when available, depending of the file/folder name
* Mainly used to get smart naming from arcade zipped roms
* @return Smart name of the current item, or file/folder name
*/
std::string getCleanName() const;
};
DEFINE_BITFLAG_ENUM(FileData::Filter, int)
DEFINE_BITFLAG_ENUM(FileData::SearchAttributes, int)
\ No newline at end of file
......@@ -4,112 +4,122 @@
namespace FileSorts
{
std::vector<SortType> SortTypes;
void init() {
SortTypes.push_back(SortType(&compareFileName, true, "\uF15d " + _("FILENAME")));
SortTypes.push_back(SortType(&compareFileName, false, "\uF15e " + _("FILENAME")));
SortTypes.push_back(SortType(&compareRating, true, "\uF165 " + _("RATING")));
SortTypes.push_back(SortType(&compareRating, false, "\uF164 " + _("RATING")));
SortTypes.push_back(SortType(&compareTimesPlayed, true, "\uF160 " + _("TIMES PLAYED")));
SortTypes.push_back(SortType(&compareTimesPlayed, false, "\uF161 " + _("TIMES PLAYED")));
SortTypes.push_back(SortType(&compareLastPlayed, true, "\uF160 " + _("LAST PLAYED")));
SortTypes.push_back(SortType(&compareLastPlayed, false, "\uF161 " + _("LAST PLAYED")));
SortTypes.push_back(SortType(&compareNumberPlayers, true, "\uF162 " + _("NUMBER OF PLAYERS")));
SortTypes.push_back(SortType(&compareNumberPlayers, false, "\uF163 " + _("NUMBER OF PLAYERS")));
SortTypes.push_back(SortType(&compareDevelopper, true, "\uF15d " + _("DEVELOPER")));
SortTypes.push_back(SortType(&compareDevelopper, false, "\uF15e " + _("DEVELOPER")));
SortTypes.push_back(SortType(&compareGenre, true, "\uF15d " + _("GENRE")));
SortTypes.push_back(SortType(&compareGenre, false, "\uF15e " + _("GENRE")));
void init()