2020#include " retroarch.h"
2121
2222#include " gameentry.h"
23- #include " pathtools.h"
2423#include " platform.h"
25- #include " strtools.h"
2624
27- #include < QDate>
2825#include < QDebug>
2926#include < QDir>
3027#include < QFile>
3330#include < QJsonObject>
3431#include < QRegularExpression>
3532#include < QStringBuilder>
36- #include < QTextStream>
3733
38- static const QString RA_LPL_VERSION = " 1.5" ;
39- static const QString RA_DETECT = " DETECT" ;
34+ static const QString LPL_VERSION_VAL = " 1.5" ;
35+ static const QString DETECT_VAL = " DETECT" ;
36+
37+ const QString META_VERSION = " version" ;
38+ const QString META_DFLT_CORE_PATH = " default_core_path" ;
39+ const QString META_DFLT_CORE_NAME = " default_core_name" ;
40+
41+ const QStringList LPL_META_PROPS = {
42+ META_DFLT_CORE_PATH, META_DFLT_CORE_NAME, META_VERSION,
43+ " label_display_mode" , " left_display_mode" , " right_display_mode" ,
44+ " thumbnail_match_mode" , " sort_mode" , " base_content_directory" };
45+
46+ const QString ITEMS_ARRAY = " items" ;
47+
48+ const QString ITEM_CORE_NAME = " core_name" ;
49+ const QString ITEM_CORE_PATH = " core_path" ;
50+ const QString ITEM_CRC = " crc32" ;
51+ const QString ITEM_DB_NAME = " db_name" ;
52+ const QString ITEM_LABEL = " label" ;
53+ const QString ITEM_PATH = " path" ;
4054
4155RetroArch::RetroArch () {}
4256
@@ -47,16 +61,6 @@ QString RetroArch::sanitizeForFilename(const QString &name) {
4761 return sanitized;
4862}
4963
50- QString RetroArch::jsonEscape (const QString &str) {
51- QString escaped = str;
52- escaped.replace (" \\ " , " \\\\ " );
53- escaped.replace (" \" " , " \\\" " );
54- escaped.replace (" \n " , " \\ n" );
55- escaped.replace (" \r " , " \\ r" );
56- escaped.replace (" \t " , " \\ t" );
57- return escaped;
58- }
59-
6064const QString RetroArch::getPlatformOutputName () {
6165 // Look up the RetroArch db_name from peas.json
6266 QString dbName = Platform::get ().getRetroArchDbName (config->platform );
@@ -72,47 +76,35 @@ const QString RetroArch::getPlatformOutputName() {
7276}
7377
7478bool RetroArch::loadOldGameList (const QString &gameListFileString) {
75- QFile gameListFile (gameListFileString);
76- if (!gameListFile.exists () || !gameListFile.open (QIODevice::ReadOnly)) {
77- return false ;
78- }
79-
80- QByteArray jsonData = gameListFile.readAll ();
81- gameListFile.close ();
82-
83- QJsonDocument doc = QJsonDocument::fromJson (jsonData);
84- if (!doc.isObject ()) {
79+ QJsonDocument doc;
80+ if (QFile gameListFile (gameListFileString);
81+ !gameListFile.open (QIODevice::ReadOnly)) {
8582 return false ;
83+ } else {
84+ QByteArray jsonData = gameListFile.readAll ();
85+ gameListFile.close ();
86+ doc = QJsonDocument::fromJson (jsonData);
87+ if (!doc.isObject ()) {
88+ return false ;
89+ }
8690 }
8791
88- QJsonObject root = doc.object ();
89- QJsonArray items = root .value (" items " ).toArray ();
92+ existingPlaylist = doc.object ();
93+ QJsonArray items = existingPlaylist .value (ITEMS_ARRAY ).toArray ();
9094
9195 for (const QJsonValue &item : items) {
92- if (!item.isObject ()) {
93- continue ;
96+ if (item.isObject ()) {
97+ QJsonObject itemObj = item.toObject ();
98+ GameEntry oldEntry;
99+ // always absolute path with Retroarch
100+ // path might be contain backslashes on Windows
101+ oldEntry.path = itemObj.value (ITEM_PATH).toString ();
102+ oldEntry.title = itemObj.value (ITEM_LABEL).toString ();
103+ // remaining properties of an item are held in existingPlaylist
104+ oldEntries.append (oldEntry);
94105 }
95-
96- QJsonObject itemObj = item.toObject ();
97- GameEntry newEntry;
98- newEntry.path = itemObj.value (" path" ).toString ();
99- newEntry.title = itemObj.value (" label" ).toString ();
100- // FIXME: preserve also db_name if present for game and core_name,
101- // core_path if not "DETECT"
102- oldEntries.append (newEntry);
103106 }
104107
105- // FIXME: preserve also "preamble" (=everything, except version sibling to
106- // items) e.g.:
107- // "default_core_path": "", # overwrite only iff -e / raExtra is provided
108- // "default_core_name": "", # overwrite only iff -e / raExtra is provided
109- // "base_content_directory": "yadda_yadda",
110- // "label_display_mode": 2,
111- // "right_thumbnail_mode": 0,
112- // "left_thumbnail_mode": 0,
113- // "thumbnail_match_mode": 0,
114- // "sort_mode": 2,
115-
116108 return true ;
117109}
118110
@@ -145,19 +137,6 @@ void RetroArch::skipExisting(QList<GameEntry> &gameEntries,
145137 }
146138}
147139
148- void RetroArch::preserveFromOld (GameEntry &entry) {
149- QString fn = entry.baseName ;
150- for (const auto &oldEntry : oldEntries) {
151- if (QFileInfo (oldEntry.path ).fileName () == fn) {
152- if (entry.title .isEmpty ()) {
153- entry.title = oldEntry.title ;
154- }
155- // FIXME: restore also other values (see FIXME in loadOldGameList())
156- break ;
157- }
158- }
159- }
160-
161140void RetroArch::assembleList (QString &finalOutput,
162141 QList<GameEntry> &gameEntries) {
163142 if (gameEntries.isEmpty ())
@@ -169,71 +148,101 @@ void RetroArch::assembleList(QString &finalOutput,
169148 baseNameToTitle[entry.baseName ] = entry.title ;
170149 }
171150
172- QJsonObject root;
173- root.insert (" version" , RA_LPL_VERSION);
174-
175- // Parse default_core_path and default_core_name from frontendExtra
176- // Format: "<CORE_PATH>;<CORE_NAME>" or leave as DETECT
177- QString corePathStr = RA_DETECT;
178- QString coreNameStr = RA_DETECT;
179-
180- if (!config->frontendExtra .isEmpty ()) {
181- // frontendExtra is used for default_core_path and default_core_name
182- // Format: "<CORE_PATH>;<CORE_NAME>"
183- QStringList parts = config->frontendExtra .split (" ;" );
184- corePathStr = parts[0 ];
185- coreNameStr = parts[1 ];
186- }
151+ QJsonObject newPlaylist = createMetaProps ();
187152
188- // FIXME: if values from preamble exist from existing playlist use these
189- // instead of the defaults
190- root.insert (" default_core_path" , corePathStr);
191- root.insert (" default_core_name" , coreNameStr);
192- root.insert (" label_display_mode" , " 0" ); // show full labels
193- root.insert (" left_display_mode" , " 0" ); // system default
194- root.insert (" right_display_mode" , " 0" ); // system default
195- root.insert (" thumbnail_match_mode" , " 0" ); // system default
196- root.insert (" sort_mode" , " 0" ); // system default
153+ QJsonArray exitsingItems = existingPlaylist.value (ITEMS_ARRAY).toArray ();
154+ QJsonObject eitemObj;
197155
198156 QJsonArray items;
157+ QString gameFn;
199158
200159 int dots = -1 ;
201160 int dotMod = gameEntries.length () * 0.1 + 1 ;
202-
203- for (const auto &entry : gameEntries) {
161+ for (auto const &entry : gameEntries) {
204162 if (++dots % dotMod == 0 ) {
205163 printf (" ." );
206164 fflush (stdout);
207165 }
208-
166+ gameFn = QFileInfo (entry. path ). fileName ();
209167 // TODO: unpack support for CRC and inter-zip reference
210168 // "path": "/storage/emulated/0/ROMs/virtualboy/Game.zip#Game.vb",
211169 // "crc32": "133E9372|crc",
212-
213- QJsonObject itemObj;
214170 QString absPath = entry.absoluteFilePath ;
215171#ifdef Q_OS_WIN
216172 absPath = absPath.replace (" /" , " \\\\ " );
217173#endif
218- itemObj.insert (" path" , absPath);
219- itemObj.insert (" label" , entry.title );
220- itemObj.insert (" core_path" , RA_DETECT);
221- itemObj.insert (" core_name" , RA_DETECT);
222- itemObj.insert (" crc32" , RA_DETECT);
223- itemObj.insert (" db_name" , QString (getPlatformOutputName () % " .lpl" ));
174+ bool hasExisting = false ;
175+ QJsonObject itemObj;
176+ for (const QJsonValue &eit : exitsingItems) {
177+ if (eit.isObject ()) {
178+ eitemObj = eit.toObject ();
179+ if (eitemObj[ITEM_PATH].toString ().endsWith (gameFn)) {
180+ hasExisting = true ;
181+ itemObj = eitemObj;
182+ break ;
183+ }
184+ }
185+ }
186+
187+ if (!hasExisting) {
188+ itemObj.insert (ITEM_CORE_PATH, DETECT_VAL);
189+ itemObj.insert (ITEM_CORE_NAME, DETECT_VAL);
190+ itemObj.insert (ITEM_CRC, DETECT_VAL);
191+ itemObj.insert (ITEM_DB_NAME, getGameListFileName ());
192+ }
193+
194+ itemObj.insert (ITEM_PATH, absPath);
195+ itemObj.insert (ITEM_LABEL, entry.title );
224196
225197 items.append (itemObj);
226198 }
227199
228- root .insert (" items " , items);
200+ newPlaylist .insert (ITEMS_ARRAY , items);
229201
230- QJsonDocument doc (root );
202+ QJsonDocument doc (newPlaylist );
231203 finalOutput = doc.toJson (QJsonDocument::Indented);
232204}
233205
206+ QJsonObject RetroArch::createMetaProps () {
207+ QJsonObject newPlaylist;
208+ newPlaylist.insert (META_VERSION, LPL_VERSION_VAL);
209+
210+ QString corePathStr = DETECT_VAL;
211+ QString coreNameStr = DETECT_VAL;
212+
213+ // Parse default_core_path and default_core_name from frontendExtra
214+ // (raExtra= or -e)
215+ if (!config->frontendExtra .isEmpty ()) {
216+ QStringList parts = config->frontendExtra .split (" ;" );
217+ corePathStr = parts[0 ];
218+ coreNameStr = parts[1 ];
219+ }
220+
221+ // create or restore meta properties
222+ for (const auto &k : LPL_META_PROPS) {
223+ QString v = existingPlaylist[k].toString ();
224+ if (v.isEmpty ()) {
225+ if (k == META_VERSION)
226+ newPlaylist.insert (k, LPL_VERSION_VAL);
227+ else if (k == META_DFLT_CORE_NAME)
228+ newPlaylist.insert (k, coreNameStr);
229+ else if (k == META_DFLT_CORE_PATH)
230+ newPlaylist.insert (k, corePathStr);
231+ else if (k == " base_content_directory" )
232+ ; // don't set default "base_content_directory"
233+ else
234+ newPlaylist.insert (k, " 0" );
235+ } else {
236+ newPlaylist.insert (k, v);
237+ }
238+ }
239+ return newPlaylist;
240+ }
241+
234242QString RetroArch::getTargetFileName (GameEntry::Types t,
235243 const QString &baseName) {
236- (void )t; // Suppress unused parameter warning
244+ (void )t;
245+ // for media files use sanitized title as filename stem
237246 QString title = baseNameToTitle.value (baseName, baseName);
238247 return sanitizeForFilename (title);
239248}
@@ -242,7 +251,7 @@ bool RetroArch::canSkip() { return true; }
242251
243252QString RetroArch::getGameListFileName () {
244253 return config->gameListFilename .isEmpty ()
245- ? (getPlatformOutputName () + " .lpl" )
254+ ? (getPlatformOutputName () % " .lpl" )
246255 : config->gameListFilename ;
247256}
248257
@@ -251,11 +260,27 @@ QString RetroArch::getInputFolder() {
251260}
252261
253262QString RetroArch::getGameListFolder () {
254- return QDir::homePath () % " /.config/retroarch/playlists" ;
263+ if (config->gameListFolder .isEmpty ()) {
264+ return QDir::homePath () % " /.config/retroarch/playlists" ;
265+ } else {
266+ if (config->gameListFolder .endsWith (" /" % config->platform )) {
267+ return config->gameListFolder .replace (" /" % config->platform , " " );
268+ }
269+ return config->gameListFolder ;
270+ }
255271}
256272
257273QString RetroArch::getMediaFolder () {
258- return QDir::homePath () % " /.config/retroarch/thumbnails" ;
274+ if (config->mediaFolder .isEmpty ()) {
275+ return QDir::homePath () % " /.config/retroarch/thumbnails/" %
276+ getPlatformOutputName ();
277+ } else {
278+ if (config->mediaFolder .endsWith (" /" % config->platform )) {
279+ return config->mediaFolder .replace (" /" % config->platform ,
280+ " /" % getPlatformOutputName ());
281+ }
282+ return config->mediaFolder ;
283+ }
259284}
260285
261286QString RetroArch::getCoversFolder () {
0 commit comments