Skip to content

Commit

Permalink
changed element name to match gamelist spec (cover -> thumbnail)
Browse files Browse the repository at this point in the history
plus:

- avoid altering gameentries list while iterating over it
- doc for <folder/> preservation/creation.
  • Loading branch information
Gemba committed Dec 18, 2023
1 parent 197568d commit 13be376
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 15 deletions.
7 changes: 7 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
## Changes of this Fork

### TBA (Version 3.9.3)

- Feature: Preserve existing `<folder/>` nodes in gamelist or create skeleton
`<folder/>` nodes when ROMs are stored in subfolders within a system folder,
see [frontend documentation](FRONTENDS.md#metadata-preservation) and the [Gamelist
specification](https://github.com/RetroPie/EmulationStation/blob/master/GAMELISTS.md#folder).

### 2023-12-01 (Version 3.9.2)

- Feature: Import of data in XML format is now more lax (does not rely on
Expand Down
40 changes: 39 additions & 1 deletion docs/FRONTENDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,45 @@ This is the default frontend used when generating a game list with Skyscraper. I

#### Metadata preservation

Skyscraper will preserve the following metadata when re-generating a game list for EmulationStation: `favorite`, `hidden`, `kidgame`, `lastplayed`, `playcount`, `sortname`.
Skyscraper will preserve the following metadata when re-generating a game list for EmulationStation: `favorite`, `hidden`, `kidgame`, `lastplayed`, `playcount`, `sortname`. Also existing `<folder/>` nodes of a Gamelist file will be preserved, if at least one ROM is within a subfolder and this subfolder is not yet part of the `gamelist.xml` file. It will be added with two mandatory subelements:

- `<path/>` reflects the relative subpath from the system folder and
- `<name/>`, which represents the direct parent folder of a ROM by default. However, you may edit this to any name which should be shown in EmulationStation.

Each parent of a folder, that is not yet present in the Gamelist file will be added until the system folder is reached. The user editable sub-XML elements for a folder are listed in the [`Metadata.cpp` of EmulationStation](https://github.com/RetroPie/EmulationStation/blob/01de7618d0d248fa2ff1eacde09a20d9d2af5f10/es-app/src/MetaData.cpp#L30).

!!! example

Consider this folder structure below `snes`, whereas each lowest folder contains at least one ROM:
```
snes
└── Retail
├── EUR
├── JP
└── USA
```
Skyscraper will generate these <folder/> elements if not present in `gamelist.xml`:
```xml
[...]
<folder>
<path>./Retail</path>
<name>Retail</name>
</folder>
<folder>
<path>./Retail/USA</path>
<name>USA</name>
</folder>
<folder>
<path>./Retail/JP</path>
<name>JP</name>
</folder>
<folder>
<path>./Retail/EUR</path>
<name>EUR</name>
</folder>
[...]
```
Note that the `Retail` folder is added even if it does not contain a ROM because but is part of the path to the ROMs in the lowest folders.

### RetroBat

Expand Down
37 changes: 25 additions & 12 deletions src/emulationstation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ void EmulationStation::assembleList(QString &finalOutput,
}
}

QList<GameEntry> added;
QDir inputDir = QDir(config->inputFolder);
for (auto &entry : gameEntries) {
if (config->platform == "daphne") {
Expand All @@ -181,25 +182,27 @@ void EmulationStation::assembleList(QString &finalOutput,

// Check if path is exactly one subfolder beneath root platform
// folder (has one more '/') and uses *.cue suffix
QString entryCanonicalPath = entryInfo.canonicalPath();
if (cueSuffix && entryCanonicalPath.count("/") ==
QString entryCanonicalDir = entryInfo.canonicalPath();
if (cueSuffix && entryCanonicalDir.count("/") ==
config->inputFolder.count("/") + 1) {
// Check if subfolder has exactly one ROM, in which case we
// use <folder>
if (QDir(entryCanonicalPath, extensions).count() == 1) {
if (QDir(entryCanonicalDir, extensions).count() == 1) {
entry.isFolder = true;
entry.path = entryCanonicalPath;
entry.path = entryCanonicalDir;
}
}

// inputDir is canonical
QString subPath = inputDir.relativeFilePath(entryCanonicalPath);
QString subPath = inputDir.relativeFilePath(entryCanonicalDir);
if (subPath != ".") {
// <folder> element(s) are needed
addFolder(config->inputFolder, subPath, gameEntries);
addFolder(config->inputFolder, subPath, gameEntries, added);
}
}

gameEntries.append(added);

int dots = -1;
int dotMod = 1 + gameEntries.length() * 0.1;

Expand All @@ -224,32 +227,41 @@ void EmulationStation::assembleList(QString &finalOutput,
}

void EmulationStation::addFolder(QString &base, QString sub,
QList<GameEntry> &gameEntries) {
QList<GameEntry> &gameEntries,
QList<GameEntry> &added) {
bool found = false;
QString absPath = base % "/" % sub;
qDebug() << "addFolder() called with:" << absPath;

for (auto &entry : gameEntries) {
if (entry.isFolder && entry.path == absPath) {
for (auto &entry : added) {
if (entry.path == absPath) {
found = true;
break;
}
}

if (!found) {
for (auto &entry : gameEntries) {
if (entry.isFolder && entry.path == absPath) {
found = true;
break;
}
}
}

if (!found) {
GameEntry fe;
fe.path = absPath;
fe.title = sub.mid(sub.lastIndexOf('/') + 1, sub.length());
fe.isFolder = true;
qDebug() << "addFolder() adding folder elem, path:" << fe.path
<< "with title/name:" << fe.title;
gameEntries.append(fe);
added.append(fe);
}

if (sub.contains('/')) {
// one folder up
sub = sub.left(sub.lastIndexOf('/'));
addFolder(base, sub, gameEntries);
addFolder(base, sub, gameEntries, added);
}
}

Expand Down Expand Up @@ -305,6 +317,7 @@ QString EmulationStation::createXml(GameEntry &entry) {

l.append(" </" % entryType % ">");
l.removeAll("");

return l.join("\n") % "\n";
}

Expand Down
3 changes: 2 additions & 1 deletion src/emulationstation.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ class EmulationStation : public AbstractFrontend {
QString getVideosFolder() override;

private:
void addFolder(QString &base, QString sub, QList<GameEntry> &gameEntries);
void addFolder(QString &base, QString sub, QList<GameEntry> &gameEntries,
QList<GameEntry> &added);
QString createXml(GameEntry &entry);
QString elem(QString elem, QString &data, bool addEmptyElem,
bool isPath = false);
Expand Down
2 changes: 1 addition & 1 deletion src/xmlreader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ void XmlReader::addEntries(const QDomNodeList &nodes,
// Do NOT get sqr and par notes here. They are not used by skipExisting
entry.title = nodes.at(a).firstChildElement("name").text();
entry.coverFile = makeAbsolute(
nodes.at(a).firstChildElement("cover").text(), inputFolder);
nodes.at(a).firstChildElement("thumbnail").text(), inputFolder);
entry.screenshotFile = makeAbsolute(
nodes.at(a).firstChildElement("image").text(), inputFolder);
entry.marqueeFile = makeAbsolute(
Expand Down

0 comments on commit 13be376

Please sign in to comment.