duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

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 }