View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0001559 | OpenMPT | General | public | 2022-01-30 17:48 | 2025-10-25 18:52 |
| Reporter | Saga Musix | Assigned To | manx | ||
| Priority | normal | Severity | minor | Reproducibility | N/A |
| Status | acknowledged | Resolution | open | ||
| Target Version | OpenMPT 1.33 / libopenmpt 0.9 (goals) | ||||
| Summary | 0001559: Don't use WinAPI functions for INI reading/writing | ||||
| Description | Currently OpenMPT uses
As a consequence of the first point, it's possible that one OpenMPT instance takes a long time to shut down, and a newly launched instance may not see the settings as saved by the previous instance. One particularly worrying example is the following scenario:
A custom INI implementation should avoid this scenario. While the file is being written, other OpenMPT instances should wait until the file is no longer locked, rather than reading incomplete settings. | ||||
| Tags | No tags attached. | ||||
| Has the bug occurred in previous versions? | |||||
| Tested code revision (in case you know it) | |||||
I do not think this would be necessary. Atomically writing the file is the easier approach, because we do not (and do not want to, and cannot) guarantee settings consistency across different instances running concurrently anyway. Writing the file in 1 go should also be plenty fast enough to avoid the mentioned race condition. If the second instance is started while the first one is still actively running, there is not much we can do anyway, except for maybe flushing the configuration file after the welcome dialog (which would probably be a good idea even with the old implementation). There are a couple of open questions regarding our own implementation:
The INI implementation that I have in an older code base answers these questions as:
In case the answer to 3 is "no", we should use a different file name in order to not break downgrades completely. "OpenMPT.ini" would make sense here. |
|
|
While strict backwards compatibility would be nice to have, I think it should not delay this effort. I think under normal circumstances 3 should not matter at all, only when a user hand-edited the file. As we lost the ability to automatically back up the current OpenMPT configuration during upgrades, using a new settings name file would probably the safest choice. |
|
This mainly happened by accident. Using GetPrivateProfileSection would be required anyway for converting from the existing Win32 INI functions to a custom implementation, because we cannot rely on all settings being read eagerly into out internal data structures (i.e. currently not selected Sound Devices). Adding the writing was only a small extra step. There is another potential problem that I noticed while working on this: We are passing the INI file name to MFC via speedup-ini-settings-v1.patch (7,831 bytes)
Index: mptrack/Settings.cpp
===================================================================
--- mptrack/Settings.cpp (revision 24333)
+++ mptrack/Settings.cpp (working copy)
@@ -140,6 +140,16 @@
backend->RemoveSection(section);
}
+bool SettingsContainer::BackendsCanWriteMultipleSettings() const
+{
+ return backend->CanWriteMultipleSettings();
+}
+
+void SettingsContainer::BackendsWriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings)
+{
+ backend->WriteMultipleSettings(settings);
+}
+
SettingValue SettingsContainer::ReadSetting(const SettingPath &path, const SettingValue &def) const
{
ASSERT(theApp.InGuiThread());
@@ -242,6 +252,20 @@
{
ASSERT(theApp.InGuiThread());
ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler.
+ if(BackendsCanWriteMultipleSettings())
+ {
+ std::map<SettingPath, SettingValue> settings;
+ for(auto &[path, value] : map)
+ {
+ if(value.IsDirty())
+ {
+ settings.insert(std::make_pair(path, value.GetRefValue()));
+ value.Clean();
+ }
+ }
+ BackendsWriteMultipleSettings(settings);
+ return;
+ }
for(auto &[path, value] : map)
{
if(value.IsDirty())
@@ -407,7 +431,12 @@
IniFileSettingsBackend::IniFileSettingsBackend(const mpt::PathString &filename)
: filename(filename)
{
- return;
+#if defined(UNICODE)
+ if(mpt::osinfo::windows::Version::Current().IsAtLeast(mpt::osinfo::windows::Version::WinXP))
+ {
+ ConvertToUnicode();
+ }
+#endif
}
IniFileSettingsBackend::~IniFileSettingsBackend()
@@ -493,10 +522,143 @@
RemoveSectionRaw(section);
}
+std::set<mpt::winstring> IniFileSettingsBackend::ReadSections() const
+{
+ std::set<mpt::winstring> result;
+ const std::vector<TCHAR> sectionsstr = [&]()
+ {
+ std::vector<TCHAR> buf;
+ buf.resize(1024);
+ while(true)
+ {
+ DWORD bufused = ::GetPrivateProfileSectionNames(buf.data(), mpt::saturate_cast<DWORD>(buf.size()), filename.AsNative().c_str());
+ if(bufused >= (buf.size() - 2))
+ {
+ buf.resize(mpt::exponential_grow(buf.size()));
+ continue;
+ }
+ buf.resize(bufused);
+ break;
+ };
+ return buf;
+ }();
+ const std::vector<mpt::winstring> sections = mpt::split(mpt::winstring(sectionsstr.data(), sectionsstr.size()), mpt::winstring(_T("\0"), 1));
+ for(const auto §ion : sections)
+ {
+ result.insert(section);
+ }
+ return result;
+}
+std::map<mpt::winstring, mpt::winstring> IniFileSettingsBackend::ReadSection(const mpt::winstring §ion) const
+{
+ std::map<mpt::winstring, mpt::winstring> result;
+ const std::vector<TCHAR> keyvalsstr = [&]()
+ {
+ std::vector<TCHAR> buf;
+ buf.resize(1024);
+ while(true)
+ {
+ DWORD bufused = ::GetPrivateProfileSection(mpt::ToWin(section).c_str(), buf.data(), mpt::saturate_cast<DWORD>(buf.size()), filename.AsNative().c_str());
+ if(bufused >= (buf.size() - 2))
+ {
+ buf.resize(mpt::exponential_grow(buf.size()));
+ continue;
+ }
+ buf.resize(bufused);
+ break;
+ };
+ return buf;
+ }();
+ const std::vector<mpt::winstring> keyvals = mpt::split(mpt::winstring(keyvalsstr.data(), keyvalsstr.size()), mpt::winstring(_T("\0"), 1));
+ for(const auto &keyval : keyvals)
+ {
+ const auto equalpos = keyval.find(_T("="));
+ if(equalpos == mpt::winstring::npos)
+ {
+ continue;
+ }
+ if(equalpos == 0)
+ {
+ continue;
+ }
+ result.insert(std::make_pair(keyval.substr(0, equalpos), keyval.substr(equalpos + 1)));
+ }
+ return result;
+}
+mpt::winstring IniFileSettingsBackend::FormatValueAsIni(const SettingValue &value)
+{
+ switch(value.GetType())
+ {
+ case SettingTypeBool:
+ return mpt::tfmt::val(value.as<bool>());
+ break;
+ case SettingTypeInt:
+ return mpt::tfmt::val(value.as<int32>());
+ break;
+ case SettingTypeFloat:
+ return mpt::tfmt::val(value.as<double>());
+ break;
+ case SettingTypeString:
+ return mpt::ToWin(value.as<mpt::ustring>());
+ break;
+ case SettingTypeBinary:
+ {
+ std::vector<std::byte> data = value.as<std::vector<std::byte>>();
+ uint8 checksum = 0;
+ for(const std::byte x : data) {
+ checksum += mpt::byte_cast<uint8>(x);
+ }
+ return mpt::ToWin(mpt::encode_hex(mpt::as_span(data)) + mpt::ufmt::HEX0<2>(checksum));
+ }
+ break;
+ case SettingTypeNone:
+ default:
+ return mpt::ustring();
+ break;
+ }
+}
+void IniFileSettingsBackend::WriteSection(const mpt::winstring §ion, const std::map<mpt::winstring, mpt::winstring> keyvalues)
+{
+ mpt::winstring keyvals;
+ for(const auto &[key, val] : keyvalues)
+ {
+ keyvals.append(key);
+ keyvals.append(_T("="));
+ keyvals.append(val);
+ keyvals.append(mpt::winstring(_T("\0"), 1));
+ }
+ keyvals.append(mpt::winstring(_T("\0"), 1));
+ ::WritePrivateProfileSection(section.c_str(), keyvals.c_str(), filename.AsNative().c_str());
+}
+void IniFileSettingsBackend::WriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings)
+{
+ std::map<mpt::ustring, std::map<SettingPath, SettingValue>> sectionssettings;
+ for(const auto &[path, value] : settings)
+ {
+ sectionssettings[path.GetRefSection()][path] = value;
+ }
+ for(const auto &[section, sectionsettings] : sectionssettings)
+ {
+ std::map<mpt::winstring, mpt::winstring> workingsectionsettings = ReadSection(mpt::ToWin(section));
+ for(const auto &[path, value] : sectionsettings)
+ {
+ workingsectionsettings[mpt::ToWin(path.GetRefKey())] = FormatValueAsIni(value);
+ }
+ WriteSection(mpt::ToWin(section), workingsectionsettings);
+ }
+}
+
+bool IniFileSettingsBackend::CanWriteMultipleSettings() const
+{
+ return true;
+}
+
+
+
IniFileSettingsContainer::IniFileSettingsContainer(const mpt::PathString &filename)
: IniFileSettingsBackend(filename)
, SettingsContainer(this)
Index: mptrack/Settings.h
===================================================================
--- mptrack/Settings.h (revision 24333)
+++ mptrack/Settings.h (working copy)
@@ -408,6 +408,8 @@
virtual void WriteSetting(const SettingPath &path, const SettingValue &val) = 0;
virtual void RemoveSetting(const SettingPath &path) = 0;
virtual void RemoveSection(const mpt::ustring §ion) = 0;
+ virtual bool CanWriteMultipleSettings() const = 0;
+ virtual void WriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings) = 0;
protected:
virtual ~ISettingsBackend() = default;
};
@@ -448,6 +450,8 @@
void BackendsWriteSetting(const SettingPath &path, const SettingValue &val);
void BackendsRemoveSetting(const SettingPath &path);
void BackendsRemoveSection(const mpt::ustring §ion);
+ bool BackendsCanWriteMultipleSettings() const;
+ void BackendsWriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings);
void NotifyListeners(const SettingPath &path);
SettingValue ReadSetting(const SettingPath &path, const SettingValue &def) const;
bool IsDefaultSetting(const SettingPath &path) const;
@@ -711,6 +715,14 @@
virtual void WriteSetting(const SettingPath &path, const SettingValue &val) override;
virtual void RemoveSetting(const SettingPath &path) override;
virtual void RemoveSection(const mpt::ustring §ion) override;
+private:
+ std::set<mpt::winstring> ReadSections() const;
+ std::map<mpt::winstring, mpt::winstring> ReadSection(const mpt::winstring §ion) const;
+ static mpt::winstring FormatValueAsIni(const SettingValue &value);
+ void WriteSection(const mpt::winstring §ion, const std::map<mpt::winstring, mpt::winstring> keyvalues);
+public:
+ virtual bool CanWriteMultipleSettings() const override;
+ virtual void WriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings) override;
const mpt::PathString& GetFilename() const { return filename; }
};
|
|
This is for storing the position and visibility of toolbars. It would probably be possible to serialize them outselves by overwriting some functions, then MFC would not be involved at all anymore. |
|
|
speedup-ini-settings-v3-wip.patch (17,925 bytes)
Index: mptrack/Settings.cpp
===================================================================
--- mptrack/Settings.cpp (revision 24343)
+++ mptrack/Settings.cpp (working copy)
@@ -140,6 +140,16 @@
backend->RemoveSection(section);
}
+bool SettingsContainer::BackendsCanWriteMultipleSettings() const
+{
+ return backend->CanBatchedSettings();
+}
+
+void SettingsContainer::BackendsWriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings)
+{
+ backend->WriteMultipleSettings(settings);
+}
+
SettingValue SettingsContainer::ReadSetting(const SettingPath &path, const SettingValue &def) const
{
ASSERT(theApp.InGuiThread());
@@ -242,6 +252,26 @@
{
ASSERT(theApp.InGuiThread());
ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler.
+ if(BackendsCanWriteMultipleSettings())
+ {
+ std::map<SettingPath, SettingValue> settings;
+ for(auto &[path, value] : map)
+ {
+ if(value.IsDirty())
+ {
+ settings.insert(std::make_pair(path, value.GetRefValue()));
+ }
+ }
+ BackendsWriteMultipleSettings(settings);
+ for(auto &[path, value] : map)
+ {
+ if(value.IsDirty())
+ {
+ value.Clean();
+ }
+ }
+ return;
+ }
for(auto &[path, value] : map)
{
if(value.IsDirty())
@@ -405,11 +435,22 @@
IniFileSettingsBackend::IniFileSettingsBackend(const mpt::PathString &filename)
- : filename(filename)
+ : IniFileSettingsBackend(filename, CachePolicy::Cached)
{
return;
}
+IniFileSettingsBackend::IniFileSettingsBackend(const mpt::PathString &filename, CachePolicy cachePolicy)
+ : filename(filename)
+{
+ OPENMPT_PROFILE_FUNCTION(Profiler::Settings);
+ if(cachePolicy == CachePolicy::Cached)
+ {
+ ConvertToUnicode();
+ cache = ReadAllSectionsRaw();
+ }
+}
+
IniFileSettingsBackend::~IniFileSettingsBackend()
{
return;
@@ -469,14 +510,41 @@
SettingValue IniFileSettingsBackend::ReadSetting(const SettingPath &path, const SettingValue &def) const
{
OPENMPT_PROFILE_FUNCTION(Profiler::Settings);
- switch(def.GetType())
+ if(cache.has_value())
{
- case SettingTypeBool: return SettingValue(ReadSettingRaw(path, def.as<bool>()), def.GetTypeTag()); break;
- case SettingTypeInt: return SettingValue(ReadSettingRaw(path, def.as<int32>()), def.GetTypeTag()); break;
- case SettingTypeFloat: return SettingValue(ReadSettingRaw(path, def.as<double>()), def.GetTypeTag()); break;
- case SettingTypeString: return SettingValue(ReadSettingRaw(path, def.as<mpt::ustring>()), def.GetTypeTag()); break;
- case SettingTypeBinary: return SettingValue(ReadSettingRaw(path, def.as<std::vector<std::byte> >()), def.GetTypeTag()); break;
- default: return SettingValue(); break;
+ const auto sectionit = (*cache).find(mpt::ToWin(path.GetRefSection()));
+ if(sectionit != (*cache).end())
+ {
+ const std::map<mpt::winstring, std::optional<mpt::winstring>> §ion = sectionit->second;
+ const auto it = section.find(mpt::ToWin(path.GetRefKey()));
+ if(it != section.end())
+ {
+ if(it->second.has_value())
+ {
+ return ParseValueFromIni(it->second.value(), def);
+ } else
+ {
+ return def;
+ }
+ } else
+ {
+ return def;
+ }
+ } else
+ {
+ return def;
+ }
+ } else
+ {
+ switch(def.GetType())
+ {
+ case SettingTypeBool: return SettingValue(ReadSettingRaw(path, def.as<bool>()), def.GetTypeTag()); break;
+ case SettingTypeInt: return SettingValue(ReadSettingRaw(path, def.as<int32>()), def.GetTypeTag()); break;
+ case SettingTypeFloat: return SettingValue(ReadSettingRaw(path, def.as<double>()), def.GetTypeTag()); break;
+ case SettingTypeString: return SettingValue(ReadSettingRaw(path, def.as<mpt::ustring>()), def.GetTypeTag()); break;
+ case SettingTypeBinary: return SettingValue(ReadSettingRaw(path, def.as<std::vector<std::byte> >()), def.GetTypeTag()); break;
+ default: return SettingValue(); break;
+ }
}
}
@@ -493,12 +561,22 @@
case SettingTypeBinary: WriteSettingRaw(path, val.as<std::vector<std::byte> >()); break;
default: break;
}
+ if(cache.has_value())
+ {
+ (*cache)[mpt::ToWin(path.GetRefSection())][mpt::ToWin(path.GetRefKey())] = FormatValueAsIni(val);
+ }
}
void IniFileSettingsBackend::RemoveSetting(const SettingPath &path)
{
OPENMPT_PROFILE_FUNCTION(Profiler::Settings);
- RemoveSettingRaw(path);
+ if(cache.has_value())
+ {
+ (*cache)[mpt::ToWin(path.GetRefSection())][mpt::ToWin(path.GetRefKey())] = std::nullopt;
+ } else
+ {
+ RemoveSettingRaw(path);
+ }
}
void IniFileSettingsBackend::RemoveSection(const mpt::ustring §ion)
@@ -505,19 +583,255 @@
{
OPENMPT_PROFILE_FUNCTION(Profiler::Settings);
RemoveSectionRaw(section);
+ if(cache.has_value())
+ {
+ const auto it = (*cache).find(mpt::ToWin(section));
+ if(it != (*cache).end())
+ {
+ (*cache).erase(it);
+ }
+ }
}
+std::set<mpt::winstring> IniFileSettingsBackend::ReadSectionNamesRaw() const
+{
+ std::set<mpt::winstring> result;
+ const std::vector<TCHAR> sectionsstr = [&]()
+ {
+ std::vector<TCHAR> buf;
+ buf.resize(mpt::IO::BUFFERSIZE_SMALL);
+ while(true)
+ {
+ DWORD bufused = ::GetPrivateProfileSectionNames(buf.data(), mpt::saturate_cast<DWORD>(buf.size()), filename.AsNative().c_str());
+ if(bufused >= (buf.size() - 2))
+ {
+ std::size_t newsize = mpt::exponential_grow(buf.size());
+ buf.resize(0);
+ buf.resize(newsize);
+ continue;
+ }
+ if(bufused >= 1)
+ {
+ bufused -= 1; // terminating \0
+ }
+ buf.resize(bufused);
+ break;
+ };
+ return buf;
+ }();
+ const std::vector<mpt::winstring> sections = mpt::split(mpt::winstring(sectionsstr.data(), sectionsstr.size()), mpt::winstring(_T("\0"), 1));
+ for(const auto §ion : sections)
+ {
+ result.insert(section);
+ }
+ return result;
+}
+std::map<mpt::winstring, std::optional<mpt::winstring>> IniFileSettingsBackend::ReadNamedSectionRaw(const mpt::winstring §ion) const
+{
+ std::map<mpt::winstring, std::optional<mpt::winstring>> result;
+ const std::vector<TCHAR> keyvalsstr = [&]()
+ {
+ std::vector<TCHAR> buf;
+ buf.resize(mpt::IO::BUFFERSIZE_SMALL);
+ while(true)
+ {
+ DWORD bufused = ::GetPrivateProfileSection(mpt::ToWin(section).c_str(), buf.data(), mpt::saturate_cast<DWORD>(buf.size()), filename.AsNative().c_str());
+ if(bufused >= (buf.size() - 2))
+ {
+ std::size_t newsize = mpt::exponential_grow(buf.size());
+ buf.resize(0);
+ buf.resize(newsize);
+ continue;
+ }
+ if(bufused >= 1)
+ {
+ bufused -= 1; // terminating \0
+ }
+ buf.resize(bufused);
+ break;
+ };
+ return buf;
+ }();
+ const std::vector<mpt::winstring> keyvals = mpt::split(mpt::winstring(keyvalsstr.data(), keyvalsstr.size()), mpt::winstring(_T("\0"), 1));
+ for(const auto &keyval : keyvals)
+ {
+ const auto equalpos = keyval.find(_T("="));
+ if(equalpos == mpt::winstring::npos)
+ {
+ continue;
+ }
+ if(equalpos == 0)
+ {
+ continue;
+ }
+ result.insert(std::make_pair(keyval.substr(0, equalpos), std::make_optional(keyval.substr(equalpos + 1))));
+ }
+ return result;
+}
+std::map<mpt::winstring, std::map<mpt::winstring, std::optional<mpt::winstring>>> IniFileSettingsBackend::ReadAllSectionsRaw() const
+{
+ std::map<mpt::winstring, std::map<mpt::winstring, std::optional<mpt::winstring>>> result;
+ const std::set<mpt::winstring> sectionnames = ReadSectionNamesRaw();
+ for(const mpt::winstring §ionname : sectionnames)
+ {
+ result.insert(std::make_pair(sectionname, ReadNamedSectionRaw(sectionname)));
+ }
+ return result;
+}
+mpt::winstring IniFileSettingsBackend::FormatValueAsIni(const SettingValue &value)
+{
+ switch(value.GetType())
+ {
+ case SettingTypeBool:
+ return mpt::tfmt::val(value.as<bool>());
+ break;
+ case SettingTypeInt:
+ return mpt::tfmt::val(value.as<int32>());
+ break;
+ case SettingTypeFloat:
+ return mpt::tfmt::val(value.as<double>());
+ break;
+ case SettingTypeString:
+ return mpt::ToWin(value.as<mpt::ustring>());
+ break;
+ case SettingTypeBinary:
+ {
+ std::vector<std::byte> data = value.as<std::vector<std::byte>>();
+ uint8 checksum = 0;
+ for(const std::byte x : data) {
+ checksum += mpt::byte_cast<uint8>(x);
+ }
+ return mpt::ToWin(mpt::encode_hex(mpt::as_span(data)) + mpt::ufmt::HEX0<2>(checksum));
+ }
+ break;
+ case SettingTypeNone:
+ default:
+ return mpt::winstring();
+ break;
+ }
+}
+
+SettingValue IniFileSettingsBackend::ParseValueFromIni(const mpt::winstring &str, const SettingValue &def)
+{
+ switch(def.GetType())
+ {
+ case SettingTypeBool:
+ return SettingValue(mpt::parse_or<bool>(mpt::trim(str), def.as<bool>()), def.GetTypeTag());
+ break;
+ case SettingTypeInt:
+ return SettingValue(mpt::parse_or<int32>(mpt::trim(str), def.as<int32>()), def.GetTypeTag());
+ break;
+ case SettingTypeFloat:
+ return SettingValue(mpt::parse_or<double>(mpt::trim(str), def.as<double>()), def.GetTypeTag());
+ break;
+ case SettingTypeString:
+ return SettingValue(mpt::ToUnicode(str), def.GetTypeTag());
+ break;
+ case SettingTypeBinary:
+ {
+ mpt::ustring ustr = mpt::trim(mpt::ToUnicode(str));
+ if((ustr.length() % 2) != 0)
+ {
+ return SettingValue(def.as<std::vector<std::byte>>(), def.GetTypeTag());
+ }
+ std::vector<std::byte> data = mpt::decode_hex(ustr);
+ if(data.size() < 1)
+ {
+ return SettingValue(def.as<std::vector<std::byte>>(), def.GetTypeTag());
+ }
+ const uint8 storedchecksum = mpt::byte_cast<uint8>(data[data.size() - 1]);
+ data.resize(data.size() - 1);
+ uint8 calculatedchecksum = 0;
+ for(const std::byte x : data) {
+ calculatedchecksum += mpt::byte_cast<uint8>(x);
+ }
+ if(calculatedchecksum != storedchecksum)
+ {
+ return SettingValue(def.as<std::vector<std::byte>>(), def.GetTypeTag());
+ }
+ return SettingValue(data, def.GetTypeTag());
+ }
+ break;
+ default:
+ return SettingValue();
+ break;
+ }
+}
+
+void IniFileSettingsBackend::WriteSectionRaw(const mpt::winstring §ion, const std::map<mpt::winstring, std::optional<mpt::winstring>> &keyvalues)
+{
+ mpt::winstring keyvals;
+ for(const auto &[key, val] : keyvalues)
+ {
+ if(val.has_value())
+ {
+ keyvals.append(key);
+ keyvals.append(_T("="));
+ keyvals.append(val.value());
+ keyvals.append(mpt::winstring(_T("\0"), 1));
+ }
+ }
+ keyvals.append(mpt::winstring(_T("\0"), 1));
+ ::WritePrivateProfileSection(section.c_str(), keyvals.c_str(), filename.AsNative().c_str());
+}
+
+void IniFileSettingsBackend::WriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings)
+{
+ OPENMPT_PROFILE_FUNCTION(Profiler::Settings);
+ std::map<mpt::ustring, std::map<SettingPath, SettingValue>> sectionssettings;
+ for(const auto &[path, value] : settings)
+ {
+ sectionssettings[path.GetRefSection()][path] = value;
+ }
+ for(const auto &[section, sectionsettings] : sectionssettings)
+ {
+ // we need to re-read the section before writing it out in order to not overwrite settings written by MFC
+ std::map<mpt::winstring, std::optional<mpt::winstring>> workingsectionsettings = ReadNamedSectionRaw(mpt::ToWin(section));
+ // apply deleted settings from cache
+ for(const auto &[key, value] : (*cache)[mpt::ToWin(section)])
+ {
+ if(!value.has_value())
+ {
+ workingsectionsettings[key] = std::nullopt;
+ }
+ }
+ for(const auto &[path, value] : sectionsettings)
+ {
+ workingsectionsettings[mpt::ToWin(path.GetRefKey())] = FormatValueAsIni(value);
+ }
+ WriteSectionRaw(mpt::ToWin(section), workingsectionsettings);
+ if(cache.has_value())
+ {
+ (*cache)[mpt::ToWin(section)] = std::move(workingsectionsettings);
+ }
+ }
+}
+
+bool IniFileSettingsBackend::CanBatchedSettings() const
+{
+ return cache.has_value();
+}
+
+
+
IniFileSettingsContainer::IniFileSettingsContainer(const mpt::PathString &filename)
- : IniFileSettingsBackend(filename)
+ : IniFileSettingsBackend(filename, CachePolicy::Cached)
, SettingsContainer(this)
{
return;
}
+IniFileSettingsContainer::IniFileSettingsContainer(const mpt::PathString &filename, CachePolicy cachePolicy)
+ : IniFileSettingsBackend(filename, cachePolicy)
+ , SettingsContainer(this)
+{
+ return;
+}
+
IniFileSettingsContainer::~IniFileSettingsContainer()
{
return;
Index: mptrack/Settings.h
===================================================================
--- mptrack/Settings.h (revision 24343)
+++ mptrack/Settings.h (working copy)
@@ -408,6 +408,8 @@
virtual void WriteSetting(const SettingPath &path, const SettingValue &val) = 0;
virtual void RemoveSetting(const SettingPath &path) = 0;
virtual void RemoveSection(const mpt::ustring §ion) = 0;
+ virtual bool CanBatchedSettings() const = 0;
+ virtual void WriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings) = 0;
protected:
virtual ~ISettingsBackend() = default;
};
@@ -448,6 +450,8 @@
void BackendsWriteSetting(const SettingPath &path, const SettingValue &val);
void BackendsRemoveSetting(const SettingPath &path);
void BackendsRemoveSection(const mpt::ustring §ion);
+ bool BackendsCanWriteMultipleSettings() const;
+ void BackendsWriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings);
void NotifyListeners(const SettingPath &path);
SettingValue ReadSetting(const SettingPath &path, const SettingValue &def) const;
bool IsDefaultSetting(const SettingPath &path) const;
@@ -684,11 +688,26 @@
};
+enum class CachePolicy
+{
+ Direct,
+ Cached,
+};
+
class IniFileSettingsBackend : public ISettingsBackend
{
+public:
private:
const mpt::PathString filename;
+ std::optional<std::map<mpt::winstring, std::map<mpt::winstring, std::optional<mpt::winstring>>>> cache;
+#ifdef ENABLE_TESTS
+public:
+#else
private:
+#endif
+ std::set<mpt::winstring> ReadSectionNamesRaw() const;
+ std::map<mpt::winstring, std::optional<mpt::winstring>> ReadNamedSectionRaw(const mpt::winstring §ion) const;
+ std::map<mpt::winstring, std::map<mpt::winstring, std::optional<mpt::winstring>>> ReadAllSectionsRaw() const;
std::vector<std::byte> ReadSettingRaw(const SettingPath &path, const std::vector<std::byte> &def) const;
mpt::ustring ReadSettingRaw(const SettingPath &path, const mpt::ustring &def) const;
double ReadSettingRaw(const SettingPath &path, double def) const;
@@ -699,12 +718,17 @@
void WriteSettingRaw(const SettingPath &path, double val);
void WriteSettingRaw(const SettingPath &path, int32 val);
void WriteSettingRaw(const SettingPath &path, bool val);
+ void WriteSectionRaw(const mpt::winstring §ion, const std::map<mpt::winstring, std::optional<mpt::winstring>> &keyvalues);
void RemoveSettingRaw(const SettingPath &path);
void RemoveSectionRaw(const mpt::ustring §ion);
+private:
+ static mpt::winstring FormatValueAsIni(const SettingValue &value);
+ static SettingValue ParseValueFromIni(const mpt::winstring &str, const SettingValue &def);
static mpt::winstring GetSection(const SettingPath &path);
static mpt::winstring GetKey(const SettingPath &path);
public:
- IniFileSettingsBackend(const mpt::PathString &filename);
+ [[deprecated]] IniFileSettingsBackend(const mpt::PathString &filename);
+ IniFileSettingsBackend(const mpt::PathString &filename, CachePolicy cachePolicy);
~IniFileSettingsBackend() override;
void ConvertToUnicode(const mpt::ustring &backupTag = mpt::ustring());
virtual SettingValue ReadSetting(const SettingPath &path, const SettingValue &def) const override;
@@ -711,6 +735,9 @@
virtual void WriteSetting(const SettingPath &path, const SettingValue &val) override;
virtual void RemoveSetting(const SettingPath &path) override;
virtual void RemoveSection(const mpt::ustring §ion) override;
+ virtual bool CanBatchedSettings() const override;
+ virtual void WriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings) override;
+public:
const mpt::PathString& GetFilename() const { return filename; }
};
@@ -717,7 +744,8 @@
class IniFileSettingsContainer : private IniFileSettingsBackend, public SettingsContainer
{
public:
- IniFileSettingsContainer(const mpt::PathString &filename);
+ [[deprecated]] IniFileSettingsContainer(const mpt::PathString &filename);
+ IniFileSettingsContainer(const mpt::PathString &filename, CachePolicy cachePolicy);
~IniFileSettingsContainer() override;
};
Index: test/test.cpp
===================================================================
--- test/test.cpp (revision 24343)
+++ test/test.cpp (working copy)
@@ -2737,9 +2737,35 @@
VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("S1"), U_("bar1")}, U_("empty")).as<mpt::ustring>(), U_("a"));
VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("S1"), U_("bar2")}, U_("empty")).as<mpt::ustring>(), U_("empty"));
}
+ {
+ IniFileSettingsBackend inifile{filename};
+ const std::set<mpt::winstring> a = inifile.ReadSectionNamesRaw();
+ for(const mpt::winstring & s : a)
+ {
+ std::map<mpt::winstring, std::optional<mpt::winstring>> b = inifile.ReadNamedSectionRaw(s);
+ }
+ }
DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
}
+ {
+ const mpt::PathString filename = theApp.GetConfigPath() + P_("test.ini");
+ std::vector<std::byte> data;
+ for(std::size_t i = 0; i < 10; ++i)
+ {
+ data.push_back(mpt::byte_cast<std::byte>(static_cast<uint8>(i)));
+ }
+ {
+ IniFileSettingsBackend inifile{filename, CachePolicy::Cached};
+ inifile.WriteMultipleSettings({{SettingPath(U_("Test"), U_("Data")), data}});
+ }
+ {
+ IniFileSettingsBackend inifile{filename, CachePolicy::Cached};
+ VERIFY_EQUAL(inifile.ReadSetting(SettingPath(U_("Test"), U_("Data")), SettingValue(data)).as<std::vector<std::byte>>(), data);
+ }
+ DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
+ }
+
#endif // MODPLUG_TRACKER
}
|
|
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2022-01-30 17:48 | Saga Musix | New Issue | |
| 2022-01-30 17:51 | Saga Musix | Target Version | => OpenMPT 1.31.01.00 / libopenmpt 0.7.0 (upgrade first) |
| 2022-10-22 13:45 | manx | Assigned To | => manx |
| 2022-10-22 13:45 | manx | Status | new => acknowledged |
| 2023-04-10 08:24 | manx | Target Version | OpenMPT 1.31.01.00 / libopenmpt 0.7.0 (upgrade first) => OpenMPT 1.32.01.00 / libopenmpt 0.8.0 (upgrade first) |
| 2024-10-26 18:05 | manx | Target Version | OpenMPT 1.32.01.00 / libopenmpt 0.8.0 (upgrade first) => OpenMPT 1.33 / libopenmpt 0.9 (goals) |
| 2025-08-16 16:27 | manx | Note Added: 0006447 | |
| 2025-08-30 13:00 | Saga Musix | Note Added: 0006454 | |
| 2025-10-18 17:35 | manx | Note Added: 0006514 | |
| 2025-10-18 17:35 | manx | File Added: speedup-ini-settings-v1.patch | |
| 2025-10-19 18:35 | Saga Musix | Note Added: 0006515 | |
| 2025-10-25 18:52 | manx | Note Added: 0006517 | |
| 2025-10-25 18:52 | manx | File Added: speedup-ini-settings-v3-wip.patch |