2 * @file RegOptionsMgr.cpp
4 * @brief Implementation of Registry Options management class.
9 #include "RegOptionsMgr.h"
14 #include "OptionsMgr.h"
16 #define MAX_PATH_FULL 32767
18 struct AsyncWriterThreadParams
21 varprop::VariantValue value;
24 CRegOptionsMgr::CRegOptionsMgr()
26 , m_bCloseHandle(false)
31 InitializeCriticalSection(&m_cs);
32 m_hThread = CreateThread(nullptr, 0, AsyncWriterThreadProc, this, 0, &m_dwThreadId);
35 CRegOptionsMgr::~CRegOptionsMgr()
38 PostThreadMessage(m_dwThreadId, WM_QUIT, 0, 0);
39 if (WaitForSingleObject(m_hThread, 1) != WAIT_TIMEOUT)
42 DeleteCriticalSection(&m_cs);
46 * @brief Split option name to path (in registry) and
47 * valuename (in registry).
49 * Option names are given as "full path", e.g. "Settings/AutomaticRescan".
50 * This function splits that to path "Settings/" and valuename
52 * @param [in] strName Option name
53 * @param [out] srPath Path (key) in registry
54 * @param [out] strValue Value in registry
56 void CRegOptionsMgr::SplitName(const String &strName, String &strPath,
57 String &strValue) const
59 size_t pos = strName.rfind('/');
60 if (pos != String::npos)
62 size_t len = strName.length();
63 strValue = strName.substr(pos + 1, len - pos - 1); //Right(len - pos - 1);
64 strPath = strName.substr(0, pos); //Left(pos);
73 HKEY CRegOptionsMgr::OpenKey(const String& strPath, bool bAlwaysCreate)
75 String strRegPath(m_registryRoot);
76 strRegPath += strPath;
78 if (m_hKeys.find(strPath) == m_hKeys.end())
84 retValReg = RegCreateKeyEx(HKEY_CURRENT_USER, strRegPath.c_str(),
85 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr,
90 retValReg = RegOpenKeyEx(HKEY_CURRENT_USER, strRegPath.c_str(),
91 0, KEY_ALL_ACCESS, &hKey);
93 if (retValReg != ERROR_SUCCESS)
96 m_hKeys[strPath] = hKey;
100 hKey = m_hKeys[strPath];
105 void CRegOptionsMgr::CloseKey(HKEY hKey, const String& strPath)
111 m_hKeys.erase(strPath);
115 void CRegOptionsMgr::CloseKeys()
117 EnterCriticalSection(&m_cs);
118 for (auto& pair : m_hKeys)
119 RegCloseKey(pair.second);
121 LeaveCriticalSection(&m_cs);
124 DWORD WINAPI CRegOptionsMgr::AsyncWriterThreadProc(void *pvThis)
126 CRegOptionsMgr *pThis = reinterpret_cast<CRegOptionsMgr *>(pvThis);
129 while ((bRet = GetMessage(&msg, 0, 0, 0)) != 0)
131 AsyncWriterThreadParams *pParam = reinterpret_cast<AsyncWriterThreadParams *>(msg.wParam);
134 pThis->SplitName(pParam->name, strPath, strValueName);
135 EnterCriticalSection(&pThis->m_cs);
136 HKEY hKey = pThis->OpenKey(strPath, true);
137 SaveValueToReg(hKey, strValueName, pParam->value);
138 pThis->CloseKey(hKey, strPath);
139 LeaveCriticalSection(&pThis->m_cs);
141 InterlockedDecrement(&pThis->m_dwQueueCount);
147 * @brief Load value from registry.
149 * Loads one value from registry from key previously opened. Type
150 * is read from value parameter and error is returned if value
151 * cannot be found or if it is different type than value parameter.
152 * @param [in] hKey Handle to open registry key
153 * @param [in] strName Name of value to read (incl. path!).
154 * @param [in, out] value
155 * [in] Values type must match to value type in registry
156 * [out] Read value is returned
157 * @note This function must handle ANSI and UNICODE data!
158 * @todo Handles only string and integer types
160 int CRegOptionsMgr::LoadValueFromReg(HKEY hKey, const String& strName,
161 varprop::VariantValue &value)
166 std::vector<BYTE> data;
169 int valType = value.GetType();
170 int retVal = COption::OPT_OK;
172 SplitName(strName, strPath, strValueName);
174 // Get type and size of value in registry
175 retValReg = RegQueryValueEx(hKey, strValueName.c_str(), 0, &type,
178 if (retValReg == ERROR_SUCCESS)
180 data.resize(size + sizeof(TCHAR), 0);
183 retValReg = RegQueryValueEx(hKey, strValueName.c_str(),
184 0, &type, &data[0], &size);
187 if (retValReg == ERROR_SUCCESS)
189 if (type == REG_SZ && valType == varprop::VT_STRING )
191 value.SetString((TCHAR *)&data[0]);
192 retVal = Set(strName, value);
194 else if (type == REG_DWORD)
196 if (valType == varprop::VT_INT)
199 CopyMemory(&dwordValue, &data[0], sizeof(DWORD));
200 value.SetInt(dwordValue);
201 retVal = Set(strName, value);
203 else if (valType == varprop::VT_BOOL)
206 CopyMemory(&dwordValue, &data[0], sizeof(DWORD));
207 value.SetBool(dwordValue > 0 ? true : false);
208 retVal = Set(strName, value);
211 retVal = COption::OPT_WRONG_TYPE;
214 retVal = COption::OPT_WRONG_TYPE;
217 retVal = COption::OPT_ERR;
223 * @brief Save value to registry.
225 * Saves one value to registry to key previously opened. Type of
226 * value is determined from given value parameter.
227 * @param [in] hKey Handle to open registry key
228 * @param [in] strValueName Name of value to write
229 * @param [in] value value to write to registry value
230 * @todo Handles only string and integer types
232 int CRegOptionsMgr::SaveValueToReg(HKEY hKey, const String& strValueName,
233 const varprop::VariantValue& value)
236 int valType = value.GetType();
237 int retVal = COption::OPT_OK;
239 if (valType == varprop::VT_STRING)
241 String strVal = value.GetString();
242 if (strVal.length() > 0)
244 retValReg = RegSetValueEx(hKey, strValueName.c_str(), 0, REG_SZ,
245 (LPBYTE)strVal.c_str(), (DWORD)(strVal.length() + 1) * sizeof(TCHAR));
250 retValReg = RegSetValueEx(hKey, strValueName.c_str(), 0, REG_SZ,
251 (LPBYTE)&str, 1 * sizeof(TCHAR));
254 else if (valType == varprop::VT_INT)
256 DWORD dwordVal = value.GetInt();
257 retValReg = RegSetValueEx(hKey, strValueName.c_str(), 0, REG_DWORD,
258 (LPBYTE)&dwordVal, sizeof(DWORD));
260 else if (valType == varprop::VT_BOOL)
262 DWORD dwordVal = value.GetBool() ? 1 : 0;
263 retValReg = RegSetValueEx(hKey, strValueName.c_str(), 0, REG_DWORD,
264 (LPBYTE)&dwordVal, sizeof(DWORD));
268 retVal = COption::OPT_UNKNOWN_TYPE;
271 if (retValReg != ERROR_SUCCESS)
273 retVal = COption::OPT_ERR;
279 * @brief Init and add new option.
281 * Adds new option to list of options. Sets value to default value.
282 * If option does not exist in registry, saves with default value.
284 int CRegOptionsMgr::InitOption(const String& name, const varprop::VariantValue& defaultValue)
286 // Check type & bail if null
287 int valType = defaultValue.GetType();
288 if (valType == varprop::VT_NULL)
289 return COption::OPT_ERR;
291 // If we're not loading & saving options, bail
293 return AddOption(name, defaultValue);
295 // Figure out registry path, for saving value
298 SplitName(name, strPath, strValueName);
301 EnterCriticalSection(&m_cs);
302 HKEY hKey = OpenKey(strPath, false);
304 // Check previous value
305 // This just checks if the value exists, LoadValueFromReg() below actually
308 DWORD size = MAX_PATH_FULL;
312 BYTE dataBuf[MAX_PATH_FULL];
314 retValReg = RegQueryValueEx(hKey, strValueName.c_str(),
315 0, &type, dataBuf, &size);
318 retValReg = ERROR_FILE_NOT_FOUND;
320 // Actually save value into our in-memory options table
321 int retVal = AddOption(name, defaultValue);
323 // Update registry if successfully saved to in-memory table
324 if (retVal == COption::OPT_OK)
326 // Value didn't exist. Do nothing
327 if (retValReg == ERROR_FILE_NOT_FOUND)
330 // Value already exists so read it.
331 else if (retValReg == ERROR_SUCCESS || retValReg == ERROR_MORE_DATA)
333 varprop::VariantValue value(defaultValue);
334 retVal = LoadValueFromReg(hKey, name, value);
335 if (retVal == COption::OPT_OK)
336 retVal = Set(name, value);
340 CloseKey(hKey, strPath);
341 LeaveCriticalSection(&m_cs);
346 * @brief Init and add new string option.
348 * Adds new option to list of options. Sets value to default value.
349 * If option does not exist in registry, saves with default value.
351 int CRegOptionsMgr::InitOption(const String& name, const String& defaultValue)
353 varprop::VariantValue defValue;
354 defValue.SetString(defaultValue);
355 return InitOption(name, defValue);
358 int CRegOptionsMgr::InitOption(const String& name, const TCHAR *defaultValue)
360 return InitOption(name, String(defaultValue));
364 * @brief Init and add new int option.
366 * Adds new option to list of options. Sets value to default value.
367 * If option does not exist in registry, saves with default value.
369 int CRegOptionsMgr::InitOption(const String& name, int defaultValue, bool serializable)
371 varprop::VariantValue defValue;
372 int retVal = COption::OPT_OK;
374 defValue.SetInt(defaultValue);
376 retVal = InitOption(name, defValue);
378 AddOption(name, defValue);
383 * @brief Init and add new boolean option.
385 * Adds new option to list of options. Sets value to default value.
386 * If option does not exist in registry, saves with default value.
388 int CRegOptionsMgr::InitOption(const String& name, bool defaultValue)
390 varprop::VariantValue defValue;
391 defValue.SetBool(defaultValue);
392 return InitOption(name, defValue);
396 * @brief Save option to registry
397 * @note Currently handles only integer and string options!
399 int CRegOptionsMgr::SaveOption(const String& name)
401 if (!m_serializing) return COption::OPT_OK;
403 varprop::VariantValue value;
404 int retVal = COption::OPT_OK;
407 int valType = value.GetType();
408 if (valType == varprop::VT_NULL)
409 retVal = COption::OPT_NOTFOUND;
411 if (retVal == COption::OPT_OK)
413 AsyncWriterThreadParams *pParam = new AsyncWriterThreadParams();
415 pParam->value = value;
416 InterlockedIncrement(&m_dwQueueCount);
417 PostThreadMessage(m_dwThreadId, WM_USER, (WPARAM)pParam, 0);
423 * @brief Set new value for option and save option to registry
425 int CRegOptionsMgr::SaveOption(const String& name, const varprop::VariantValue& value)
427 int retVal = Set(name, value);
428 if (retVal == COption::OPT_OK)
429 retVal = SaveOption(name);
434 * @brief Set new string value for option and save option to registry
436 int CRegOptionsMgr::SaveOption(const String& name, const String& value)
438 varprop::VariantValue val;
439 val.SetString(value);
440 int retVal = Set(name, val);
441 if (retVal == COption::OPT_OK)
442 retVal = SaveOption(name);
447 * @brief Set new string value for option and save option to registry
449 int CRegOptionsMgr::SaveOption(const String& name, const TCHAR *value)
451 return SaveOption(name, String(value));
455 * @brief Set new integer value for option and save option to registry
457 int CRegOptionsMgr::SaveOption(const String& name, int value)
459 varprop::VariantValue val;
461 int retVal = Set(name, val);
462 if (retVal == COption::OPT_OK)
463 retVal = SaveOption(name);
468 * @brief Set new boolean value for option and save option to registry
470 int CRegOptionsMgr::SaveOption(const String& name, bool value)
472 varprop::VariantValue val;
474 int retVal = Set(name, val);
475 if (retVal == COption::OPT_OK)
476 retVal = SaveOption(name);
480 int CRegOptionsMgr::RemoveOption(const String& name)
482 int retVal = COption::OPT_OK;
487 SplitName(name, strPath, strValueName);
489 if (!strValueName.empty())
491 retVal = COptionsMgr::RemoveOption(name);
495 for (auto it = m_optionsMap.begin(); it != m_optionsMap.end(); )
497 if (it->first.find(strPath) == 0)
498 it = m_optionsMap.erase(it);
502 retVal = COption::OPT_OK;
505 while (InterlockedCompareExchange(&m_dwQueueCount, 0, 0) != 0)
508 EnterCriticalSection(&m_cs);
509 HKEY hKey = OpenKey(strPath, true);
510 if (strValueName.empty())
512 RegDeleteTree(hKey, nullptr);
514 SHDeleteKey(hKey, nullptr);
517 RegDeleteValue(hKey, strValueName.c_str());
518 CloseKey(hKey, strPath);
519 LeaveCriticalSection(&m_cs);
526 * @brief Set registry root path for options.
528 * Sets path used as root path when loading/saving options. Paths
529 * given to other functions are relative to this path.
531 int CRegOptionsMgr::SetRegRootKey(const String& key)
536 int retVal = COption::OPT_OK;
538 size_t ind = keyname.find(_T("Software"));
540 keyname.insert(0, _T("Software\\"));
542 m_registryRoot = keyname;
544 LONG retValReg = RegCreateKeyEx(HKEY_CURRENT_USER, m_registryRoot.c_str(), 0, nullptr,
545 REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr, &hKey, &action);
547 if (retValReg == ERROR_SUCCESS)
549 if (action == REG_CREATED_NEW_KEY)
551 // TODO: At least log message..?
557 retVal = COption::OPT_ERR;
564 * @brief Export options to file.
566 * This function enumerates through our options storage and saves
567 * every option name and value to file.
569 * @param [in] filename Filename where optios are written.
571 * - COption::OPT_OK when succeeds
572 * - COption::OPT_ERR when writing to the file fails
574 int CRegOptionsMgr::ExportOptions(const String& filename, const bool bHexColor /*= false*/) const
576 int retVal = COption::OPT_OK;
577 OptionsMap::const_iterator optIter = m_optionsMap.begin();
578 while (optIter != m_optionsMap.end() && retVal == COption::OPT_OK)
580 const String name(optIter->first);
582 varprop::VariantValue value = optIter->second.Get();
583 if (value.GetType() == varprop::VT_BOOL)
590 else if (value.GetType() == varprop::VT_INT)
592 if ( bHexColor && (strutils::makelower(name).find(String(_T("color"))) != std::string::npos) )
593 strVal = strutils::format(_T("0x%06x"), value.GetInt());
595 strVal = strutils::to_str(value.GetInt());
597 else if (value.GetType() == varprop::VT_STRING)
599 strVal = value.GetString();
602 bool bRet = !!WritePrivateProfileString(_T("WinMerge"), name.c_str(),
603 strVal.c_str(), filename.c_str());
605 retVal = COption::OPT_ERR;
612 * @brief Import options from file.
614 * This function reads options values and names from given file and
615 * updates values to our options storage. If valuename does not exist
616 * already in options storage its is not created.
618 * @param [in] filename Filename where optios are written.
620 * - COption::OPT_OK when succeeds
621 * - COption::OPT_NOTFOUND if file wasn't found or didn't contain values
623 int CRegOptionsMgr::ImportOptions(const String& filename)
625 int retVal = COption::OPT_OK;
626 const int BufSize = 20480; // This should be enough for a long time..
627 TCHAR buf[BufSize] = {0};
628 auto oleTranslateColor = [](unsigned color) -> unsigned { return ((color & 0xffffff00) == 0x80000000) ? GetSysColor(color & 0x000000ff) : color; };
630 // Query keys - returns NUL separated strings
631 DWORD len = GetPrivateProfileString(_T("WinMerge"), nullptr, _T(""),buf, BufSize, filename.c_str());
633 return COption::OPT_NOTFOUND;
636 while (*pKey != '\0')
638 varprop::VariantValue value = Get(pKey);
639 if (value.GetType() == varprop::VT_BOOL)
641 bool boolVal = GetPrivateProfileInt(_T("WinMerge"), pKey, 0, filename.c_str()) == 1;
642 value.SetBool(boolVal);
643 SaveOption(pKey, boolVal);
645 else if (value.GetType() == varprop::VT_INT)
647 int intVal = GetPrivateProfileInt(_T("WinMerge"), pKey, 0, filename.c_str());
648 if (strutils::makelower(pKey).find(String(_T("color"))) != std::string::npos)
649 intVal = static_cast<int>(oleTranslateColor(static_cast<unsigned>(intVal)));
650 value.SetInt(intVal);
651 SaveOption(pKey, intVal);
653 else if (value.GetType() == varprop::VT_STRING)
655 TCHAR strVal[MAX_PATH_FULL] = {0};
656 GetPrivateProfileString(_T("WinMerge"), pKey, _T(""), strVal, MAX_PATH_FULL, filename.c_str());
657 value.SetString(strVal);
658 SaveOption(pKey, strVal);
662 pKey += _tcslen(pKey);
664 // Check: pointer is not past string end, and next char is not null
665 // double NUL char ends the keynames string
666 if ((pKey < buf + len) && (*(pKey + 1) != '\0'))