Index: common/Profiler.h =================================================================== --- common/Profiler.h (revision 24593) +++ common/Profiler.h (working copy) @@ -54,7 +54,7 @@ #if defined(MODPLUG_TRACKER) -//#define USE_PROFILER +#define USE_PROFILER #endif Index: mptrack/Mptrack.cpp =================================================================== --- mptrack/Mptrack.cpp (revision 24593) +++ mptrack/Mptrack.cpp (working copy) @@ -932,7 +932,7 @@ const mpt::PathString &CTrackApp::GetSongSettingsFilename() const { - return m_pSongSettingsIniFile->GetFilename(); + return m_pSongSettingsIniFile->Filename(); } Index: mptrack/SettingsIni.cpp =================================================================== --- mptrack/SettingsIni.cpp (revision 24593) +++ mptrack/SettingsIni.cpp (working copy) @@ -13,6 +13,7 @@ #include "SettingsIni.h" +#include "mpt/binary/hex.hpp" #include "mpt/io_file/fstream.hpp" #include "mpt/io_file/outputfile.hpp" #include "mpt/parse/parse.hpp" @@ -22,11 +23,33 @@ #include "../common/mptStringBuffer.h" #include +#include + OPENMPT_NAMESPACE_BEGIN + +WindowsIniFileBase::WindowsIniFileBase(mpt::PathString filename) + : filename(std::move(filename)) + , file(filename) +{ + return; +} + +const mpt::PathString &WindowsIniFileBase::Filename() const +{ + return filename; +} + +mpt::PathString WindowsIniFileBase::GetFilename() const +{ + return filename; +} + + + std::vector ImmediateWindowsIniFileSettingsBackend::ReadSettingRaw(const SettingPath &path, const std::vector &def) const { std::vector result = def; @@ -126,12 +149,17 @@ ::WritePrivateProfileString(mpt::ToWin(section).c_str(), NULL, NULL, filename.AsNative().c_str()); } +void CachedBatchedWindowsIniFileSettingsBackend::RemoveSectionRaw(const mpt::ustring §ion) +{ + ::WritePrivateProfileString(mpt::ToWin(section).c_str(), NULL, NULL, filename.AsNative().c_str()); +} -mpt::winstring ImmediateWindowsIniFileSettingsBackend::GetSection(const SettingPath &path) + +mpt::winstring IniFileHelpers::GetSection(const SettingPath &path) { return mpt::ToWin(path.GetSection()); } -mpt::winstring ImmediateWindowsIniFileSettingsBackend::GetKey(const SettingPath &path) +mpt::winstring IniFileHelpers::GetKey(const SettingPath &path) { return mpt::ToWin(path.GetKey()); } @@ -138,18 +166,36 @@ -ImmediateWindowsIniFileSettingsBackend::ImmediateWindowsIniFileSettingsBackend(const mpt::PathString &filename) - : filename(filename) - , file(filename) +ImmediateWindowsIniFileSettingsBackend::ImmediateWindowsIniFileSettingsBackend(mpt::PathString filename) + : WindowsIniFileBase(std::move(filename)) { + OPENMPT_PROFILE_FUNCTION(Profiler::Settings); return; } +CachedBatchedWindowsIniFileSettingsBackend::CachedBatchedWindowsIniFileSettingsBackend(mpt::PathString filename) + : WindowsIniFileBase(std::move(filename)) +{ + OPENMPT_PROFILE_FUNCTION(Profiler::Settings); + ConvertToUnicode(); + cache = ReadAllSectionsRaw(); +} + +void CachedBatchedWindowsIniFileSettingsBackend::InvalidateCache() +{ + cache = ReadAllSectionsRaw(); +} + ImmediateWindowsIniFileSettingsBackend::~ImmediateWindowsIniFileSettingsBackend() { return; } +CachedBatchedWindowsIniFileSettingsBackend::~CachedBatchedWindowsIniFileSettingsBackend() +{ + return; +} + static void WriteFileUTF16LE(mpt::IO::atomic_shared_file_ref &file, const std::wstring &str) { static_assert(sizeof(wchar_t) == 2); @@ -160,7 +206,7 @@ file.write(mpt::byte_cast(mpt::as_span(inifile.str()))); } -void ImmediateWindowsIniFileSettingsBackend::ConvertToUnicode(const mpt::ustring &backupTag) +void WindowsIniFileBase::ConvertToUnicode(const mpt::ustring &backupTag) { // Force ini file to be encoded in UTF16. // This causes WINAPI ini file functions to keep it in UTF16 encoding @@ -209,6 +255,39 @@ } } +SettingValue CachedBatchedWindowsIniFileSettingsBackend::ReadSetting(const SettingPath &path, const SettingValue &def) const +{ + OPENMPT_PROFILE_FUNCTION(Profiler::Settings); + const auto sectionit = cache.find(path.GetRefSection()); + if(sectionit != cache.end()) + { + if(sectionit->second.has_value()) + { + const std::map> §ion = sectionit->second.value(); + const auto it = section.find(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 + { + return def; + } +} + void ImmediateWindowsIniFileSettingsBackend::WriteSetting(const SettingPath &path, const SettingValue &val) { OPENMPT_PROFILE_FUNCTION(Profiler::Settings); @@ -236,22 +315,271 @@ RemoveSectionRaw(section); } +std::set CachedBatchedWindowsIniFileSettingsBackend::ReadSectionNamesRaw() const +{ + std::set result; + const std::vector sectionsstr = [&]() + { + std::vector buf; + buf.resize(mpt::IO::BUFFERSIZE_SMALL); + while(true) + { + DWORD bufused = ::GetPrivateProfileSectionNames(buf.data(), mpt::saturate_cast(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 sections = mpt::split(mpt::winstring(sectionsstr.data(), sectionsstr.size()), mpt::winstring(_T("\0"), 1)); + for(const auto §ion : sections) + { + result.insert(mpt::ToUnicode(section)); + } + return result; +} +std::map> CachedBatchedWindowsIniFileSettingsBackend::ReadNamedSectionRaw(const mpt::ustring §ion) const +{ + std::map> result; + const std::vector keyvalsstr = [&]() + { + std::vector buf; + buf.resize(mpt::IO::BUFFERSIZE_SMALL); + while(true) + { + DWORD bufused = ::GetPrivateProfileSection(mpt::ToWin(section).c_str(), buf.data(), mpt::saturate_cast(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 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(mpt::ToUnicode(keyval.substr(0, equalpos)), std::make_optional(mpt::ToUnicode(keyval.substr(equalpos + 1))))); + } + return result; +} +std::map>>> CachedBatchedWindowsIniFileSettingsBackend::ReadAllSectionsRaw() const +{ + std::map>>> result; + const std::set sectionnames = ReadSectionNamesRaw(); + for(const mpt::ustring §ionname : sectionnames) + { + result.insert(std::make_pair(sectionname, std::make_optional(ReadNamedSectionRaw(sectionname)))); + } + return result; +} +mpt::ustring IniFileHelpers::FormatValueAsIni(const SettingValue &value) +{ + switch(value.GetType()) + { + case SettingTypeBool: + return mpt::ufmt::val(value.as()); + break; + case SettingTypeInt: + return mpt::ufmt::val(value.as()); + break; + case SettingTypeFloat: + return mpt::ufmt::val(value.as()); + break; + case SettingTypeString: + return value.as(); + break; + case SettingTypeBinary: + { + std::vector data = value.as>(); + uint8 checksum = 0; + for(const std::byte x : data) { + checksum += mpt::byte_cast(x); + } + return mpt::encode_hex(mpt::as_span(data)) + mpt::ufmt::HEX0<2>(checksum); + } + break; + case SettingTypeNone: + default: + return mpt::ustring(); + break; + } +} -IniFileSettingsContainer::IniFileSettingsContainer(const mpt::PathString &filename) - : ImmediateWindowsIniFileSettingsBackend(filename) - , SettingsContainer(this) + +SettingValue IniFileHelpers::ParseValueFromIni(const mpt::ustring &str, const SettingValue &def) { + switch(def.GetType()) + { + case SettingTypeBool: + return SettingValue(mpt::parse_or(mpt::trim(str), def.as()), def.GetTypeTag()); + break; + case SettingTypeInt: + return SettingValue(mpt::parse_or(mpt::trim(str), def.as()), def.GetTypeTag()); + break; + case SettingTypeFloat: + return SettingValue(mpt::parse_or(mpt::trim(str), def.as()), def.GetTypeTag()); + break; + case SettingTypeString: + return SettingValue(str, def.GetTypeTag()); + break; + case SettingTypeBinary: + { + if((str.length() % 2) != 0) + { + return SettingValue(def.as>(), def.GetTypeTag()); + } + std::vector data = mpt::decode_hex(str); + if(data.size() < 1) + { + return SettingValue(def.as>(), def.GetTypeTag()); + } + const uint8 storedchecksum = mpt::byte_cast(data[data.size() - 1]); + data.resize(data.size() - 1); + uint8 calculatedchecksum = 0; + for(const std::byte x : data) { + calculatedchecksum += mpt::byte_cast(x); + } + if(calculatedchecksum != storedchecksum) + { + return SettingValue(def.as>(), def.GetTypeTag()); + } + return SettingValue(data, def.GetTypeTag()); + } + break; + default: + return SettingValue(); + break; + } +} + +void CachedBatchedWindowsIniFileSettingsBackend::WriteSectionRaw(const mpt::ustring §ion, const std::map> &keyvalues) +{ + OPENMPT_PROFILE_FUNCTION(Profiler::Settings); + mpt::winstring keyvals; + for(const auto &[key, val] : keyvalues) + { + if(val.has_value()) + { + keyvals.append(mpt::ToWin(key)); + keyvals.append(_T("=")); + keyvals.append(mpt::ToWin(val.value())); + keyvals.append(mpt::winstring(_T("\0"), 1)); + } + } + keyvals.append(mpt::winstring(_T("\0"), 1)); + ::WritePrivateProfileSection(mpt::ToWin(section).c_str(), keyvals.c_str(), filename.AsNative().c_str()); +} + +void CachedBatchedWindowsIniFileSettingsBackend::WriteRemovedSections(const std::set &removeSections) +{ + OPENMPT_PROFILE_FUNCTION(Profiler::Settings); + for(const auto §ion : removeSections) + { + const auto it = cache.find(section); + if(it != cache.end()) + { + it->second = std::nullopt; + } + } + for(const auto &[section, keyvalues] : cache) + { + if(!keyvalues.has_value()) + { + RemoveSectionRaw(section); + } + } + for(const auto §ion : removeSections) + { + cache.erase(section); + } +} + +void CachedBatchedWindowsIniFileSettingsBackend::WriteMultipleSettings(const std::map> &settings) +{ + OPENMPT_PROFILE_FUNCTION(Profiler::Settings); + std::map>> sectionssettings; + for(const auto &[path, value] : settings) + { + sectionssettings[path.GetRefSection()][path] = value; + } + for(const auto &[section, sectionsettings] : sectionssettings) + { + if(!cache[section].has_value()) + { + cache[section].emplace(); + } + std::map> &workingsectionsettings = cache[section].value(); + for(const auto &[path, value] : sectionsettings) + { + if(value.has_value()) + { + workingsectionsettings[path.GetRefKey()] = FormatValueAsIni(value.value()); + } else + { + workingsectionsettings.erase(path.GetRefKey()); + } + } + WriteSectionRaw(section, workingsectionsettings); + } +} + + + +IniFileSettingsContainer::IniFileSettingsContainer(mpt::PathString filename) + : ImmediateWindowsIniFileSettingsBackend(std::move(filename)) + , SettingsContainer(static_cast*>(this)) +{ return; } +BatchedIniFileSettingsContainer::BatchedIniFileSettingsContainer(mpt::PathString filename) + : CachedBatchedWindowsIniFileSettingsBackend(std::move(filename)) + , SettingsContainer(static_cast*>(this)) +{ + return; +} + IniFileSettingsContainer::~IniFileSettingsContainer() { return; } +BatchedIniFileSettingsContainer::~BatchedIniFileSettingsContainer() +{ + return; +} + OPENMPT_NAMESPACE_END Index: mptrack/SettingsIni.h =================================================================== --- mptrack/SettingsIni.h (revision 24593) +++ mptrack/SettingsIni.h (working copy) @@ -13,12 +13,12 @@ #include "openmpt/all/BuildSettings.hpp" - #include "Settings.h" #include "mpt/io_file_atomic/atomic_file.hpp" #include +#include #include #include @@ -25,49 +25,112 @@ OPENMPT_NAMESPACE_BEGIN + +// Version 1: ANSI +// Version 2: UTF-16LE with BOM +// Version 3: UTF8 with BOM (not supported by Windows) +// Version 4: UTF8 without BOM and explicit version + + +class IniFileHelpers +{ +protected: + static mpt::ustring FormatValueAsIni(const SettingValue &value); + static SettingValue ParseValueFromIni(const mpt::ustring &str, const SettingValue &def); + static mpt::winstring GetSection(const SettingPath &path); + static mpt::winstring GetKey(const SettingPath &path); +}; + + +class WindowsIniFileBase +{ +protected: + const mpt::PathString filename; + mpt::IO::atomic_shared_file_ref file; +protected: + WindowsIniFileBase(mpt::PathString filename); + ~WindowsIniFileBase() = default; +public: + void ConvertToUnicode(const mpt::ustring &backupTag = mpt::ustring()); +public: + const mpt::PathString &Filename() const; + mpt::PathString GetFilename() const; +}; + + class ImmediateWindowsIniFileSettingsBackend : virtual public ISettingsBackend , public ISettingsBackendFlavour + , public WindowsIniFileBase + , protected IniFileHelpers { private: - const mpt::PathString filename; - mpt::IO::atomic_shared_file_ref file; -private: std::vector ReadSettingRaw(const SettingPath &path, const std::vector &def) const; mpt::ustring ReadSettingRaw(const SettingPath &path, const mpt::ustring &def) const; double ReadSettingRaw(const SettingPath &path, double def) const; int32 ReadSettingRaw(const SettingPath &path, int32 def) const; bool ReadSettingRaw(const SettingPath &path, bool def) const; + void RemoveSectionRaw(const mpt::ustring §ion); + void RemoveSettingRaw(const SettingPath &path); void WriteSettingRaw(const SettingPath &path, const std::vector &val); void WriteSettingRaw(const SettingPath &path, const mpt::ustring &val); void WriteSettingRaw(const SettingPath &path, double val); void WriteSettingRaw(const SettingPath &path, int32 val); void WriteSettingRaw(const SettingPath &path, bool val); - void RemoveSettingRaw(const SettingPath &path); - void RemoveSectionRaw(const mpt::ustring §ion); - static mpt::winstring GetSection(const SettingPath &path); - static mpt::winstring GetKey(const SettingPath &path); public: - ImmediateWindowsIniFileSettingsBackend(const mpt::PathString &filename); + ImmediateWindowsIniFileSettingsBackend(mpt::PathString filename); ~ImmediateWindowsIniFileSettingsBackend() override; - void ConvertToUnicode(const mpt::ustring &backupTag = mpt::ustring()); +public: virtual SettingValue ReadSetting(const SettingPath &path, const SettingValue &def) const override; + virtual void RemoveSection(const mpt::ustring §ion) override; + virtual void RemoveSetting(const SettingPath &path) override; 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; - const mpt::PathString& GetFilename() const { return filename; } }; -#ifndef IniFileSettingsBackend + +class CachedBatchedWindowsIniFileSettingsBackend + : virtual public ISettingsBackend + , public ISettingsBackendFlavour + , public WindowsIniFileBase + , protected IniFileHelpers +{ +private: + std::map>>> cache; +private: + std::set ReadSectionNamesRaw() const; + std::map> ReadNamedSectionRaw(const mpt::ustring §ion) const; + std::map>>> ReadAllSectionsRaw() const; + void RemoveSectionRaw(const mpt::ustring §ion); + void WriteSectionRaw(const mpt::ustring §ion, const std::map> &keyvalues); +public: + CachedBatchedWindowsIniFileSettingsBackend(mpt::PathString filename); + ~CachedBatchedWindowsIniFileSettingsBackend() override; +public: + virtual void InvalidateCache() override; + virtual SettingValue ReadSetting(const SettingPath &path, const SettingValue &def) const override; + virtual void WriteRemovedSections(const std::set &removeSections) override; + virtual void WriteMultipleSettings(const std::map> &settings) override; +}; + + #define IniFileSettingsBackend ImmediateWindowsIniFileSettingsBackend -#endif -class IniFileSettingsContainer : private IniFileSettingsBackend, public SettingsContainer + +class IniFileSettingsContainer : private ImmediateWindowsIniFileSettingsBackend, public SettingsContainer { public: - IniFileSettingsContainer(const mpt::PathString &filename); + IniFileSettingsContainer(mpt::PathString filename); ~IniFileSettingsContainer() override; }; +class BatchedIniFileSettingsContainer : private CachedBatchedWindowsIniFileSettingsBackend, public SettingsContainer +{ +public: + BatchedIniFileSettingsContainer(mpt::PathString filename); + ~BatchedIniFileSettingsContainer() override; +}; + + + OPENMPT_NAMESPACE_END Index: test/test.cpp =================================================================== --- test/test.cpp (revision 24593) +++ test/test.cpp (working copy) @@ -2741,6 +2741,14 @@ VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("S1"), U_("bar1")}, U_("empty")).as(), U_("a")); VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("S1"), U_("bar2")}, U_("empty")).as(), U_("empty")); } + { + IniFileSettingsBackend inifile{filename}; + const std::set a = inifile.ReadSectionNamesRaw(); + for(const mpt::winstring & s : a) + { + std::map> b = inifile.ReadNamedSectionRaw(s); + } + } DeleteFile(mpt::support_long_path(filename.AsNative()).c_str()); } @@ -2801,6 +2809,23 @@ } #endif + { + std::vector data; + for(std::size_t i = 0; i < 10; ++i) + { + data.push_back(mpt::byte_cast(static_cast(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>(), data); + } + DeleteFile(mpt::support_long_path(filename.AsNative()).c_str()); + } + #endif // MODPLUG_TRACKER }