2 * @file IniOptionsMgr.cpp
4 * @brief Implementation of Ini file Options management class.
9 #include "IniOptionsMgr.h"
12 #include "OptionsMgr.h"
14 LPCWSTR lpAppName = TEXT("WinMerge");
15 LPCWSTR lpDefaultSection = TEXT("Defaults");
17 struct AsyncWriterThreadParams
19 AsyncWriterThreadParams(const String& name, const varprop::VariantValue& value) : name(name), value(value) {}
21 varprop::VariantValue value;
24 CIniOptionsMgr::CIniOptionsMgr(const String& filePath)
26 , m_filePath{filePath}
32 m_iniFileKeyValues = Load(m_filePath);
33 m_hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
34 m_hThread = reinterpret_cast<HANDLE>(
35 _beginthreadex(nullptr, 0, AsyncWriterThreadProc, this, 0,
36 reinterpret_cast<unsigned *>(&m_dwThreadId)));
37 WaitForSingleObject(m_hEvent, INFINITE);
38 CloseHandle(m_hEvent);
42 CIniOptionsMgr::~CIniOptionsMgr()
45 PostThreadMessage(m_dwThreadId, WM_QUIT, 0, 0);
46 if (WaitForSingleObject(m_hThread, 1) != WAIT_TIMEOUT)
51 unsigned __stdcall CIniOptionsMgr::AsyncWriterThreadProc(void *pvThis)
53 CIniOptionsMgr *pThis = reinterpret_cast<CIniOptionsMgr *>(pvThis);
56 // create message queue
57 PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
58 SetEvent(pThis->m_hEvent);
59 while ((bRet = GetMessage(&msg, 0, 0, 0)) != 0)
61 auto* pParam = reinterpret_cast<AsyncWriterThreadParams *>(msg.wParam);
62 if (msg.message == WM_USER && pParam)
64 pThis->SaveValueToFile(pParam->name, pParam->value);
66 InterlockedDecrement(&pThis->m_dwQueueCount);
72 std::map<String, String> CIniOptionsMgr::Load(const String& iniFilePath)
74 std::map<String, String> iniFileKeyValues;
75 std::vector<tchar_t> str(32768);
76 if (GetPrivateProfileSection(lpAppName, str.data(), static_cast<DWORD>(str.size()), iniFilePath.c_str()) > 0)
78 const tchar_t* p = str.data();
81 const tchar_t* v = tc::tcschr(p, '=');
85 size_t vlen = tc::tcslen(v);
86 String value{ v, v + vlen };
87 String key{ p, v - 1 };
88 iniFileKeyValues.insert_or_assign(key, UnescapeValue(value));
93 // after reading the "WinMerge" section try to read the "Defaults" section; overwrite existing entries in "iniFileKeyValues" with the ones from the "Defaults" section
94 if (GetPrivateProfileSection(lpDefaultSection, str.data(), static_cast<DWORD>(str.size()), iniFilePath.c_str()) > 0)
96 const tchar_t* p = str.data();
99 const tchar_t* v = tc::tcschr(p, '=');
103 size_t vlen = tc::tcslen(v);
104 String value{ v, v + vlen };
105 String key{ p, v - 1 };
106 iniFileKeyValues.insert_or_assign(key, UnescapeValue(value));
111 return iniFileKeyValues;
114 int CIniOptionsMgr::LoadValueFromBuf(const String& strName, const String& textValue, varprop::VariantValue& value)
116 int retVal = COption::OPT_OK;
117 int valType = value.GetType();
118 if (valType == varprop::VT_STRING)
120 value.SetString(textValue);
121 retVal = Set(strName, value);
123 else if (valType == varprop::VT_INT)
125 tchar_t* endptr = nullptr;
126 DWORD val = static_cast<DWORD>(tc::tcstoll(textValue.c_str(), &endptr,
127 (textValue.length() >= 2 && textValue[1] == 'x') ? 16 : 10));
128 value.SetInt(static_cast<int>(val));
129 retVal = Set(strName, value);
131 else if (valType == varprop::VT_BOOL)
133 value.SetBool(textValue[0] == '1' ? true : false);
134 retVal = Set(strName, value);
137 retVal = COption::OPT_WRONG_TYPE;
142 int CIniOptionsMgr::SaveValueToFile(const String& name, const varprop::VariantValue& value)
144 BOOL retValReg = TRUE;
145 int valType = value.GetType();
146 int retVal = COption::OPT_OK;
148 if (valType == varprop::VT_STRING)
150 String strVal = EscapeValue(value.GetString());
151 LPCWSTR text = strVal.c_str();
152 retValReg = WritePrivateProfileString(lpAppName, name.c_str(), text, GetFilePath());
154 else if (valType == varprop::VT_INT)
156 DWORD dwordVal = value.GetInt();
157 String strVal = strutils::to_str(dwordVal);
158 LPCWSTR text = strVal.c_str();
159 retValReg = WritePrivateProfileString(lpAppName, name.c_str(), text, GetFilePath());
161 else if (valType == varprop::VT_BOOL)
163 DWORD dwordVal = value.GetBool() ? 1 : 0;
164 String strVal = strutils::to_str(dwordVal);
165 LPCWSTR text = strVal.c_str();
166 retValReg = WritePrivateProfileString(lpAppName, name.c_str(), text, GetFilePath());
168 else if (valType == varprop::VT_NULL)
170 auto [strPath, strValueName] = SplitName(name);
171 if (!strValueName.empty())
172 retValReg = WritePrivateProfileString(lpAppName, name.c_str(), nullptr, GetFilePath());
175 auto iniFileMap = Load(GetFilePath());
176 for (auto& [key, value2] : iniFileMap)
178 if (key.find(strPath) == 0 && key.length() > strPath.length() && key[strPath.length()] == '/')
179 retValReg = WritePrivateProfileString(lpAppName, key.c_str(), nullptr, GetFilePath());
185 retVal = COption::OPT_UNKNOWN_TYPE;
190 retVal = COption::OPT_ERR;
195 int CIniOptionsMgr::InitOption(const String& name, const varprop::VariantValue& defaultValue)
197 // Check type & bail if null
198 int valType = defaultValue.GetType();
199 if (valType == varprop::VT_NULL)
200 return COption::OPT_ERR;
202 // If we're not loading & saving options, bail
204 return AddOption(name, defaultValue);
206 // Actually save value into our in-memory options table
207 int retVal = AddOption(name, defaultValue);
209 // Update registry if successfully saved to in-memory table
210 if (retVal == COption::OPT_OK)
212 // check if value exist
213 bool found = m_iniFileKeyValues.find(name) != m_iniFileKeyValues.end();
216 String textValue = m_iniFileKeyValues[name];
217 varprop::VariantValue value(defaultValue);
218 retVal = LoadValueFromBuf(name, textValue, value);
225 int CIniOptionsMgr::InitOption(const String& name, const String& defaultValue)
227 varprop::VariantValue defValue;
228 defValue.SetString(defaultValue);
229 return InitOption(name, defValue);
232 int CIniOptionsMgr::InitOption(const String& name, const tchar_t* defaultValue)
234 return InitOption(name, String(defaultValue));
237 int CIniOptionsMgr::InitOption(const String& name, int defaultValue, bool serializable)
239 varprop::VariantValue defValue;
240 int retVal = COption::OPT_OK;
242 defValue.SetInt(defaultValue);
244 retVal = InitOption(name, defValue);
246 AddOption(name, defValue);
250 int CIniOptionsMgr::InitOption(const String& name, bool defaultValue)
252 varprop::VariantValue defValue;
253 defValue.SetBool(defaultValue);
254 return InitOption(name, defValue);
257 int CIniOptionsMgr::SaveOption(const String& name)
259 if (!m_serializing) return COption::OPT_OK;
261 varprop::VariantValue value;
262 int retVal = COption::OPT_OK;
265 int valType = value.GetType();
266 if (valType == varprop::VT_NULL)
267 retVal = COption::OPT_NOTFOUND;
269 if (retVal == COption::OPT_OK)
271 auto* pParam = new AsyncWriterThreadParams(name, value);
272 InterlockedIncrement(&m_dwQueueCount);
273 if (!PostThreadMessage(m_dwThreadId, WM_USER, (WPARAM)pParam, 0))
274 InterlockedDecrement(&m_dwQueueCount);
280 * @brief Set new value for option and save option to file
282 int CIniOptionsMgr::SaveOption(const String& name, const varprop::VariantValue& value)
284 int retVal = Set(name, value);
285 if (retVal == COption::OPT_OK)
286 retVal = SaveOption(name);
291 * @brief Set new string value for option and save option to file
293 int CIniOptionsMgr::SaveOption(const String& name, const String& value)
295 varprop::VariantValue val;
296 val.SetString(value);
297 int retVal = Set(name, val);
298 if (retVal == COption::OPT_OK)
299 retVal = SaveOption(name);
304 * @brief Set new string value for option and save option to file
306 int CIniOptionsMgr::SaveOption(const String& name, const tchar_t* value)
308 return SaveOption(name, String(value));
311 int CIniOptionsMgr::SaveOption(const String& name, int value)
313 varprop::VariantValue val;
315 int retVal = Set(name, val);
316 if (retVal == COption::OPT_OK)
317 retVal = SaveOption(name);
321 int CIniOptionsMgr::SaveOption(const String& name, bool value)
323 varprop::VariantValue val;
325 int retVal = Set(name, val);
326 if (retVal == COption::OPT_OK)
327 retVal = SaveOption(name);
331 int CIniOptionsMgr::RemoveOption(const String& name)
333 int retVal = COption::OPT_OK;
334 auto [strPath, strValueName] = SplitName(name);
336 if (!strValueName.empty())
338 retVal = COptionsMgr::RemoveOption(name);
342 for (auto it = m_optionsMap.begin(); it != m_optionsMap.end(); )
344 const String& key = it->first;
345 if (key.find(strPath) == 0 && key.length() > strPath.length() && key[strPath.length()] == '/')
347 m_iniFileKeyValues.erase(key);
348 it = m_optionsMap.erase(it);
353 retVal = COption::OPT_OK;
356 auto* pParam = new AsyncWriterThreadParams(name, varprop::VariantValue());
357 InterlockedIncrement(&m_dwQueueCount);
358 if (!PostThreadMessage(m_dwThreadId, WM_USER, (WPARAM)pParam, 0))
359 InterlockedDecrement(&m_dwQueueCount);
364 int CIniOptionsMgr::FlushOptions()
366 int retVal = COption::OPT_OK;
368 while (InterlockedCompareExchange(&m_dwQueueCount, 0, 0) != 0)
374 int CIniOptionsMgr::ExportOptions(const String& filename, const bool bHexColor /*= false*/) const
376 for (auto& [key, value] : m_iniFileKeyValues)
378 if (m_optionsMap.find(key) == m_optionsMap.end())
380 WritePrivateProfileString(_T("WinMerge"), key.c_str(),
381 EscapeValue(value).c_str(), filename.c_str());
384 return COptionsMgr::ExportOptions(filename, bHexColor);
387 int CIniOptionsMgr::ImportOptions(const String& filename)
389 int retVal = COptionsMgr::ImportOptions(filename);
390 auto iniFileMap = Load(filename);
391 for (auto& [key, value] : iniFileMap)
393 if (m_optionsMap.find(key) == m_optionsMap.end())
395 m_iniFileKeyValues.insert_or_assign(key, value);
396 WritePrivateProfileString(_T("WinMerge"), key.c_str(),
397 EscapeValue(value).c_str(), GetFilePath());