2014-06-25 16:29:58 +00:00
# include "Gamelist.h"
2017-11-01 22:21:10 +00:00
2019-08-24 14:22:02 +00:00
# include <chrono>
2018-01-09 22:55:09 +00:00
# include "utils/FileSystemUtil.h"
2017-11-01 22:21:10 +00:00
# include "FileData.h"
# include "FileFilterIndex.h"
2014-06-25 16:29:58 +00:00
# include "Log.h"
# include "Settings.h"
2017-11-01 22:21:10 +00:00
# include "SystemData.h"
2017-11-10 19:16:42 +00:00
# include <pugixml/src/pugixml.hpp>
2014-06-25 16:29:58 +00:00
2018-01-29 22:50:10 +00:00
FileData * findOrCreateFile ( SystemData * system , const std : : string & path , FileType type )
2014-06-25 16:29:58 +00:00
{
// first, verify that path is within the system's root folder
FileData * root = system - > getRootFolder ( ) ;
bool contains = false ;
2018-01-29 22:50:10 +00:00
std : : string relative = Utils : : FileSystem : : removeCommonPath ( path , root - > getPath ( ) , contains ) ;
2018-01-26 18:53:19 +00:00
2014-06-25 16:29:58 +00:00
if ( ! contains )
{
LOG ( LogError ) < < " File path \" " < < path < < " \" is outside system path \" " < < system - > getStartPath ( ) < < " \" " ;
return NULL ;
}
2018-01-29 22:50:10 +00:00
Utils : : FileSystem : : stringList pathList = Utils : : FileSystem : : getPathList ( relative ) ;
auto path_it = pathList . begin ( ) ;
2014-06-25 16:29:58 +00:00
FileData * treeNode = root ;
bool found = false ;
2018-01-29 22:50:10 +00:00
while ( path_it ! = pathList . end ( ) )
2014-06-25 16:29:58 +00:00
{
2016-08-09 20:26:30 +00:00
const std : : unordered_map < std : : string , FileData * > & children = treeNode - > getChildrenByFilename ( ) ;
2018-01-29 22:50:10 +00:00
std : : string key = * path_it ;
2017-11-11 14:56:22 +00:00
found = children . find ( key ) ! = children . cend ( ) ;
2016-08-09 20:26:30 +00:00
if ( found ) {
treeNode = children . at ( key ) ;
2014-06-25 16:29:58 +00:00
}
// this is the end
2018-01-29 22:50:10 +00:00
if ( path_it = = - - pathList . end ( ) )
2014-06-25 16:29:58 +00:00
{
if ( found )
return treeNode ;
if ( type = = FOLDER )
{
LOG ( LogWarning ) < < " gameList: folder doesn't already exist, won't create " ;
return NULL ;
}
2017-06-12 16:38:59 +00:00
FileData * file = new FileData ( type , path , system - > getSystemEnvData ( ) , system ) ;
2018-05-10 01:29:46 +00:00
// skipping arcade assets from gamelist
if ( ! file - > isArcadeAsset ( ) )
{
treeNode - > addChild ( file ) ;
}
2014-06-25 16:29:58 +00:00
return file ;
}
if ( ! found )
{
// don't create folders unless it's leading up to a game
// 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 " ;
return NULL ;
}
2017-05-18 10:16:57 +00:00
2014-06-25 16:29:58 +00:00
// create missing folder
2018-01-29 22:50:10 +00:00
FileData * folder = new FileData ( FOLDER , Utils : : FileSystem : : getStem ( treeNode - > getPath ( ) ) + " / " + * path_it , system - > getSystemEnvData ( ) , system ) ;
2014-06-25 16:29:58 +00:00
treeNode - > addChild ( folder ) ;
treeNode = folder ;
}
path_it + + ;
}
return NULL ;
}
void parseGamelist ( SystemData * system )
{
2018-01-29 22:50:10 +00:00
bool trustGamelist = Settings : : getInstance ( ) - > getBool ( " ParseGamelistOnly " ) ;
2014-06-25 16:29:58 +00:00
std : : string xmlpath = system - > getGamelistPath ( false ) ;
2018-01-09 22:55:09 +00:00
if ( ! Utils : : FileSystem : : exists ( xmlpath ) )
2014-06-25 16:29:58 +00:00
return ;
LOG ( LogInfo ) < < " Parsing XML file \" " < < xmlpath < < " \" ... " ;
pugi : : xml_document doc ;
pugi : : xml_parse_result result = doc . load_file ( xmlpath . c_str ( ) ) ;
if ( ! result )
{
LOG ( LogError ) < < " Error parsing XML file \" " < < xmlpath < < " \" ! \n " < < result . description ( ) ;
return ;
}
pugi : : xml_node root = doc . child ( " gameList " ) ;
if ( ! root )
{
LOG ( LogError ) < < " Could not find <gameList> node in gamelist \" " < < xmlpath < < " \" ! " ;
return ;
}
2018-01-29 22:50:10 +00:00
std : : string relativeTo = system - > getStartPath ( ) ;
2014-06-25 16:29:58 +00:00
const char * tagList [ 2 ] = { " game " , " folder " } ;
FileType typeList [ 2 ] = { GAME , FOLDER } ;
for ( int i = 0 ; i < 2 ; i + + )
{
const char * tag = tagList [ i ] ;
FileType type = typeList [ i ] ;
for ( pugi : : xml_node fileNode = root . child ( tag ) ; fileNode ; fileNode = fileNode . next_sibling ( tag ) )
{
2018-01-29 22:50:10 +00:00
const std : : string path = Utils : : FileSystem : : resolveRelativePath ( fileNode . child ( " path " ) . text ( ) . get ( ) , relativeTo , false ) ;
2017-05-18 10:16:57 +00:00
2018-01-29 22:50:10 +00:00
if ( ! trustGamelist & & ! Utils : : FileSystem : : exists ( path ) )
2014-06-25 16:29:58 +00:00
{
LOG ( LogWarning ) < < " File \" " < < path < < " \" does not exist! Ignoring. " ;
continue ;
}
2018-01-26 18:53:19 +00:00
FileData * file = findOrCreateFile ( system , path , type ) ;
2014-06-25 16:29:58 +00:00
if ( ! file )
{
LOG ( LogError ) < < " Error finding/creating FileData for \" " < < path < < " \" , skipping. " ;
continue ;
}
2018-05-10 01:29:46 +00:00
else if ( ! file - > isArcadeAsset ( ) )
{
std : : string defaultName = file - > metadata . get ( " name " ) ;
file - > metadata = MetaDataList : : createFromXML ( GAME_METADATA , fileNode , relativeTo ) ;
2014-06-25 16:29:58 +00:00
2018-05-10 01:29:46 +00:00
//make sure name gets set if one didn't exist
if ( file - > metadata . get ( " name " ) . empty ( ) )
file - > metadata . set ( " name " , defaultName ) ;
2016-12-19 15:59:40 +00:00
2018-05-10 01:29:46 +00:00
file - > metadata . resetChangedFlag ( ) ;
}
2014-06-25 16:29:58 +00:00
}
}
}
void addFileDataNode ( pugi : : xml_node & parent , const FileData * file , const char * tag , SystemData * system )
{
//create game and add to parent node
pugi : : xml_node newNode = parent . append_child ( tag ) ;
//write metadata
file - > metadata . appendToXML ( newNode , true , system - > getStartPath ( ) ) ;
2017-05-18 10:16:57 +00:00
2014-06-25 16:29:58 +00:00
if ( newNode . children ( ) . begin ( ) = = newNode . child ( " name " ) //first element is name
& & + + newNode . children ( ) . begin ( ) = = newNode . children ( ) . end ( ) //theres only one element
2016-03-29 15:33:19 +00:00
& & newNode . child ( " name " ) . text ( ) . get ( ) = = file - > getDisplayName ( ) ) //the name is the default
2014-06-25 16:29:58 +00:00
{
//if the only info is the default name, don't bother with this node
//delete it and ultimately do nothing
parent . remove_child ( newNode ) ;
} else {
//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
2018-01-29 22:50:10 +00:00
newNode . prepend_child ( " path " ) . text ( ) . set ( Utils : : FileSystem : : createRelativePath ( file - > getPath ( ) , system - > getStartPath ( ) , false ) . c_str ( ) ) ;
2014-06-25 16:29:58 +00:00
}
}
void updateGamelist ( SystemData * system )
{
//We do this by reading the XML again, adding changes and then writing it back,
//because there might be information missing in our systemdata which would then miss in the new XML.
//We have the complete information for every game though, so we can simply remove a game
//we already have in the system from the XML, and then add it back from its GameData information...
if ( Settings : : getInstance ( ) - > getBool ( " IgnoreGamelist " ) )
return ;
pugi : : xml_document doc ;
pugi : : xml_node root ;
std : : string xmlReadPath = system - > getGamelistPath ( false ) ;
2018-01-09 22:55:09 +00:00
if ( Utils : : FileSystem : : exists ( xmlReadPath ) )
2014-06-25 16:29:58 +00:00
{
//parse an existing file first
pugi : : xml_parse_result result = doc . load_file ( xmlReadPath . c_str ( ) ) ;
2017-05-18 10:16:57 +00:00
2014-06-25 16:29:58 +00:00
if ( ! result )
{
LOG ( LogError ) < < " Error parsing XML file \" " < < xmlReadPath < < " \" ! \n " < < result . description ( ) ;
return ;
}
root = doc . child ( " gameList " ) ;
if ( ! root )
{
LOG ( LogError ) < < " Could not find <gameList> node in gamelist \" " < < xmlReadPath < < " \" ! " ;
return ;
}
} else {
//set up an empty gamelist to append to
root = doc . append_child ( " gameList " ) ;
}
//now we have all the information from the XML. now iterate through all our games and add information from there
FileData * rootFolder = system - > getRootFolder ( ) ;
if ( rootFolder ! = nullptr )
{
2016-12-19 15:59:40 +00:00
int numUpdated = 0 ;
2014-06-25 16:29:58 +00:00
//get only files, no folders
std : : vector < FileData * > files = rootFolder - > getFilesRecursive ( GAME | FOLDER ) ;
//iterate through all files, checking if they're already in the XML
2016-12-19 15:59:40 +00:00
for ( std : : vector < FileData * > : : const_iterator fit = files . cbegin ( ) ; fit ! = files . cend ( ) ; + + fit )
2014-06-25 16:29:58 +00:00
{
const char * tag = ( ( * fit ) - > getType ( ) = = GAME ) ? " game " : " folder " ;
2016-12-19 15:59:40 +00:00
// do not touch if it wasn't changed anyway
if ( ! ( * fit ) - > metadata . wasChanged ( ) )
continue ;
2014-06-25 16:29:58 +00:00
// check if the file already exists in the XML
// if it does, remove it before adding
for ( pugi : : xml_node fileNode = root . child ( tag ) ; fileNode ; fileNode = fileNode . next_sibling ( tag ) )
{
pugi : : xml_node pathNode = fileNode . child ( " path " ) ;
if ( ! pathNode )
{
LOG ( LogError ) < < " < " < < tag < < " > node contains no <path> child! " ;
continue ;
}
2019-03-13 20:18:58 +00:00
std : : string nodePath = Utils : : FileSystem : : getCanonicalPath ( Utils : : FileSystem : : resolveRelativePath ( pathNode . text ( ) . get ( ) , system - > getStartPath ( ) , true ) ) ;
std : : string gamePath = Utils : : FileSystem : : getCanonicalPath ( ( * fit ) - > getPath ( ) ) ;
if ( nodePath = = gamePath )
2014-06-25 16:29:58 +00:00
{
// found it
root . remove_child ( fileNode ) ;
break ;
}
}
// it was either removed or never existed to begin with; either way, we can add it now
addFileDataNode ( root , * fit , tag , system ) ;
2016-12-19 15:59:40 +00:00
+ + numUpdated ;
2014-06-25 16:29:58 +00:00
}
//now write the file
2016-12-19 15:59:40 +00:00
if ( numUpdated > 0 ) {
2019-08-24 14:22:02 +00:00
const auto startTs = std : : chrono : : system_clock : : now ( ) ;
2016-12-19 15:59:40 +00:00
//make sure the folders leading up to this path exist (or the write will fail)
2018-01-29 22:50:10 +00:00
std : : string xmlWritePath ( system - > getGamelistPath ( true ) ) ;
Utils : : FileSystem : : createDirectory ( Utils : : FileSystem : : getParent ( xmlWritePath ) ) ;
2016-12-19 15:59:40 +00:00
LOG ( LogInfo ) < < " Added/Updated " < < numUpdated < < " entities in ' " < < xmlReadPath < < " ' " ;
2014-06-25 16:29:58 +00:00
2016-12-19 15:59:40 +00:00
if ( ! doc . save_file ( xmlWritePath . c_str ( ) ) ) {
LOG ( LogError ) < < " Error saving gamelist.xml to \" " < < xmlWritePath < < " \" (for system " < < system - > getName ( ) < < " )! " ;
}
2019-08-24 14:22:02 +00:00
const auto endTs = std : : chrono : : system_clock : : now ( ) ;
LOG ( LogInfo ) < < " Saved gamelist.xml for system \" " < < system - > getName ( ) < < " \" in " < < std : : chrono : : duration_cast < std : : chrono : : milliseconds > ( endTs - startTs ) . count ( ) < < " ms " ;
2014-06-25 16:29:58 +00:00
}
} else {
LOG ( LogError ) < < " Found no root folder for system \" " < < system - > getName ( ) < < " \" ! " ;
}
}