diff --git a/src/Gamelist.cpp b/src/Gamelist.cpp index c4c3eeba3..3331f8c79 100644 --- a/src/Gamelist.cpp +++ b/src/Gamelist.cpp @@ -4,65 +4,10 @@ #include #include "Log.h" #include "Settings.h" +#include "Util.h" namespace fs = boost::filesystem; -// expands "./my/path.sfc" to "[relativeTo]/my/path.sfc" -fs::path resolvePath(const fs::path& path, const fs::path& relativeTo) -{ - if(path.begin() != path.end() && *path.begin() == fs::path(".")) - { - fs::path ret = relativeTo; - for(auto it = ++path.begin(); it != path.end(); ++it) - ret /= *it; - - return ret; - } - - return path; -} - -// example: removeCommonPath("/home/pi/roms/nes/foo/bar.nes", "/home/pi/roms/nes/") returns "foo/bar.nes" -fs::path removeCommonPath(const fs::path& path, const fs::path& relativeTo, bool& contains) -{ - fs::path p = fs::canonical(path); - fs::path r = fs::canonical(relativeTo); - - if(p.root_path() != r.root_path()) - { - contains = false; - return p; - } - - fs::path result; - - // find point of divergence - auto itr_path = p.begin(); - auto itr_relative_to = r.begin(); - while(*itr_path == *itr_relative_to && itr_path != p.end() && itr_relative_to != r.end()) - { - ++itr_path; - ++itr_relative_to; - } - - if(itr_relative_to != r.end()) - { - contains = false; - return p; - } - - while(itr_path != p.end()) - { - if(*itr_path != fs::path(".")) - result = result / *itr_path; - - ++itr_path; - } - - contains = true; - return result; -} - FileData* findOrCreateFile(SystemData* system, const boost::filesystem::path& path, FileType type) { // first, verify that path is within the system's root folder @@ -157,6 +102,8 @@ void parseGamelist(SystemData* system) return; } + fs::path relativeTo = system->getStartPath(); + const char* tagList[2] = { "game", "folder" }; FileType typeList[2] = { GAME, FOLDER }; for(int i = 0; i < 2; i++) @@ -165,7 +112,7 @@ void parseGamelist(SystemData* system) FileType type = typeList[i]; for(pugi::xml_node fileNode = root.child(tag); fileNode; fileNode = fileNode.next_sibling(tag)) { - fs::path path = resolvePath(fileNode.child("path").text().get(), system->getStartPath()); + fs::path path = resolvePath(fileNode.child("path").text().get(), relativeTo, false); if(!boost::filesystem::exists(path)) { @@ -182,7 +129,7 @@ void parseGamelist(SystemData* system) //load the metadata std::string defaultName = file->metadata.get("name"); - file->metadata = MetaDataList::createFromXML(GAME_METADATA, fileNode); + file->metadata = MetaDataList::createFromXML(GAME_METADATA, fileNode, relativeTo); //make sure name gets set if one didn't exist if(file->metadata.get("name").empty()) @@ -197,7 +144,7 @@ void addFileDataNode(pugi::xml_node& parent, const FileData* file, const char* t pugi::xml_node newNode = parent.append_child(tag); //write metadata - file->metadata.appendToXML(newNode, true); + file->metadata.appendToXML(newNode, true, system->getStartPath()); if(newNode.children().begin() == newNode.child("name") //first element is name && ++newNode.children().begin() == newNode.children().end() //theres only one element @@ -210,17 +157,7 @@ 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 - fs::path relPath = file->getPath(); - - bool contains = false; - relPath = removeCommonPath(relPath, system->getStartPath(), contains); - if(contains) - { - // it's in the start path (which we just stripped off), so add a "./" - relPath = "." / relPath; - } - - newNode.prepend_child("path").text().set(relPath.generic_string().c_str()); + newNode.prepend_child("path").text().set(makeRelativePath(file->getPath(), system->getStartPath(), false).generic_string().c_str()); } } @@ -284,7 +221,7 @@ void updateGamelist(SystemData* system) continue; } - fs::path nodePath = resolvePath(pathNode.text().get(), system->getStartPath()); + fs::path nodePath = resolvePath(pathNode.text().get(), system->getStartPath(), true); fs::path gamePath((*fit)->getPath()); if(nodePath == gamePath || (fs::exists(nodePath) && fs::exists(gamePath) && fs::equivalent(nodePath, gamePath))) { diff --git a/src/MetaData.cpp b/src/MetaData.cpp index 1d63808ad..de111f9e4 100644 --- a/src/MetaData.cpp +++ b/src/MetaData.cpp @@ -1,6 +1,9 @@ #include "MetaData.h" #include "components/TextComponent.h" #include "Log.h" +#include "Util.h" + +namespace fs = boost::filesystem; MetaDataDecl gameDecls[] = { // key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd @@ -52,7 +55,7 @@ MetaDataList::MetaDataList(MetaDataListType type) } -MetaDataList MetaDataList::createFromXML(MetaDataListType type, pugi::xml_node node) +MetaDataList MetaDataList::createFromXML(MetaDataListType type, pugi::xml_node node, const fs::path& relativeTo) { MetaDataList mdl(type); @@ -63,7 +66,12 @@ MetaDataList MetaDataList::createFromXML(MetaDataListType type, pugi::xml_node n pugi::xml_node md = node.child(iter->key.c_str()); if(md) { - mdl.set(iter->key, md.text().get()); + // if it's a path, resolve relative paths + std::string value = md.text().get(); + if(iter->type == MD_IMAGE_PATH) + value = resolvePath(value, relativeTo, true).generic_string(); + + mdl.set(iter->key, value); }else{ mdl.set(iter->key, iter->defaultValue); } @@ -72,30 +80,27 @@ MetaDataList MetaDataList::createFromXML(MetaDataListType type, pugi::xml_node n return mdl; } -void MetaDataList::appendToXML(pugi::xml_node parent, bool ignoreDefaults) const +void MetaDataList::appendToXML(pugi::xml_node parent, bool ignoreDefaults, const fs::path& relativeTo) const { const std::vector& mdd = getMDD(); - for(auto iter = mMap.begin(); iter != mMap.end(); iter++) + for(auto mddIter = mdd.begin(); mddIter != mdd.end(); mddIter++) { - bool write = true; - - if(ignoreDefaults) + auto mapIter = mMap.find(mddIter->key); + if(mapIter != mMap.end()) { - for(auto mddIter = mdd.begin(); mddIter != mdd.end(); mddIter++) - { - if(mddIter->key == iter->first) - { - if(iter->second == mddIter->defaultValue) - write = false; + // we have this value! + // if it's just the default (and we ignore defaults), don't write it + if(ignoreDefaults && mapIter->second == mddIter->defaultValue) + continue; + + // try and make paths relative if we can + std::string value = mapIter->second; + if(mddIter->type == MD_IMAGE_PATH) + value = makeRelativePath(value, relativeTo, true).generic_string(); - break; - } - } + parent.append_child(mapIter->first.c_str()).text().set(value.c_str()); } - - if(write) - parent.append_child(iter->first.c_str()).text().set(iter->second.c_str()); } } diff --git a/src/MetaData.h b/src/MetaData.h index fa28fc547..9cecdda2a 100644 --- a/src/MetaData.h +++ b/src/MetaData.h @@ -5,6 +5,7 @@ #include #include "GuiComponent.h" #include +#include enum MetaDataType { @@ -44,8 +45,8 @@ const std::vector& getMDDByType(MetaDataListType type); class MetaDataList { public: - static MetaDataList createFromXML(MetaDataListType type, pugi::xml_node node); - void appendToXML(pugi::xml_node parent, bool ignoreDefaults = false) const; + static MetaDataList createFromXML(MetaDataListType type, pugi::xml_node node, const boost::filesystem::path& relativeTo); + void appendToXML(pugi::xml_node parent, bool ignoreDefaults, const boost::filesystem::path& relativeTo) const; MetaDataList(MetaDataListType type); diff --git a/src/Util.cpp b/src/Util.cpp index acff2d28a..efc805ae0 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -1,6 +1,8 @@ #include "Util.h" -#include #include "resources/ResourceManager.h" +#include "platform.h" + +namespace fs = boost::filesystem; std::string strToUpper(const char* from) { @@ -70,3 +72,102 @@ std::string getCanonicalPath(const std::string& path) return boost::filesystem::canonical(path).generic_string(); } + +// expands "./my/path.sfc" to "[relativeTo]/my/path.sfc" +// if allowHome is true, also expands "~/my/path.sfc" to "/home/pi/my/path.sfc" +fs::path resolvePath(const fs::path& path, const fs::path& relativeTo, bool allowHome) +{ + // nothing here + if(path.begin() == path.end()) + return path; + + if(*path.begin() == ".") + { + fs::path ret = relativeTo; + for(auto it = ++path.begin(); it != path.end(); ++it) + ret /= *it; + return ret; + } + + if(allowHome && *path.begin() == "~") + { + fs::path ret = getHomePath(); + for(auto it = ++path.begin(); it != path.end(); ++it) + ret /= *it; + return ret; + } + + return path; +} + +// example: removeCommonPath("/home/pi/roms/nes/foo/bar.nes", "/home/pi/roms/nes/") returns "foo/bar.nes" +fs::path removeCommonPath(const fs::path& path, const fs::path& relativeTo, bool& contains) +{ + fs::path p = fs::canonical(path); + fs::path r = fs::canonical(relativeTo); + + if(p.root_path() != r.root_path()) + { + contains = false; + return p; + } + + fs::path result; + + // find point of divergence + auto itr_path = p.begin(); + auto itr_relative_to = r.begin(); + while(*itr_path == *itr_relative_to && itr_path != p.end() && itr_relative_to != r.end()) + { + ++itr_path; + ++itr_relative_to; + } + + if(itr_relative_to != r.end()) + { + contains = false; + return p; + } + + while(itr_path != p.end()) + { + if(*itr_path != fs::path(".")) + result = result / *itr_path; + + ++itr_path; + } + + contains = true; + return result; +} + +// usage: makeRelativePath("/path/to/my/thing.sfc", "/path/to") -> "./my/thing.sfc" +// usage: makeRelativePath("/home/pi/my/thing.sfc", "/path/to", true) -> "~/my/thing.sfc" +fs::path makeRelativePath(const fs::path& path, const fs::path& relativeTo, bool allowHome) +{ + bool contains = false; + + fs::path ret = removeCommonPath(path, relativeTo, contains); + if(contains) + { + // success + ret = "." / ret; + return ret; + } + + if(allowHome) + { + contains = false; + std::string homePath = getHomePath(); + ret = removeCommonPath(path, homePath, contains); + if(contains) + { + // success + ret = "~" / ret; + return ret; + } + } + + // nothing could be resolved + return path; +} diff --git a/src/Util.h b/src/Util.h index 254520619..d72677839 100644 --- a/src/Util.h +++ b/src/Util.h @@ -1,5 +1,6 @@ #include #include +#include std::string strToUpper(const char* from); std::string& strToUpper(std::string& str); @@ -13,4 +14,15 @@ Eigen::Vector2f roundVector(const Eigen::Vector2f& vec); float round(float num); -std::string getCanonicalPath(const std::string& str); \ No newline at end of file +std::string getCanonicalPath(const std::string& str); + +// example: removeCommonPath("/home/pi/roms/nes/foo/bar.nes", "/home/pi/roms/nes/") returns "foo/bar.nes" +boost::filesystem::path removeCommonPath(const boost::filesystem::path& path, const boost::filesystem::path& relativeTo, bool& contains); + +// usage: makeRelativePath("/path/to/my/thing.sfc", "/path/to") -> "./my/thing.sfc" +// usage: makeRelativePath("/home/pi/my/thing.sfc", "/path/to", true) -> "~/my/thing.sfc" +boost::filesystem::path makeRelativePath(const boost::filesystem::path& path, const boost::filesystem::path& relativeTo, bool allowHome); + +// expands "./my/path.sfc" to "[relativeTo]/my/path.sfc" +// if allowHome is true, also expands "~/my/path.sfc" to "/home/pi/my/path.sfc" +boost::filesystem::path resolvePath(const boost::filesystem::path& path, const boost::filesystem::path& relativeTo, bool allowHome);