Skip to content

Commit

Permalink
Changes to maintain and persist <folder/> information in a gamelist:
Browse files Browse the repository at this point in the history
1. folder elements are loaded when present in a gamelist and if they match the filesystem representation
2. missing optional folder elements are rendered empty in theme (and not "unknown")
3. As before non-existing folder elements are generated by ES, with mandatory elements <path/> and <name/>, but with this PR, whenever metadata is edited for this folder object (FileData class) it is persisted
4. Editiing metadata on existing <folder/> gets persisted too
5. UI metadata edit: layout fix that renders entered metadata not centered when returning from editing
6. UI metadata edit: format tooltip for date format in release date field
  • Loading branch information
Gemba committed Feb 7, 2024
1 parent 01de761 commit a113b22
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 64 deletions.
55 changes: 33 additions & 22 deletions es-app/src/Gamelist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,34 @@

FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType type)
{
// first, verify that path is within the system's root folder
FileData* root = system->getRootFolder();
bool contains = false;
std::string relative = Utils::FileSystem::removeCommonPath(path, root->getPath(), contains, true);
const std::string systemPath = root->getPath();

// first, verify that path is within the system's root folder
std::string relative = Utils::FileSystem::removeCommonPath(path, systemPath, contains, true);
if(!contains)
{
LOG(LogError) << "File path \"" << path << "\" is outside system path \"" << system->getStartPath() << "\"";
return NULL;
}

Utils::FileSystem::stringList pathList = Utils::FileSystem::getPathList(relative);

auto path_it = pathList.begin();
FileData* treeNode = root;
bool found = false;

// iterate over all subpaths below the provided path
while(path_it != pathList.end())
{
const std::unordered_map<std::string, FileData*>& children = treeNode->getChildrenByFilename();

std::string key = *path_it;
found = children.find(key) != children.cend();
std::string pathSegment = *path_it;
auto candidate = children.find(pathSegment);
found = candidate != children.cend();
if (found) {
treeNode = children.at(key);
treeNode = candidate->second;
}

// this is the end
Expand Down Expand Up @@ -65,12 +70,16 @@ FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType
// if type is a folder it's gonna be empty, so don't bother
if(type == FOLDER)
{
LOG(LogWarning) << "gameList: folder doesn't already exist, won't create";
std::string absFolder = Utils::FileSystem::getAbsolutePath(pathSegment, systemPath);
LOG(LogWarning) << "gameList: folder " << absFolder << " absent on fs, no FileData object created. Do remove leftover in gamelist.xml to remediate this warning.";
return NULL;
}

// create missing folder
FileData* folder = new FileData(FOLDER, Utils::FileSystem::getStem(treeNode->getPath()) + "/" + *path_it, system->getSystemEnvData(), system);
// create folder filedata object
std::string absPath = Utils::FileSystem::resolveRelativePath(treeNode->getPath() + "/" + pathSegment, systemPath, false, true);
FileData* folder = new FileData(FOLDER, absPath, system->getSystemEnvData(), system);
LOG(LogDebug) << "folder not found as FileData, adding: " << folder->getPath();

treeNode->addChild(folder);
treeNode = folder;
}
Expand Down Expand Up @@ -118,7 +127,8 @@ void parseGamelist(SystemData* system)
FileType type = typeList[i];
for(pugi::xml_node fileNode = root.child(tag); fileNode; fileNode = fileNode.next_sibling(tag))
{
const std::string path = Utils::FileSystem::resolveRelativePath(fileNode.child("path").text().get(), relativeTo, false, true);
std::string path = fileNode.child("path").text().get();
path = Utils::FileSystem::resolveRelativePath(path, relativeTo, false, true);

if(!trustGamelist && !Utils::FileSystem::exists(path))
{
Expand All @@ -127,7 +137,7 @@ void parseGamelist(SystemData* system)
}

// Check whether the file's extension is allowed in the system
if (std::find(allowedExtensions.cbegin(), allowedExtensions.cend(), Utils::FileSystem::getExtension(path)) == allowedExtensions.cend())
if (i == 0 /*game*/ && std::find(allowedExtensions.cbegin(), allowedExtensions.cend(), Utils::FileSystem::getExtension(path)) == allowedExtensions.cend())
{
LOG(LogDebug) << "file " << path << " found in gamelist, but has unregistered extension";
continue;
Expand All @@ -142,7 +152,7 @@ void parseGamelist(SystemData* system)
else if(!file->isArcadeAsset())
{
std::string defaultName = file->metadata.get("name");
file->metadata = MetaDataList::createFromXML(GAME_METADATA, fileNode, relativeTo);
file->metadata = MetaDataList::createFromXML(i == 0 ? GAME_METADATA : FOLDER_METADATA, fileNode, relativeTo);

//make sure name gets set if one didn't exist
if(file->metadata.get("name").empty())
Expand Down Expand Up @@ -173,7 +183,8 @@ void addFileDataNode(pugi::xml_node& parent, const FileData* file, const char* t
//there's something useful in there so we'll keep the node, add the path

// try and make the path relative if we can so things still work if we change the rom folder location in the future
newNode.prepend_child("path").text().set(Utils::FileSystem::createRelativePath(file->getPath(), system->getStartPath(), false, true).c_str());
std::string relPath = Utils::FileSystem::createRelativePath(file->getPath(), system->getStartPath(), false, true);
newNode.prepend_child("path").text().set(relPath.c_str());
}
}

Expand Down Expand Up @@ -224,23 +235,22 @@ void updateGamelist(SystemData* system)
{
int numUpdated = 0;

//get only files, no folders
std::vector<FileData*> files = rootFolder->getFilesRecursive(GAME | FOLDER);

// Stage 1: iterate through all files in memory, checking for changes
for(std::vector<FileData*>::const_iterator fit = files.cbegin(); fit != files.cend(); ++fit)
{

// do not touch if it wasn't changed anyway
if (!(*fit)->metadata.wasChanged())
continue;

// adding item to changed list
if ((*fit)->getType() == GAME)
if ((*fit)->getType() == GAME)
{
changedGames.push_back((*fit));
changedGames.push_back((*fit));
}
else
else
{
changedFolders.push_back((*fit));
}
Expand All @@ -251,13 +261,13 @@ void updateGamelist(SystemData* system)
const char* tagList[2] = { "game", "folder" };
FileType typeList[2] = { GAME, FOLDER };
std::vector<FileData*> changedList[2] = { changedGames, changedFolders };

for(int i = 0; i < 2; i++)
{
const char* tag = tagList[i];
std::vector<FileData*> changes = changedList[i];

// if changed items of this type
// check for changed items of this type
if (changes.size() > 0) {
// check if the item already exists in the XML
// if it does, remove all corresponding items before adding
Expand All @@ -274,9 +284,10 @@ void updateGamelist(SystemData* system)
continue;
}

std::string xmlpath = pathNode.text().get();
// apply the same transformation as in Gamelist::parseGamelist
std::string xmlpath = Utils::FileSystem::resolveRelativePath(pathNode.text().get(), relativeTo, false, true);
xmlpath = Utils::FileSystem::resolveRelativePath(xmlpath, relativeTo, false, true);

for(std::vector<FileData*>::const_iterator cfit = changes.cbegin(); cfit != changes.cend(); ++cfit)
{
if(xmlpath == (*cfit)->getPath())
Expand Down
19 changes: 13 additions & 6 deletions es-app/src/MetaData.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "MetaData.h"

#include "utils/FileSystemUtil.h"
#include "utils/TimeUtil.h"
#include "Log.h"
#include <pugixml.hpp>

Expand All @@ -27,6 +28,12 @@ MetaDataDecl gameDecls[] = {
};
const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls + sizeof(gameDecls) / sizeof(gameDecls[0]));

const inline std::string blankDate() {
// blank date (1970-01-02) is used to render "" (see DateTimeComponent.cpp) for
// folder metadata when no date is provided (=default case)
return Utils::Time::timeToString(Utils::Time::BLANK_DATE, "%Y%m%d");
}

MetaDataDecl folderDecls[] = {
{"name", MD_STRING, "", false, "name", "enter game name"},
{"sortname", MD_STRING, "", false, "sortname", "enter game sort name"},
Expand All @@ -35,12 +42,12 @@ MetaDataDecl folderDecls[] = {
{"thumbnail", MD_PATH, "", false, "thumbnail", "enter path to thumbnail"},
{"video", MD_PATH, "", false, "video", "enter path to video"},
{"marquee", MD_PATH, "", false, "marquee", "enter path to marquee"},
{"rating", MD_RATING, "0.000000", false, "rating", "enter rating"},
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"},
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer"},
{"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher"},
{"genre", MD_STRING, "unknown", false, "genre", "enter game genre"},
{"players", MD_INT, "1", false, "players", "enter number of players"}
{"rating", MD_RATING, "", false, "rating", "enter rating"},
{"releasedate", MD_DATE, blankDate(), true, "release date", "enter release date"},
{"developer", MD_STRING, "", false, "developer", "enter game developer"},
{"publisher", MD_STRING, "", false, "publisher", "enter game publisher"},
{"genre", MD_STRING, "", false, "genre", "enter game genre"},
{"players", MD_INT, "", false, "players", "enter number of players"}
};
const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls + sizeof(folderDecls) / sizeof(folderDecls[0]));

Expand Down
2 changes: 1 addition & 1 deletion es-app/src/MetaData.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct MetaDataDecl
std::string key;
MetaDataType type;
std::string defaultValue;
bool isStatistic; //if true, ignore scraper values for this metadata
bool isStatistic; // if true: ignore in scraping and hide in metadata edits for this key
std::string displayName; // displayed as this in editors
std::string displayPrompt; // phrase displayed in editors when prompted to enter value (currently only for strings)
};
Expand Down
5 changes: 4 additions & 1 deletion es-app/src/guis/GuiGamelistOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,10 @@ GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : Gui
if (UIModeController::getInstance()->isUIModeFull() && !mFromPlaceholder && !(mSystem->isCollection() && file->getType() == FOLDER))
{
row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, "EDIT THIS GAME'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
std::string lblTxt = std::string("EDIT THIS ");
lblTxt += std::string((file->getType() == FOLDER ? "FOLDER" : "GAME"));
lblTxt += std::string("'S METADATA");
row.addElement(std::make_shared<TextComponent>(mWindow, lblTxt, Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(makeArrow(mWindow), false);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this));
mMenu.addRow(row);
Expand Down
71 changes: 45 additions & 26 deletions es-app/src/guis/GuiMetaDataEd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "components/ButtonComponent.h"
#include "components/ComponentList.h"
#include "components/DateTimeComponent.h"
#include "components/DateTimeEditComponent.h"
#include "components/MenuComponent.h"
#include "components/RatingComponent.h"
Expand Down Expand Up @@ -37,8 +38,9 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
mHeaderGrid = std::make_shared<ComponentGrid>(mWindow, Vector2i(1, 5));

mTitle = std::make_shared<TextComponent>(mWindow, "EDIT METADATA", Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER);
mSubtitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(Utils::FileSystem::getFileName(scraperParams.game->getPath())),
Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER);
std::string tgt = md->getType() == GAME_METADATA ? "GAME" : "FOLDER";
std::string subt = tgt + ": " + Utils::String::toUpper(Utils::FileSystem::getFileName(scraperParams.game->getPath()));
mSubtitle = std::make_shared<TextComponent>(mWindow, subt, Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER);
mHeaderGrid->setEntry(mTitle, Vector2i(0, 1), false, true);
mHeaderGrid->setEntry(mSubtitle, Vector2i(0, 3), false, true);

Expand All @@ -50,16 +52,20 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
// populate list
for(auto iter = mdd.cbegin(); iter != mdd.cend(); iter++)
{
std::shared_ptr<GuiComponent> ed;

// don't add statistics
if(iter->isStatistic)
continue;

std::shared_ptr<GuiComponent> ed;

// create ed and add it (and any related components) to mMenu
// ed's value will be set below
ComponentListRow row;
auto lbl = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(iter->displayName), Font::get(FONT_SIZE_SMALL), 0x777777FF);
auto lblTxt = Utils::String::toUpper(iter->displayName);
if (iter->type == MD_DATE) {
lblTxt += " (" + DateTimeComponent::getDateformatTip() + ")";
}
auto lbl = std::make_shared<TextComponent>(mWindow, lblTxt, Font::get(FONT_SIZE_SMALL), 0x777777FF);
row.addElement(lbl, true); // label

switch(iter->type)
Expand Down Expand Up @@ -111,6 +117,8 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
{
// MD_STRING
ed = std::make_shared<TextComponent>(window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
const float height = lbl->getSize().y() * 0.71f;
ed->setSize(0, height);
row.addElement(ed, true);

auto spacer = std::make_shared<GuiComponent>(mWindow);
Expand Down Expand Up @@ -140,7 +148,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector

std::vector< std::shared_ptr<ButtonComponent> > buttons;

if(!scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
if(md->getType() == GAME_METADATA && !scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SCRAPE", "scrape", std::bind(&GuiMetaDataEd::fetch, this)));

buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SAVE", "save", [&] { save(); delete this; }));
Expand Down Expand Up @@ -185,11 +193,17 @@ void GuiMetaDataEd::save()
// remove game from index
mScraperParams.system->getIndex()->removeFromIndex(mScraperParams.game);

for(unsigned int i = 0; i < mEditors.size(); i++)
assert(mMetaDataDecl.size() >= mEditors.size());
// there may be less editfields than metadata entries as
// statistic md fields are not shown to the user.
// md statistic fields are not necessarily at the end of the md list
int edIdx = 0;
for(auto &mdd : mMetaDataDecl)
{
if(mMetaDataDecl.at(i).isStatistic)
continue;
mMetaData->set(mMetaDataDecl.at(i).key, mEditors.at(i)->getValue());
if(!mdd.isStatistic) {
mMetaData->set(mdd.key, mEditors.at(edIdx)->getValue());
edIdx++;
}
}

// enter game in index
Expand All @@ -212,29 +226,21 @@ void GuiMetaDataEd::fetch()

void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
{
for(unsigned int i = 0; i < mEditors.size(); i++)
assert(mMetaDataDecl.size() >= mEditors.size());
int edIdx = 0;
for(auto &mdd : mMetaDataDecl)
{
if(mMetaDataDecl.at(i).isStatistic)
if(mdd.isStatistic)
continue;

const std::string& key = mMetaDataDecl.at(i).key;
mEditors.at(i)->setValue(result.mdl.get(key));
mEditors.at(edIdx)->setValue(result.mdl.get(mdd.key));
edIdx++;
}
}

void GuiMetaDataEd::close(bool closeAllWindows)
{
// find out if the user made any changes
bool dirty = false;
for(unsigned int i = 0; i < mEditors.size(); i++)
{
const std::string& key = mMetaDataDecl.at(i).key;
if(mMetaData->get(key) != mEditors.at(i)->getValue())
{
dirty = true;
break;
}
}
bool dirty = hasChanges();

std::function<void()> closeFunc;
if(!closeAllWindows)
Expand All @@ -248,7 +254,6 @@ void GuiMetaDataEd::close(bool closeAllWindows)
};
}


if(dirty)
{
// changes were made, ask if the user wants to save them
Expand All @@ -262,6 +267,20 @@ void GuiMetaDataEd::close(bool closeAllWindows)
}
}

bool GuiMetaDataEd::hasChanges()
{
assert(mMetaDataDecl.size() >= mEditors.size());
// find out if the user made any changes
int edIdx = 0;
for(auto &mdd : mMetaDataDecl)
{
if(!mdd.isStatistic && mMetaData->get(mdd.key) != mEditors.at(edIdx++)->getValue())
return true;
}
return false;
}


bool GuiMetaDataEd::input(InputConfig* config, Input input)
{
if(GuiComponent::input(config, input))
Expand Down
1 change: 1 addition & 0 deletions es-app/src/guis/GuiMetaDataEd.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class GuiMetaDataEd : public GuiComponent
void fetch();
void fetchDone(const ScraperSearchResult& result);
void close(bool closeAllWindows);
bool hasChanges();

NinePatchComponent mBackground;
ComponentGrid mGrid;
Expand Down
Loading

0 comments on commit a113b22

Please sign in to comment.