F4MP/tiltedcode/Code/components/console/IniSettingsProvider.cpp

140 lines
5.9 KiB
C++
Raw Normal View History

// Copyright (C) 2022 TiltedPhoques SRL.
// For licensing information see LICENSE at the root of this distribution.
#include <base/Check.h>
#include <base/simpleini/SimpleIni.h>
#include <console/IniSettingsProvider.h>
#include <console/ConsoleUtils.h>
#include <console/Setting.h>
#include <console/ConsoleRegistry.h>
#include <charconv>
#include <fstream>
#include <TiltedCore/Filesystem.hpp>
namespace Console
{
namespace
{
template <typename T, typename TVal> static SI_Error SetIniValue(CSimpleIni& ini, const T* a_pSection, const T* a_pKey, const TVal a_nValue, const T* a_pComment = nullptr)
{
char szValue[64]{};
std::to_chars(szValue, szValue + sizeof(szValue), a_nValue);
// convert to output text
T szOutput[256];
CSimpleIni::Converter c(ini.IsUnicode());
c.ConvertFromStore(szValue, std::strlen(szValue) + 1, szOutput, sizeof(szOutput) / sizeof(T));
return ini.AddEntry(a_pSection, a_pKey, szOutput, a_pComment, false, true);
}
template <typename T, typename TVal> TVal GetIniValue(CSimpleIni& ini, const T* a_pSection, const T* a_pKey, const TVal a_nDefault, bool& a_pHasMultiple)
{
const T* pszValue = ini.GetValue(a_pSection, a_pKey, nullptr, &a_pHasMultiple);
if (!pszValue || !*pszValue)
return a_nDefault;
// convert to UTF-8/MBCS which for a numeric value will be the same as ASCII
char szValue[64]{};
CSimpleIni::Converter c(ini.IsUnicode());
if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue)))
{
return a_nDefault;
}
return ConvertStringValue<TVal>(szValue, a_nDefault);
}
std::pair<std::string, std::string> SplitSection(const SettingBase* setting)
{
std::string nameProperty(setting->name);
size_t pos = nameProperty.find_first_of(':');
std::string section = pos == std::string_view::npos ? "general" : nameProperty.substr(0, pos);
auto name = &nameProperty[pos + 1];
return {section, name};
}
} // namespace
void SaveSettingsToIni(ConsoleRegistry& aReg, const std::filesystem::path& aPath)
{
CSimpleIni ini;
SI_Error error{SI_Error::SI_OK};
aReg.ForAllSettings(
[&](SettingBase* setting)
{
auto items = SplitSection(setting);
auto& section = items.first;
auto& name = items.second;
switch (setting->type)
{
case SettingBase::Type::kBoolean: error = ini.SetBoolValue(section.c_str(), name.c_str(), setting->data.as_boolean); break;
case SettingBase::Type::kInt: error = SetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_int32); break;
case SettingBase::Type::kUInt: error = SetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_uint32); break;
case SettingBase::Type::kInt64: error = SetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_int64); break;
case SettingBase::Type::kUInt64: error = SetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_uint64); break;
case SettingBase::Type::kFloat: error = SetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_float); break;
case SettingBase::Type::kString: error = ini.SetValue(section.c_str(), name.c_str(), setting->c_str()); break;
default: BASE_ASSERT(true, "SaveSettingsToIni(): Unknown type index for {}", setting->name); break;
}
if (error != SI_Error::SI_OK)
{
BASE_ASSERT(true, "Failed to write ini setting.");
}
});
std::string buf;
error = ini.Save(buf, true);
BASE_ASSERT(error == SI_Error::SI_OK, "Saving the ini failed");
// TODO(Vince): eventually we shall burn the ini library
TiltedPhoques::SaveFile(aPath, TiltedPhoques::String(buf));
}
void LoadSettingsFromIni(ConsoleRegistry& aReg, const std::filesystem::path& aPath)
{
CSimpleIni ini;
{
auto buf = TiltedPhoques::LoadFile(aPath);
BASE_ASSERT(ini.LoadData(buf.c_str()) == SI_Error::SI_OK, "Failed to load ini data");
}
aReg.ForAllSettings(
[&](SettingBase* setting)
{
auto items = SplitSection(setting);
auto& section = items.first;
auto& name = items.second;
bool multiMatch = false;
switch (setting->type)
{
// With scalar types we don't expect the size to change...
// However, they should all call StoreValue in the future.
case SettingBase::Type::kBoolean: setting->data.as_boolean = ini.GetBoolValue(section.c_str(), name.c_str(), setting->data.as_boolean); break;
case SettingBase::Type::kInt: setting->data.as_int32 = GetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_int32, multiMatch); break;
case SettingBase::Type::kUInt: setting->data.as_uint32 = GetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_uint32, multiMatch); break;
case SettingBase::Type::kInt64: setting->data.as_int64 = GetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_int64, multiMatch); break;
case SettingBase::Type::kUInt64: setting->data.as_uint64 = GetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_uint64, multiMatch); break;
case SettingBase::Type::kFloat: setting->data.as_float = GetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_float, multiMatch); break;
// Strings however are a special case, as it has its own allocator.
case SettingBase::Type::kString:
{
// This is not at all how i want it to be :/
const char* c = ini.GetValue(section.c_str(), name.c_str(), setting->c_str());
static_cast<StringSetting*>(setting)->StoreValue(*setting, c);
break;
}
default: BASE_ASSERT(true, "LoadSettingsFromIni(): Unknown type index for {}", setting->name); break;
}
});
}
} // namespace Console