From 8989d75720f5d67ca3f1505d5c06ce7030c76c3a Mon Sep 17 00:00:00 2001 From: Joseph Walton-Rivers <joseph@walton-rivers.uk> Date: Sun, 26 Jun 2022 15:45:03 +0100 Subject: [PATCH] more standardized path detection --- demo/demo/main.cpp | 6 +- fggl/platform/CMakeLists.txt | 2 + fggl/platform/fallback/paths.cpp | 65 +++++++++++ fggl/platform/linux/CMakeLists.txt | 7 +- fggl/platform/linux/paths.cpp | 135 +++++++++++++++++++++++ include/fggl/data/storage.hpp | 41 ++++--- include/fggl/platform/fallback/paths.hpp | 56 ++++++++++ include/fggl/platform/linux/paths.hpp | 33 +++--- include/fggl/platform/paths.hpp | 31 ++++++ 9 files changed, 342 insertions(+), 34 deletions(-) create mode 100644 fggl/platform/fallback/paths.cpp create mode 100644 fggl/platform/linux/paths.cpp create mode 100644 include/fggl/platform/fallback/paths.hpp create mode 100644 include/fggl/platform/paths.hpp diff --git a/demo/demo/main.cpp b/demo/demo/main.cpp index 2e68921..6e36d92 100644 --- a/demo/demo/main.cpp +++ b/demo/demo/main.cpp @@ -20,7 +20,6 @@ #include <filesystem> #include <iostream> -#include <memory> #include "fggl/app.hpp" #include "fggl/audio/openal/audio.hpp" @@ -32,6 +31,7 @@ #include "fggl/data/storage.hpp" #include "fggl/util/service.hpp" +#include "fggl/platform/paths.hpp" #include "fggl/ecs3/types.hpp" #include "fggl/phys/bullet/bullet.hpp" @@ -72,9 +72,11 @@ static void test_atlas_api() { } static void setup_service_locators(fggl::util::ServiceLocator& locator) { + auto pathConfig = fggl::platform::calc_engine_paths("fggl-demo"); + // FIXME: janky API(s) auto inputs = locator.supply<fggl::input::Input>(std::make_shared<fggl::input::Input>()); - auto storage = locator.supply<fggl::data::Storage>(std::make_shared<fggl::data::Storage>()); + auto storage = locator.supply<fggl::data::Storage>(std::make_shared<fggl::data::Storage>(pathConfig)); locator.supply<fggl::gui::FontLibrary>(std::make_shared<fggl::gui::FontLibrary>()); locator.supply<fggl::ecs3::TypeRegistry>(std::make_shared<fggl::ecs3::TypeRegistry>()); diff --git a/fggl/platform/CMakeLists.txt b/fggl/platform/CMakeLists.txt index 5895d7d..87c143a 100644 --- a/fggl/platform/CMakeLists.txt +++ b/fggl/platform/CMakeLists.txt @@ -1,3 +1,5 @@ if ( CMAKE_SYSTEM_NAME MATCHES "Linux" ) add_subdirectory(linux) +else() + add_subdirectory(fallback) endif() \ No newline at end of file diff --git a/fggl/platform/fallback/paths.cpp b/fggl/platform/fallback/paths.cpp new file mode 100644 index 0000000..8dca3fc --- /dev/null +++ b/fggl/platform/fallback/paths.cpp @@ -0,0 +1,65 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 26/06/22. +// + +#include <cstdlib> + +#include "fggl/platform/fallback/paths.hpp" + +namespace fggl::platform { + + inline static std::filesystem::path get_user_path(const char* env, const char* fallback) { + const char* path = std::getenv(env); + if (path != nullptr) { + return {path}; + } + return std::filesystem::current_path() / fallback; + } + + EnginePaths calc_engine_paths(const char* base) { + return EnginePaths { + get_user_path(ENV_USER_CONFIG, DEFAULT_USER_CONFIG) / base, + get_user_path(ENV_USER_DATA, DEFAULT_USER_DATA) / base, + std::filesystem::temp_directory_path() / base + }; + } + + std::filesystem::path locate_data(const EnginePaths &paths, const std::filesystem::path &relPath) { + auto userPath = paths.userData / relPath; + if ( std::filesystem::exists(userPath) ) { + return userPath; + } + + // if debug mode, try CWD as well. + auto debugPath = std::filesystem::current_path() / "data" / relPath; + if ( std::filesystem::exists(debugPath) ) { + return debugPath; + } + + // if the file existed, it should exist in the user space + return userPath; + } + + std::filesystem::path locate_config(const EnginePaths &paths, const std::filesystem::path &relPath) { + return paths.userConfig / relPath; + } + + std::filesystem::path locate_cache(const EnginePaths &paths, const std::filesystem::path &relPath) { + return paths.userCache / relPath; + } + +} // namespace fggl::platform diff --git a/fggl/platform/linux/CMakeLists.txt b/fggl/platform/linux/CMakeLists.txt index baafc29..cc1d2b0 100644 --- a/fggl/platform/linux/CMakeLists.txt +++ b/fggl/platform/linux/CMakeLists.txt @@ -1,7 +1,8 @@ -find_package(Fontconfig) -target_link_libraries(fggl PRIVATE Fontconfig::Fontconfig) +#find_package(Fontconfig) +#target_link_libraries(fggl PRIVATE Fontconfig::Fontconfig) target_sources( fggl PRIVATE - fonts.cpp + # fonts.cpp + paths.cpp ) \ No newline at end of file diff --git a/fggl/platform/linux/paths.cpp b/fggl/platform/linux/paths.cpp new file mode 100644 index 0000000..2d3564d --- /dev/null +++ b/fggl/platform/linux/paths.cpp @@ -0,0 +1,135 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 26/06/22. +// + +#include <cstdlib> + +#include "fggl/platform/linux/paths.hpp" + +namespace fggl::platform { + + inline static std::filesystem::path get_user_path(const char* env, const char* fallback) { + const char* path = std::getenv(env); + if (path != nullptr) { + return {path}; + } + return {fallback}; + } + + static std::vector<std::filesystem::path> get_path_list(const char* env) { + const char* pathList = std::getenv(env); + std::vector<std::filesystem::path> paths; + if (pathList) { + std::string pathListStr(pathList); + std::string::size_type pos = 0; + while (pos < pathListStr.size()) { + std::string::size_type nextPos = pathListStr.find(':', pos); + if (nextPos == std::string::npos) { + nextPos = pathListStr.size(); + } + std::string path = pathListStr.substr(pos, nextPos - pos); + paths.push_back(std::filesystem::path(path)); + pos = nextPos + 1; + } + } + return paths; + } + + EnginePaths calc_engine_paths(const char* base) { + auto dataDirs = get_path_list(ENV_DATA_DIRS); + if ( dataDirs.empty() ) { + for ( const auto& defaultDir : DEFAULT_DATA_DIRS ) { + dataDirs.push_back(std::filesystem::path(defaultDir) / base ); + } + } + + auto configDirs = get_path_list(ENV_CONFIG_DIRS); + if ( configDirs.empty() ) { + for ( const auto& defaultDir : DEFAULT_CONFIG_DIRS ) { + configDirs.push_back(std::filesystem::path(defaultDir) / base ); + } + } + + return EnginePaths { + get_user_path(ENV_USER_CONFIG, DEFAULT_USER_CONFIG) / base, + get_user_path(ENV_USER_DATA, DEFAULT_USER_DATA) / base, + get_user_path(ENV_USER_CACHE, DEFAULT_USER_CACHE) / base, + dataDirs, + configDirs + }; + } + + std::filesystem::path locate_data(const EnginePaths &paths, const std::filesystem::path &relPath) { + auto userPath = paths.userData / relPath; + if ( std::filesystem::exists(userPath) ) { + return userPath; + } + + // check system paths + for ( const auto& path : paths.dataDirs ) { + auto fullPath = path / relPath; + if ( std::filesystem::exists(fullPath) ) { + return fullPath; + } + } + + // if debug mode, try CWD as well. + auto debugPath = std::filesystem::current_path() / "data" / relPath; + if ( std::filesystem::exists(debugPath) ) { + return debugPath; + } + + // if the file existed, it shoudl exist in the user space + return userPath; + } + + std::filesystem::path locate_config(const EnginePaths &paths, const std::filesystem::path &relPath) { + auto userPath = paths.userConfig / relPath; + if ( std::filesystem::exists(userPath) ) { + return userPath; + } + + // check system paths + for ( const auto& path : paths.configDirs ) { + auto fullPath = path / relPath; + if ( std::filesystem::exists(fullPath) ) { + return fullPath; + } + } + + // if the file existed, it should exist in the user space + return userPath; + } + + std::filesystem::path locate_cache(const EnginePaths &paths, const std::filesystem::path &relPath) { + auto userPath = paths.userCache / relPath; + if ( std::filesystem::exists(userPath) ) { + return userPath; + } + + // check system paths + for ( const auto& path : paths.configDirs ) { + auto fullPath = path / relPath; + if ( std::filesystem::exists(fullPath) ) { + return fullPath; + } + } + + // if the file existed, it should exist in the user space + return userPath; + } +} // namespace fggl::platform::linux diff --git a/include/fggl/data/storage.hpp b/include/fggl/data/storage.hpp index 51b5b93..f52dad1 100644 --- a/include/fggl/data/storage.hpp +++ b/include/fggl/data/storage.hpp @@ -18,8 +18,12 @@ #include <iostream> #include <string> #include <filesystem> +#include <utility> #include <vector> +#include "fggl/debug/logging.hpp" +#include "fggl/platform/paths.hpp" + namespace fggl::data { template<typename T> @@ -28,16 +32,18 @@ namespace fggl::data { template<typename T> bool fggl_deserialize(std::filesystem::path &data, T *out); - enum StorageType { Data, User, Cache }; + enum StorageType { Data, Config, Cache }; class Storage { public: + Storage( fggl::platform::EnginePaths paths ) : m_paths(std::move( paths )) {} + template<typename T> bool load(StorageType pool, const std::string &name, T *out) { auto path = resolvePath(pool, name); if (!std::filesystem::exists(path)) { - std::cerr << "Path " << path << " does not exist!" << std::endl; + debug::log(debug::Level::warning, "Attempted to load '{}', but it did not exist", path.string()); return false; } return fggl_deserialize<T>(path, out); @@ -59,28 +65,35 @@ namespace fggl::data { template<typename T> void save(StorageType pool, const std::string &name, const T *out) { - auto path = resolvePath(pool, name); + auto path = resolvePath(pool, name, true); fggl_serialize<T>(path, out); } - inline std::filesystem::path resolvePath(StorageType pool, const std::string &name) { + inline std::filesystem::path resolvePath(StorageType pool, const std::string &name, bool createParents = false) { std::filesystem::path path; - switch (pool) { - case Data: path = std::filesystem::current_path() / "data"; - break; - case User: path = "./user-data/"; - break; - case Cache: path = "/tmp/fggl/"; - break; + case Data: + path = fggl::platform::locate_data(m_paths, name); + break; + case Config: + path = fggl::platform::locate_config(m_paths, name); + break; + case Cache: + path = fggl::platform::locate_cache(m_paths, name); + break; } - if (!std::filesystem::exists(path)) { - std::filesystem::create_directories(path); + if ( createParents ){ + if ( !std::filesystem::exists(path.parent_path()) ) { + std::filesystem::create_directories(path.parent_path()); + } } - return path / name; + return path; } + + private: + fggl::platform::EnginePaths m_paths; }; } diff --git a/include/fggl/platform/fallback/paths.hpp b/include/fggl/platform/fallback/paths.hpp new file mode 100644 index 0000000..9a74a9b --- /dev/null +++ b/include/fggl/platform/fallback/paths.hpp @@ -0,0 +1,56 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Low-Level Linux-specific path management +// Created by webpigeon on 23/06/22. +// see https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html +// + +#ifndef FGGL_PLATFORM_LINUX_PATHS_HPP +#define FGGL_PLATFORM_LINUX_PATHS_HPP + +#include "fggl/platform/paths.hpp" + +#include <filesystem> +#include <array> + +#include <vector> + +namespace fggl::platform { + + constexpr const char* ENV_USER_CONFIG = "FGGL_CONFIG_HOME"; + constexpr const char* ENV_USER_DATA = "FGGL_DATA_HOME"; + constexpr const char* ENV_USER_CACHE = "FGGL_CACHE_HOME"; + + // fallback user paths defined in the XDG spec + constexpr const char* DEFAULT_USER_CONFIG = "user_config"; + constexpr const char* DEFAULT_USER_DATA = "user_data"; + + struct EnginePaths { + std::filesystem::path userConfig; + std::filesystem::path userData; + std::filesystem::path userCache; + }; + + EnginePaths calc_engine_paths(const char* base); + + // search routines for finding data and configuration files + std::filesystem::path locate_data(const EnginePaths& paths, const std::filesystem::path& relPath); + std::filesystem::path locate_config(const EnginePaths& paths, const std::filesystem::path& relPath); + std::filesystem::path locate_cache(const EnginePaths& paths, const std::filesystem::path& relPath); + +} + +#endif //FGGL_PLATFORM_LINUX_PATHS_HPP diff --git a/include/fggl/platform/linux/paths.hpp b/include/fggl/platform/linux/paths.hpp index 6a66d5f..2c2b783 100644 --- a/include/fggl/platform/linux/paths.hpp +++ b/include/fggl/platform/linux/paths.hpp @@ -21,13 +21,17 @@ #ifndef FGGL_PLATFORM_LINUX_PATHS_HPP #define FGGL_PLATFORM_LINUX_PATHS_HPP +#include "fggl/platform/paths.hpp" + #include <filesystem> #include <array> -namespace fggl::platform::Linux { +#include <vector> + +namespace fggl::platform { constexpr const char* ENV_USER_CONFIG = "XDG_CONFIG_HOME"; - constexpr const char* EVN_USER_DATA = "XDG_DATA_HOME"; + constexpr const char* ENV_USER_DATA = "XDG_DATA_HOME"; constexpr const char* ENV_USER_CACHE = "XDG_CACHE_HOME"; constexpr const char* ENV_DATA_DIRS = "XDG_DATA_DIRS"; @@ -42,22 +46,21 @@ namespace fggl::platform::Linux { constexpr const std::array<const char*, 2> DEFAULT_DATA_DIRS = {"/usr/local/share/", "/usr/share/"}; constexpr const std::array<const char*, 1> DEFAULT_CONFIG_DIRS = {"/etc/xdg"}; - // search routines for finding data and configuration files - std::filesystem::path locate_data(const std::string& base, const std::filesystem::path& relPath); - std::filesystem::path locate_config(const std::string& base, const std::filesystem::path& relPath); + struct EnginePaths { + std::filesystem::path userConfig; + std::filesystem::path userData; + std::filesystem::path userCache; + std::vector<std::filesystem::path> dataDirs; + std::vector<std::filesystem::path> configDirs; + }; - // helper functions for getting file pointers - FILE* open_user_config(std::string& base, const std::filesystem::path& relPath); - FILE* open_user_config_rw(std::string& base, const std::filesystem::path& relPath, bool create = true); + EnginePaths calc_engine_paths(const char* base); - FILE* open_user_cache(std::string& base, const std::filesystem::path& relPath); - FILE* open_user_cache_rw(std::string& base, const std::filesystem::path& relPath, bool create = true); - - FILE* open_user_data(std::string& base, const std::filesystem::path& relPath); - FILE* open_user_data_rw(std::string& base, const std::filesystem::path& relPath, bool create = true); + // search routines for finding data and configuration files + std::filesystem::path locate_data(const EnginePaths& paths, const std::filesystem::path& relPath); + std::filesystem::path locate_config(const EnginePaths& paths, const std::filesystem::path& relPath); + std::filesystem::path locate_cache(const EnginePaths& paths, const std::filesystem::path& relPath); - // game data is always read only - FILE* open_game_data(std::string& base, const std::filesystem::path& relPath); } #endif //FGGL_PLATFORM_LINUX_PATHS_HPP diff --git a/include/fggl/platform/paths.hpp b/include/fggl/platform/paths.hpp new file mode 100644 index 0000000..5265b53 --- /dev/null +++ b/include/fggl/platform/paths.hpp @@ -0,0 +1,31 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 26/06/22. +// + +#ifndef FGGL_PLATFORM_PATHS_HPP +#define FGGL_PLATFORM_PATHS_HPP + +#include <filesystem> +#include <vector> + +#ifdef __linux__ + #include "fggl/platform/linux/paths.hpp" +#else + #include "fggl/platform/fallback/paths.hpp" +#endif + +#endif //FGGL_PLATFORM_PATHS_HPP -- GitLab