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