diff --git a/library/phx/resources/resource_utils.cpp b/library/phx/resources/resource_utils.cpp index e8e82fcb5356defa3988ae2696281f75a7054cc9..3804fae89b37f689c34763a38e47933942da29df 100644 --- a/library/phx/resources/resource_utils.cpp +++ b/library/phx/resources/resource_utils.cpp @@ -22,13 +22,41 @@ #include "phx/resources/resource_utils.hpp" #include <algorithm> +#include <cassert> +#include <fstream> #include <locale> #include <string> +#include <vector> + +#include "boost/filesystem.hpp" #include "phx/resources/resource_manager.hpp" #include "phx/resources_path.hpp" namespace phx { +namespace { +std::vector<std::string> default_resource_search_paths = { + {std::string("./"), resources_root}}; +} + +std::vector<std::string> ResourceUtils::resource_search_paths_{ + default_resource_search_paths}; + +void ResourceUtils::AddResourceSearchPath(const std::string& path_to_add) { + assert(!path_to_add.empty()); + auto p = (path_to_add.back() == '/' ? path_to_add + : path_to_add + std::string("/")); + resource_search_paths_.push_back(p); +} + +void ResourceUtils::ResetResourceSearchPath() { + resource_search_paths_ = default_resource_search_paths; +} + +std::vector<std::string> ResourceUtils::GetResourceSearchPath() { + return resource_search_paths_; +} + ResourceDeclaration ResourceUtils::DeclarationFromFile( const std::string& file_name, nlohmann::json additional_info /*= {}*/, bool absolute_path /*= false*/) { @@ -36,7 +64,7 @@ ResourceDeclaration ResourceUtils::DeclarationFromFile( if (absolute_path) { declaration["file_name"] = file_name; } else { - declaration["file_name"] = resources_root + file_name; + declaration["file_name"] = FindFileInSearchPath(file_name); } for (nlohmann::json::iterator it = additional_info.begin(); it != additional_info.end(); ++it) { @@ -52,4 +80,12 @@ std::string ResourceUtils::ExtractFileExtension(const std::string& file_name) { return extension; } +std::string ResourceUtils::FindFileInSearchPath(const std::string& file_name) { + for (auto& entry : resource_search_paths_) { + auto full_name = entry + file_name; + if (boost::filesystem::exists(full_name.c_str())) return full_name; + } + return ""; +} + } // namespace phx diff --git a/library/phx/resources/resource_utils.hpp b/library/phx/resources/resource_utils.hpp index 8f18bc1fa3c17ae0db1dc93a83ed4aebab8d8236..95cba49f82f2601230288d4e65a948b376d436f0 100644 --- a/library/phx/resources/resource_utils.hpp +++ b/library/phx/resources/resource_utils.hpp @@ -26,25 +26,35 @@ #include <map> #include <memory> #include <string> +#include <vector> #include "json.hpp" #include "phx/core/logger.hpp" +#include "phx/export.hpp" #include "phx/resources/resource_declaration.hpp" #include "phx/resources/resource_load_strategy.hpp" #include "phx/resources/resource_manager.hpp" #include "phx/resources/resource_pointer.hpp" #include "phx/utility/aspects/singleton.hpp" -#include "phx/export.hpp" namespace phx { /** * The ResourceUtils class contains static convenience methods to simplify - * interactions with the resource system. It should never hold any state. + * interactions with the resource system. It also holds a user configurable + * resource search path. When operating on a relative file name, it will + * search all entries in the search path and return/load the first file that + * matches the given file name. Per default, the resource search path + * contains the local directory and phx::resources_root. */ class PHOENIX_EXPORT ResourceUtils final { public: + static void AddResourceSearchPath(const std::string& path_to_add); + static void ResetResourceSearchPath(); + static std::vector<std::string> GetResourceSearchPath(); + static std::string ExtractFileExtension(const std::string& file_name); + static ResourceDeclaration DeclarationFromFile( const std::string& file_name, nlohmann::json additional_info = {}, bool absolute_path = false); @@ -59,6 +69,11 @@ class PHOENIX_EXPORT ResourceUtils final { res_ptr.Load(); return res_ptr; } + + private: + static std::string FindFileInSearchPath(const std::string& file_name); + + static std::vector<std::string> resource_search_paths_; }; } // namespace phx diff --git a/tests/src/test_resource_utils.cpp b/tests/src/test_resource_utils.cpp index d2b0a9bb82b807735219581322600fec1e1c6e26..1da8fcab9e8789cba8a6a11d1a0261b45dc8aa36 100644 --- a/tests/src/test_resource_utils.cpp +++ b/tests/src/test_resource_utils.cpp @@ -20,15 +20,27 @@ // limitations under the License. //------------------------------------------------------------------------------ +#include <fstream> #include <string> #include "catch/catch.hpp" -#include "phx/resources/types/image.hpp" #include "phx/resources/resource_declaration.hpp" #include "phx/resources/resource_utils.hpp" +#include "phx/resources/types/image.hpp" #include "phx/resources_path.hpp" +namespace { +void CreateDummyFile(const std::string &file_name) { + std::fstream out_file(file_name.c_str(), std::ios::out); + // force fail test if file could not be created + REQUIRE(out_file.is_open()); +} +void DeleteDummyFile(const std::string &file_name) { + std::remove(file_name.c_str()); +} +} // namespace + SCENARIO("Resource utils can extract file extensions.", "[phx][phx::ResourceUtils]") { GIVEN("A file name") { @@ -56,17 +68,19 @@ SCENARIO("Resource utils can declarare a file resource.", GIVEN("A file name with absolute path") { std::string file_name{"C:/some_path/to/file.34.ext"}; - THEN("It can declare it") { + THEN("we get a declaration with a valid (unmodified) file name") { phx::ResourceDeclaration declaration = phx::ResourceUtils::DeclarationFromFile(file_name, {}, true); REQUIRE(declaration["TYPE"] == ".ext"); REQUIRE(declaration["file_name"] == file_name); } } - GIVEN("A file name with relative path") { - std::string file_name{"file.png"}; + GIVEN("A file name and a search path") { + std::string file_name{"textures/splash_progress.png"}; - THEN("It can declare it") { + THEN( + "we get a declaration with a valid file name that has been pre-pended " + "by the first hit in the search path") { phx::ResourceDeclaration declaration = phx::ResourceUtils::DeclarationFromFile(file_name); REQUIRE(declaration["TYPE"] == ".png"); @@ -74,11 +88,11 @@ SCENARIO("Resource utils can declarare a file resource.", } } GIVEN("Additional key, value pairs are provided") { - std::string file_name{"file.png"}; + std::string file_name{"textures/splash_progress.png"}; std::string key = "some_key"; int value = 42; - THEN("these are in the resulting declaration") { + THEN("the resulting declaration contains them") { phx::ResourceDeclaration declaration = phx::ResourceUtils::DeclarationFromFile(file_name, {{key, value}}, false); @@ -87,9 +101,26 @@ SCENARIO("Resource utils can declarare a file resource.", } } +SCENARIO("Resource utils find file resource outside the original search path", + "[phx][phx::ResourceUtils]") { + GIVEN("A file outside the original resource tree") { + std::string dummy_file = "dummy.txt"; + std::string offset_search_path = "../"; + ::CreateDummyFile(offset_search_path + dummy_file); + + phx::ResourceUtils::AddResourceSearchPath(offset_search_path); + THEN("the file is found and a valid declaration is returned") { + phx::ResourceDeclaration declaration = + phx::ResourceUtils::DeclarationFromFile(dummy_file, {}, false); + CHECK(declaration["file_name"] == offset_search_path + dummy_file); + ::DeleteDummyFile(offset_search_path + dummy_file); + } + } +} + SCENARIO("Resource utils can load a file resource.", "[phx][phx::ResourceUtils]") { - GIVEN("A file name") { + GIVEN("A file name within the original resource tree") { std::string file_name{"textures/splash_progress.png"}; THEN("It can load it") {