aboutsummaryrefslogtreecommitdiff
path: root/src/client/settings.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/settings.cc')
-rw-r--r--src/client/settings.cc139
1 files changed, 139 insertions, 0 deletions
diff --git a/src/client/settings.cc b/src/client/settings.cc
new file mode 100644
index 0000000..8e2d0c9
--- /dev/null
+++ b/src/client/settings.cc
@@ -0,0 +1,139 @@
+#include "client/settings.hh"
+
+namespace {
+const std::string home_dir = getenv("HOME");
+const char* const config_dir = "/.config/blockgame_linux/";
+const char* const config_name = "blockgame_linux.conf";
+} // namespace
+
+namespace client {
+namespace settings {
+
+static std::string get_settings_file() noexcept {
+ std::filesystem::create_directories(home_dir + ::config_dir);
+ std::ifstream in{home_dir + ::config_dir + config_name,
+ std::fstream::in | std::fstream::app};
+
+ // this must end with a newline, IT IS NOT UP FOR NEGOTIATION
+ const std::string file{std::istreambuf_iterator<char>(in),
+ std::istreambuf_iterator<char>()};
+ return file.ends_with('\n') ? file : file + '\n';
+}
+
+static void set_settings_file(const std::string_view contents) noexcept {
+ std::filesystem::create_directories(home_dir + ::config_dir);
+ std::ofstream out{home_dir + ::config_dir + ::config_name,
+ std::fstream::out | std::fstream::trunc};
+ out << contents;
+}
+
+// Settings maps subkeys to std::string values (ie, "fov" -> "100.0"f).
+// Heading maps setting to headings (ie, "camera -> {map with fov, 100.0f}).
+using value_map = std::unordered_map<std::string, std::string>;
+using setting_map = std::unordered_map<std::string, value_map>;
+static setting_map& get_settings() noexcept {
+ static setting_map ret = []() -> setting_map {
+ setting_map settings{};
+
+ std::stringstream ss{get_settings_file()};
+ std::string heading = "default";
+ for (std::string line; std::getline(ss, line);) {
+ if (line.empty()) {
+ continue;
+ }
+
+ if (line.starts_with('[') && line.ends_with(']')) {
+ heading = std::string{std::begin(line) + 1, std::end(line) - 1};
+ continue;
+ }
+
+ const size_t split_pos = line.find_first_of(':');
+
+ if (split_pos == std::string::npos) {
+ shared::print::warn("client: failed to parse line in settings "
+ "file, consider manual intervention\n");
+ continue;
+ }
+
+ const std::string key{std::begin(line),
+ std::begin(line) +
+ static_cast<long>(split_pos)};
+ const std::string value{std::begin(line) +
+ static_cast<long>(split_pos) + 1,
+ std::end(line)};
+
+ value_map& values = settings[heading];
+
+ values.emplace(key, value);
+ }
+ return settings;
+ }();
+
+ return ret;
+}
+
+// TODO fix this bit of code duplication
+std::string
+get_setting_str(const std::pair<const std::string&, const std::string&> loc,
+ const std::string default_value) noexcept {
+ const auto& [heading, name] = loc;
+
+ setting_map& settings = get_settings();
+ value_map& values = settings[heading];
+
+ const auto find_it = values.find(name);
+ if (find_it == std::end(values)) {
+ values.emplace(name, default_value);
+ return default_value;
+ }
+ return find_it->second;
+}
+
+void set_setting_str(
+ const std::pair<const std::string&, const std::string&> loc,
+ const std::string& value) noexcept {
+ const auto& [heading, name] = loc;
+
+ setting_map& settings = get_settings();
+ value_map& values = settings[heading];
+
+ const auto find_it = values.find(name);
+ if (find_it == std::end(values)) {
+ values.emplace(name, value);
+ return;
+ }
+ find_it->second = value;
+}
+
+void save() noexcept {
+ std::string contents;
+
+ const setting_map& settings = get_settings();
+
+ // We alphabetically order the headings, and the key:values between them.
+ std::vector<std::string> headings;
+ std::ranges::transform(std::begin(settings), std::end(settings),
+ std::back_inserter(headings),
+ [](const auto& map) { return map.first; });
+ std::ranges::sort(headings);
+
+ for (const auto& heading : headings) {
+ contents.append('[' + heading + "]\n");
+
+ const value_map& values = settings.find(heading)->second;
+ std::vector<std::string> keys;
+ std::ranges::transform(
+ std::begin(values), std::end(values), std::back_inserter(keys),
+ [](const auto& key_val) { return key_val.first; });
+ std::ranges::sort(keys);
+
+ for (const auto& key : keys) {
+ contents.append(key + ':' + values.find(key)->second + '\n');
+ }
+ }
+
+ set_settings_file(contents);
+}
+
+} // namespace settings
+} // namespace client