ini_settings_interface.cpp (10670B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "ini_settings_interface.h" 5 6 #include "common/error.h" 7 #include "common/file_system.h" 8 #include "common/log.h" 9 #include "common/path.h" 10 #include "common/string_util.h" 11 12 #include <algorithm> 13 #include <iterator> 14 #include <mutex> 15 16 Log_SetChannel(INISettingsInterface); 17 18 #ifdef _WIN32 19 #include <io.h> // _mktemp_s 20 #else 21 #include <stdlib.h> // mktemp 22 #include <unistd.h> 23 #endif 24 25 // To prevent races between saving and loading settings, particularly with game settings, 26 // we only allow one ini to be parsed at any point in time. 27 static std::mutex s_ini_load_save_mutex; 28 29 static std::FILE* GetTemporaryFile(std::string* temporary_filename, const std::string& original_filename, 30 const char* mode, Error* error) 31 { 32 temporary_filename->clear(); 33 temporary_filename->reserve(original_filename.length() + 8); 34 temporary_filename->append(original_filename); 35 36 #ifdef _WIN32 37 temporary_filename->append(".XXXXXXX"); 38 const errno_t err = _mktemp_s(temporary_filename->data(), temporary_filename->length() + 1); 39 if (err != 0) 40 { 41 Error::SetErrno(error, "_mktemp_s() failed: ", err); 42 return nullptr; 43 } 44 45 return FileSystem::OpenCFile(temporary_filename->c_str(), mode, error); 46 #else 47 temporary_filename->append(".XXXXXX"); 48 const int fd = mkstemp(temporary_filename->data()); 49 if (fd < 0) 50 { 51 Error::SetErrno(error, "mkstemp() failed: ", errno); 52 return nullptr; 53 } 54 55 std::FILE* fp = fdopen(fd, mode); 56 if (!fp) 57 { 58 Error::SetErrno(error, "mkstemp() failed: ", errno); 59 close(fd); 60 return nullptr; 61 } 62 63 return fp; 64 #endif 65 } 66 67 INISettingsInterface::INISettingsInterface(std::string filename) : m_filename(std::move(filename)), m_ini(true, true) 68 { 69 } 70 71 INISettingsInterface::~INISettingsInterface() 72 { 73 if (m_dirty) 74 Save(); 75 } 76 77 bool INISettingsInterface::Load(Error* error /* = nullptr */) 78 { 79 if (m_filename.empty()) 80 { 81 Error::SetStringView(error, "Filename is not set."); 82 return false; 83 } 84 85 std::unique_lock lock(s_ini_load_save_mutex); 86 SI_Error err = SI_FAIL; 87 auto fp = FileSystem::OpenManagedCFile(m_filename.c_str(), "rb", error); 88 if (fp) 89 { 90 err = m_ini.LoadFile(fp.get()); 91 if (err != SI_OK) 92 Error::SetStringFmt(error, "INI LoadFile() failed: {}", static_cast<int>(err)); 93 } 94 95 return (err == SI_OK); 96 } 97 98 bool INISettingsInterface::Save(Error* error /* = nullptr */) 99 { 100 if (m_filename.empty()) 101 { 102 Error::SetStringView(error, "Filename is not set."); 103 return false; 104 } 105 106 std::unique_lock lock(s_ini_load_save_mutex); 107 std::string temp_filename; 108 std::FILE* fp = GetTemporaryFile(&temp_filename, m_filename, "wb", error); 109 SI_Error err = SI_FAIL; 110 if (fp) 111 { 112 err = m_ini.SaveFile(fp, false); 113 std::fclose(fp); 114 115 if (err != SI_OK) 116 { 117 Error::SetStringFmt(error, "INI SaveFile() failed: {}", static_cast<int>(err)); 118 119 // remove temporary file 120 FileSystem::DeleteFile(temp_filename.c_str()); 121 } 122 else if (!FileSystem::RenamePath(temp_filename.c_str(), m_filename.c_str(), error)) 123 { 124 Error::AddPrefixFmt(error, "Failed to rename '{}' to '{}': ", temp_filename, m_filename); 125 FileSystem::DeleteFile(temp_filename.c_str()); 126 return false; 127 } 128 } 129 130 if (err != SI_OK) 131 { 132 WARNING_LOG("Failed to save settings to '{}'.", m_filename); 133 return false; 134 } 135 136 m_dirty = false; 137 return true; 138 } 139 140 void INISettingsInterface::Clear() 141 { 142 m_ini.Reset(); 143 } 144 145 bool INISettingsInterface::IsEmpty() 146 { 147 return (m_ini.GetKeyCount() == 0); 148 } 149 150 bool INISettingsInterface::GetIntValue(const char* section, const char* key, s32* value) const 151 { 152 const char* str_value = m_ini.GetValue(section, key); 153 if (!str_value) 154 return false; 155 156 std::optional<s32> parsed_value = StringUtil::FromChars<s32>(str_value, 10); 157 if (!parsed_value.has_value()) 158 return false; 159 160 *value = parsed_value.value(); 161 return true; 162 } 163 164 bool INISettingsInterface::GetUIntValue(const char* section, const char* key, u32* value) const 165 { 166 const char* str_value = m_ini.GetValue(section, key); 167 if (!str_value) 168 return false; 169 170 std::optional<u32> parsed_value = StringUtil::FromChars<u32>(str_value, 10); 171 if (!parsed_value.has_value()) 172 return false; 173 174 *value = parsed_value.value(); 175 return true; 176 } 177 178 bool INISettingsInterface::GetFloatValue(const char* section, const char* key, float* value) const 179 { 180 const char* str_value = m_ini.GetValue(section, key); 181 if (!str_value) 182 return false; 183 184 std::optional<float> parsed_value = StringUtil::FromChars<float>(str_value); 185 if (!parsed_value.has_value()) 186 return false; 187 188 *value = parsed_value.value(); 189 return true; 190 } 191 192 bool INISettingsInterface::GetDoubleValue(const char* section, const char* key, double* value) const 193 { 194 const char* str_value = m_ini.GetValue(section, key); 195 if (!str_value) 196 return false; 197 198 std::optional<double> parsed_value = StringUtil::FromChars<double>(str_value); 199 if (!parsed_value.has_value()) 200 return false; 201 202 *value = parsed_value.value(); 203 return true; 204 } 205 206 bool INISettingsInterface::GetBoolValue(const char* section, const char* key, bool* value) const 207 { 208 const char* str_value = m_ini.GetValue(section, key); 209 if (!str_value) 210 return false; 211 212 std::optional<bool> parsed_value = StringUtil::FromChars<bool>(str_value); 213 if (!parsed_value.has_value()) 214 return false; 215 216 *value = parsed_value.value(); 217 return true; 218 } 219 220 bool INISettingsInterface::GetStringValue(const char* section, const char* key, std::string* value) const 221 { 222 const char* str_value = m_ini.GetValue(section, key); 223 if (!str_value) 224 return false; 225 226 value->assign(str_value); 227 return true; 228 } 229 230 bool INISettingsInterface::GetStringValue(const char* section, const char* key, SmallStringBase* value) const 231 { 232 const char* str_value = m_ini.GetValue(section, key); 233 if (!str_value) 234 return false; 235 236 value->assign(str_value); 237 return true; 238 } 239 240 void INISettingsInterface::SetIntValue(const char* section, const char* key, s32 value) 241 { 242 m_dirty = true; 243 m_ini.SetValue(section, key, StringUtil::ToChars(value).c_str(), nullptr, true); 244 } 245 246 void INISettingsInterface::SetUIntValue(const char* section, const char* key, u32 value) 247 { 248 m_dirty = true; 249 m_ini.SetValue(section, key, StringUtil::ToChars(value).c_str(), nullptr, true); 250 } 251 252 void INISettingsInterface::SetFloatValue(const char* section, const char* key, float value) 253 { 254 m_dirty = true; 255 m_ini.SetValue(section, key, StringUtil::ToChars(value).c_str(), nullptr, true); 256 } 257 258 void INISettingsInterface::SetDoubleValue(const char* section, const char* key, double value) 259 { 260 m_dirty = true; 261 m_ini.SetValue(section, key, StringUtil::ToChars(value).c_str(), nullptr, true); 262 } 263 264 void INISettingsInterface::SetBoolValue(const char* section, const char* key, bool value) 265 { 266 m_dirty = true; 267 m_ini.SetBoolValue(section, key, value, nullptr, true); 268 } 269 270 void INISettingsInterface::SetStringValue(const char* section, const char* key, const char* value) 271 { 272 m_dirty = true; 273 m_ini.SetValue(section, key, value, nullptr, true); 274 } 275 276 bool INISettingsInterface::ContainsValue(const char* section, const char* key) const 277 { 278 return (m_ini.GetValue(section, key, nullptr) != nullptr); 279 } 280 281 void INISettingsInterface::DeleteValue(const char* section, const char* key) 282 { 283 m_dirty = true; 284 m_ini.Delete(section, key); 285 } 286 287 void INISettingsInterface::ClearSection(const char* section) 288 { 289 m_dirty = true; 290 m_ini.Delete(section, nullptr); 291 m_ini.SetValue(section, nullptr, nullptr); 292 } 293 294 void INISettingsInterface::RemoveSection(const char* section) 295 { 296 if (!m_ini.GetSection(section)) 297 return; 298 299 m_dirty = true; 300 m_ini.Delete(section, nullptr); 301 } 302 303 void INISettingsInterface::RemoveEmptySections() 304 { 305 std::list<CSimpleIniA::Entry> entries; 306 m_ini.GetAllSections(entries); 307 for (const CSimpleIniA::Entry& entry : entries) 308 { 309 if (m_ini.GetSectionSize(entry.pItem) > 0) 310 continue; 311 312 m_dirty = true; 313 m_ini.Delete(entry.pItem, nullptr); 314 } 315 } 316 317 std::vector<std::string> INISettingsInterface::GetStringList(const char* section, const char* key) const 318 { 319 std::list<CSimpleIniA::Entry> entries; 320 if (!m_ini.GetAllValues(section, key, entries)) 321 return {}; 322 323 std::vector<std::string> ret; 324 ret.reserve(entries.size()); 325 for (const CSimpleIniA::Entry& entry : entries) 326 ret.emplace_back(entry.pItem); 327 return ret; 328 } 329 330 void INISettingsInterface::SetStringList(const char* section, const char* key, const std::vector<std::string>& items) 331 { 332 m_dirty = true; 333 m_ini.Delete(section, key); 334 335 for (const std::string& sv : items) 336 m_ini.SetValue(section, key, sv.c_str(), nullptr, false); 337 } 338 339 bool INISettingsInterface::RemoveFromStringList(const char* section, const char* key, const char* item) 340 { 341 m_dirty = true; 342 return m_ini.DeleteValue(section, key, item, true); 343 } 344 345 bool INISettingsInterface::AddToStringList(const char* section, const char* key, const char* item) 346 { 347 std::list<CSimpleIniA::Entry> entries; 348 if (m_ini.GetAllValues(section, key, entries) && 349 std::find_if(entries.begin(), entries.end(), 350 [item](const CSimpleIniA::Entry& e) { return (std::strcmp(e.pItem, item) == 0); }) != entries.end()) 351 { 352 return false; 353 } 354 355 m_dirty = true; 356 m_ini.SetValue(section, key, item, nullptr, false); 357 return true; 358 } 359 360 std::vector<std::pair<std::string, std::string>> INISettingsInterface::GetKeyValueList(const char* section) const 361 { 362 using Entry = CSimpleIniA::Entry; 363 using KVEntry = std::pair<const char*, Entry>; 364 std::vector<KVEntry> entries; 365 std::vector<std::pair<std::string, std::string>> output; 366 std::list<Entry> keys, values; 367 if (m_ini.GetAllKeys(section, keys)) 368 { 369 for (Entry& key : keys) 370 { 371 if (!m_ini.GetAllValues(section, key.pItem, values)) // [[unlikely]] 372 { 373 ERROR_LOG("Got no values for a key returned from GetAllKeys!"); 374 continue; 375 } 376 for (const Entry& value : values) 377 entries.emplace_back(key.pItem, value); 378 } 379 } 380 381 std::sort(entries.begin(), entries.end(), 382 [](const KVEntry& a, const KVEntry& b) { return a.second.nOrder < b.second.nOrder; }); 383 for (const KVEntry& entry : entries) 384 output.emplace_back(entry.first, entry.second.pItem); 385 386 return output; 387 } 388 389 void INISettingsInterface::SetKeyValueList(const char* section, 390 const std::vector<std::pair<std::string, std::string>>& items) 391 { 392 m_ini.Delete(section, nullptr); 393 for (const std::pair<std::string, std::string>& item : items) 394 m_ini.SetValue(section, item.first.c_str(), item.second.c_str(), nullptr, false); 395 }