OSDN Git Service

Reduce the number of #include <Windows.h> and <tchar.h>.
[winmerge-jp/winmerge-jp.git] / Src / Common / RegOptionsMgr.cpp
1 /** 
2  * @file RegOptionsMgr.cpp
3  *
4  * @brief Implementation of Registry Options management class.
5  *
6  */
7
8 #include "pch.h"
9 #include "RegOptionsMgr.h"
10 #include <windows.h>
11 #include <Shlwapi.h>
12 #include <vector>
13 #include "varprop.h"
14 #include "OptionsMgr.h"
15
16 #define MAX_PATH_FULL 32767
17
18 struct AsyncWriterThreadParams
19 {
20         String name;
21         varprop::VariantValue value;
22 };
23
24 CRegOptionsMgr::CRegOptionsMgr()
25         : m_serializing(true)
26         , m_bCloseHandle(false)
27         , m_dwThreadId(0)
28         , m_hThread(nullptr)
29         , m_dwQueueCount(0)
30 {
31         InitializeCriticalSection(&m_cs);
32         m_hThread = CreateThread(nullptr, 0, AsyncWriterThreadProc, this, 0, &m_dwThreadId);
33 }
34
35 CRegOptionsMgr::~CRegOptionsMgr()
36 {
37         for (;;) {
38                 PostThreadMessage(m_dwThreadId, WM_QUIT, 0, 0);
39                 if (WaitForSingleObject(m_hThread, 1) != WAIT_TIMEOUT)
40                         break;
41         }
42         DeleteCriticalSection(&m_cs);
43 }
44
45 /**
46  * @brief Split option name to path (in registry) and
47  * valuename (in registry).
48  *
49  * Option names are given as "full path", e.g. "Settings/AutomaticRescan".
50  * This function splits that to path "Settings/" and valuename
51  * "AutomaticRescan".
52  * @param [in] strName Option name
53  * @param [out] srPath Path (key) in registry
54  * @param [out] strValue Value in registry
55  */
56 void CRegOptionsMgr::SplitName(const String &strName, String &strPath,
57         String &strValue) const
58 {
59         size_t pos = strName.rfind('/');
60         if (pos != String::npos)
61         {
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);
65         }
66         else
67         {
68                 strValue = strName;
69                 strPath.erase();
70         }
71 }
72
73 HKEY CRegOptionsMgr::OpenKey(const String& strPath, bool bAlwaysCreate)
74 {
75         String strRegPath(m_registryRoot);
76         strRegPath += strPath;
77         HKEY hKey = nullptr;
78         if (m_hKeys.find(strPath) == m_hKeys.end())
79         {
80                 DWORD action = 0;
81                 LONG retValReg;
82                 if (bAlwaysCreate)
83                 {
84                         retValReg = RegCreateKeyEx(HKEY_CURRENT_USER, strRegPath.c_str(),
85                                 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr,
86                                 &hKey, &action);
87                 }
88                 else
89                 {
90                         retValReg = RegOpenKeyEx(HKEY_CURRENT_USER, strRegPath.c_str(),
91                                 0, KEY_ALL_ACCESS, &hKey);
92                 }
93                 if (retValReg != ERROR_SUCCESS)
94                         return nullptr;
95
96                 m_hKeys[strPath] = hKey;
97         }
98         else
99         {
100                 hKey = m_hKeys[strPath];
101         }
102         return hKey;
103 }
104
105 void CRegOptionsMgr::CloseKey(HKEY hKey, const String& strPath)
106 {
107         if (m_bCloseHandle)
108         {
109                 if (hKey)
110                         RegCloseKey(hKey);
111                 m_hKeys.erase(strPath);
112         }
113 }
114
115 void CRegOptionsMgr::CloseKeys()
116 {
117         EnterCriticalSection(&m_cs);
118         for (auto& pair : m_hKeys)
119                 RegCloseKey(pair.second);
120         m_hKeys.clear();
121         LeaveCriticalSection(&m_cs);
122 }
123
124 DWORD WINAPI CRegOptionsMgr::AsyncWriterThreadProc(void *pvThis)
125 {
126         CRegOptionsMgr *pThis = reinterpret_cast<CRegOptionsMgr *>(pvThis);
127         MSG msg;
128         BOOL bRet;
129         while ((bRet = GetMessage(&msg, 0, 0, 0)) != 0)
130         {
131                 AsyncWriterThreadParams *pParam = reinterpret_cast<AsyncWriterThreadParams *>(msg.wParam);
132                 String strPath;
133                 String strValueName;
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);
140                 delete pParam;
141                 InterlockedDecrement(&pThis->m_dwQueueCount);
142         }
143         return 0;
144 }
145
146 /**
147  * @brief Load value from registry.
148  *
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
159  */
160 int CRegOptionsMgr::LoadValueFromReg(HKEY hKey, const String& strName,
161         varprop::VariantValue &value)
162 {
163         String strPath;
164         String strValueName;
165         LONG retValReg = 0;
166         std::vector<BYTE> data;
167         DWORD type = 0;
168         DWORD size = 0;
169         int valType = value.GetType();
170         int retVal = COption::OPT_OK;
171
172         SplitName(strName, strPath, strValueName);
173
174         // Get type and size of value in registry
175         retValReg = RegQueryValueEx(hKey, strValueName.c_str(), 0, &type,
176                 nullptr, &size);
177         
178         if (retValReg == ERROR_SUCCESS)
179         {
180                 data.resize(size + sizeof(TCHAR), 0);
181
182                 // Get data
183                 retValReg = RegQueryValueEx(hKey, strValueName.c_str(),
184                         0, &type, &data[0], &size);
185         }
186         
187         if (retValReg == ERROR_SUCCESS)
188         {
189                 if (type == REG_SZ && valType == varprop::VT_STRING )
190                 {
191                         value.SetString((TCHAR *)&data[0]);
192                         retVal = Set(strName, value);
193                 }
194                 else if (type == REG_DWORD)
195                 {
196                         if (valType == varprop::VT_INT)
197                         {
198                                 DWORD dwordValue;
199                                 CopyMemory(&dwordValue, &data[0], sizeof(DWORD));
200                                 value.SetInt(dwordValue);
201                                 retVal = Set(strName, value);
202                         }
203                         else if (valType == varprop::VT_BOOL)
204                         {
205                                 DWORD dwordValue;
206                                 CopyMemory(&dwordValue, &data[0], sizeof(DWORD));
207                                 value.SetBool(dwordValue > 0 ? true : false);
208                                 retVal = Set(strName, value);
209                         }
210                         else
211                                 retVal = COption::OPT_WRONG_TYPE;
212                 }
213                 else
214                         retVal = COption::OPT_WRONG_TYPE;
215         }
216         else
217                 retVal = COption::OPT_ERR;
218
219         return retVal;
220 }
221
222 /**
223  * @brief Save value to registry.
224  *
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
231  */
232 int CRegOptionsMgr::SaveValueToReg(HKEY hKey, const String& strValueName,
233         const varprop::VariantValue& value)
234 {
235         LONG retValReg = 0;
236         int valType = value.GetType();
237         int retVal = COption::OPT_OK;
238
239         if (valType == varprop::VT_STRING)
240         {
241                 String strVal = value.GetString();
242                 if (strVal.length() > 0)
243                 {
244                         retValReg = RegSetValueEx(hKey, strValueName.c_str(), 0, REG_SZ,
245                                         (LPBYTE)strVal.c_str(), (DWORD)(strVal.length() + 1) * sizeof(TCHAR));
246                 }
247                 else
248                 {
249                         TCHAR str[1] = {0};
250                         retValReg = RegSetValueEx(hKey, strValueName.c_str(), 0, REG_SZ,
251                                         (LPBYTE)&str, 1 * sizeof(TCHAR));
252                 }
253         }
254         else if (valType == varprop::VT_INT)
255         {
256                 DWORD dwordVal = value.GetInt();
257                 retValReg = RegSetValueEx(hKey, strValueName.c_str(), 0, REG_DWORD,
258                                 (LPBYTE)&dwordVal, sizeof(DWORD));
259         }
260         else if (valType == varprop::VT_BOOL)
261         {
262                 DWORD dwordVal = value.GetBool() ? 1 : 0;
263                 retValReg = RegSetValueEx(hKey, strValueName.c_str(), 0, REG_DWORD,
264                                 (LPBYTE)&dwordVal, sizeof(DWORD));
265         }
266         else
267         {
268                 retVal = COption::OPT_UNKNOWN_TYPE;
269         }
270                 
271         if (retValReg != ERROR_SUCCESS)
272         {
273                 retVal = COption::OPT_ERR;
274         }
275         return retVal;
276 }
277
278 /**
279  * @brief Init and add new option.
280  *
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.
283  */
284 int CRegOptionsMgr::InitOption(const String& name, const varprop::VariantValue& defaultValue)
285 {
286         // Check type & bail if null
287         int valType = defaultValue.GetType();
288         if (valType == varprop::VT_NULL)
289                 return COption::OPT_ERR;
290
291         // If we're not loading & saving options, bail
292         if (!m_serializing)
293                 return AddOption(name, defaultValue);
294
295         // Figure out registry path, for saving value
296         String strPath;
297         String strValueName;
298         SplitName(name, strPath, strValueName);
299
300         // Open key.
301         EnterCriticalSection(&m_cs);
302         HKEY hKey = OpenKey(strPath, false);
303
304         // Check previous value
305         // This just checks if the value exists, LoadValueFromReg() below actually
306         // loads the value.
307         DWORD type = 0;
308         DWORD size = MAX_PATH_FULL;
309         LONG retValReg;
310         if (hKey)
311         {
312                 BYTE dataBuf[MAX_PATH_FULL];
313                 dataBuf[0] = 0;
314                 retValReg = RegQueryValueEx(hKey, strValueName.c_str(),
315                         0, &type, dataBuf, &size);
316         }
317         else
318                 retValReg = ERROR_FILE_NOT_FOUND;
319
320         // Actually save value into our in-memory options table
321         int retVal = AddOption(name, defaultValue);
322         
323         // Update registry if successfully saved to in-memory table
324         if (retVal == COption::OPT_OK)
325         {
326                 // Value didn't exist. Do nothing
327                 if (retValReg == ERROR_FILE_NOT_FOUND)
328                 {
329                 }
330                 // Value already exists so read it.
331                 else if (retValReg == ERROR_SUCCESS || retValReg == ERROR_MORE_DATA)
332                 {
333                         varprop::VariantValue value(defaultValue);
334                         retVal = LoadValueFromReg(hKey, name, value);
335                         if (retVal == COption::OPT_OK)
336                                 retVal = Set(name, value);
337                 }
338         }
339
340         CloseKey(hKey, strPath);
341         LeaveCriticalSection(&m_cs);
342         return retVal;
343 }
344
345 /**
346  * @brief Init and add new string option.
347  *
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.
350  */
351 int CRegOptionsMgr::InitOption(const String& name, const String& defaultValue)
352 {
353         varprop::VariantValue defValue;
354         defValue.SetString(defaultValue);
355         return InitOption(name, defValue);
356 }
357
358 int CRegOptionsMgr::InitOption(const String& name, const TCHAR *defaultValue)
359 {
360         return InitOption(name, String(defaultValue));
361 }
362
363 /**
364  * @brief Init and add new int option.
365  *
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.
368  */
369 int CRegOptionsMgr::InitOption(const String& name, int defaultValue, bool serializable)
370 {
371         varprop::VariantValue defValue;
372         int retVal = COption::OPT_OK;
373         
374         defValue.SetInt(defaultValue);
375         if (serializable)
376                 retVal = InitOption(name, defValue);
377         else
378                 AddOption(name, defValue);
379         return retVal;
380 }
381
382 /**
383  * @brief Init and add new boolean option.
384  *
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.
387  */
388 int CRegOptionsMgr::InitOption(const String& name, bool defaultValue)
389 {
390         varprop::VariantValue defValue;
391         defValue.SetBool(defaultValue);
392         return InitOption(name, defValue);
393 }
394
395 /**
396  * @brief Save option to registry
397  * @note Currently handles only integer and string options!
398  */
399 int CRegOptionsMgr::SaveOption(const String& name)
400 {
401         if (!m_serializing) return COption::OPT_OK;
402
403         varprop::VariantValue value;
404         int retVal = COption::OPT_OK;
405
406         value = Get(name);
407         int valType = value.GetType();
408         if (valType == varprop::VT_NULL)
409                 retVal = COption::OPT_NOTFOUND;
410         
411         if (retVal == COption::OPT_OK)
412         {
413                 AsyncWriterThreadParams *pParam = new AsyncWriterThreadParams();
414                 pParam->name = name;
415                 pParam->value = value;
416                 InterlockedIncrement(&m_dwQueueCount);
417                 PostThreadMessage(m_dwThreadId, WM_USER, (WPARAM)pParam, 0);
418         }
419         return retVal;
420 }
421
422 /**
423  * @brief Set new value for option and save option to registry
424  */
425 int CRegOptionsMgr::SaveOption(const String& name, const varprop::VariantValue& value)
426 {
427         int retVal = Set(name, value);
428         if (retVal == COption::OPT_OK)
429                 retVal = SaveOption(name);
430         return retVal;
431 }
432
433 /**
434  * @brief Set new string value for option and save option to registry
435  */
436 int CRegOptionsMgr::SaveOption(const String& name, const String& value)
437 {
438         varprop::VariantValue val;
439         val.SetString(value);
440         int retVal = Set(name, val);
441         if (retVal == COption::OPT_OK)
442                 retVal = SaveOption(name);
443         return retVal;
444 }
445
446 /**
447  * @brief Set new string value for option and save option to registry
448  */
449 int CRegOptionsMgr::SaveOption(const String& name, const TCHAR *value)
450 {
451         return SaveOption(name, String(value));
452 }
453
454 /**
455  * @brief Set new integer value for option and save option to registry
456  */
457 int CRegOptionsMgr::SaveOption(const String& name, int value)
458 {
459         varprop::VariantValue val;
460         val.SetInt(value);
461         int retVal = Set(name, val);
462         if (retVal == COption::OPT_OK)
463                 retVal = SaveOption(name);
464         return retVal;
465 }
466
467 /**
468  * @brief Set new boolean value for option and save option to registry
469  */
470 int CRegOptionsMgr::SaveOption(const String& name, bool value)
471 {
472         varprop::VariantValue val;
473         val.SetBool(value);
474         int retVal = Set(name, val);
475         if (retVal == COption::OPT_OK)
476                 retVal = SaveOption(name);
477         return retVal;
478 }
479
480 int CRegOptionsMgr::RemoveOption(const String& name)
481 {
482         int retVal = COption::OPT_OK;
483
484         String strPath;
485         String strValueName;
486
487         SplitName(name, strPath, strValueName);
488
489         if (!strValueName.empty())
490         {
491                 retVal = COptionsMgr::RemoveOption(name);
492         }
493         else
494         {
495                 for (auto it = m_optionsMap.begin(); it != m_optionsMap.end(); )
496                 {
497                         if (it->first.find(strPath) == 0)
498                                 it = m_optionsMap.erase(it);
499                         else 
500                                 ++it;
501                 }
502                 retVal = COption::OPT_OK;
503         }
504
505         while (InterlockedCompareExchange(&m_dwQueueCount, 0, 0) != 0)
506                 Sleep(0);
507
508         EnterCriticalSection(&m_cs);
509         HKEY hKey = OpenKey(strPath, true);
510         if (strValueName.empty())
511 #ifdef _WIN64
512                 RegDeleteTree(hKey, nullptr);
513 #else
514                 SHDeleteKey(hKey, nullptr);
515 #endif
516         else
517                 RegDeleteValue(hKey, strValueName.c_str());
518         CloseKey(hKey, strPath);
519         LeaveCriticalSection(&m_cs);
520
521         return retVal;
522
523 }
524
525 /**
526  * @brief Set registry root path for options.
527  *
528  * Sets path used as root path when loading/saving options. Paths
529  * given to other functions are relative to this path.
530  */
531 int CRegOptionsMgr::SetRegRootKey(const String& key)
532 {
533         String keyname(key);
534         HKEY hKey = nullptr;
535         DWORD action = 0;
536         int retVal = COption::OPT_OK;
537
538         size_t ind = keyname.find(_T("Software"));
539         if (ind != 0)
540                 keyname.insert(0, _T("Software\\"));
541         
542         m_registryRoot = keyname;
543
544         LONG retValReg =  RegCreateKeyEx(HKEY_CURRENT_USER, m_registryRoot.c_str(), 0, nullptr,
545                 REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr, &hKey, &action);
546
547         if (retValReg == ERROR_SUCCESS)
548         {
549                 if (action == REG_CREATED_NEW_KEY)
550                 {
551                         // TODO: At least log message..?
552                 }
553                 RegCloseKey(hKey);
554         }
555         else
556         {
557                 retVal = COption::OPT_ERR;
558         }
559
560         return retVal;
561 }
562
563 /**
564  * @brief Export options to file.
565  *
566  * This function enumerates through our options storage and saves
567  * every option name and value to file.
568  *
569  * @param [in] filename Filename where optios are written.
570  * @return
571  * - COption::OPT_OK when succeeds
572  * - COption::OPT_ERR when writing to the file fails
573  */
574 int CRegOptionsMgr::ExportOptions(const String& filename, const bool bHexColor /*= false*/) const
575 {
576         int retVal = COption::OPT_OK;
577         OptionsMap::const_iterator optIter = m_optionsMap.begin();
578         while (optIter != m_optionsMap.end() && retVal == COption::OPT_OK)
579         {
580                 const String name(optIter->first);
581                 String strVal;
582                 varprop::VariantValue value = optIter->second.Get();
583                 if (value.GetType() == varprop::VT_BOOL)
584                 {
585                         if (value.GetBool())
586                                 strVal = _T("1");
587                         else
588                                 strVal = _T("0");
589                 }
590                 else if (value.GetType() == varprop::VT_INT)
591                 {
592                         if ( bHexColor && (strutils::makelower(name).find(String(_T("color"))) != std::string::npos) )
593                                 strVal = strutils::format(_T("0x%06x"), value.GetInt());
594                         else
595                                 strVal = strutils::to_str(value.GetInt());
596                 }
597                 else if (value.GetType() == varprop::VT_STRING)
598                 {
599                         strVal = value.GetString();
600                 }
601
602                 bool bRet = !!WritePrivateProfileString(_T("WinMerge"), name.c_str(),
603                                 strVal.c_str(), filename.c_str());
604                 if (!bRet)
605                         retVal = COption::OPT_ERR;
606                 ++optIter;
607         }
608         return retVal;
609 }
610
611 /**
612  * @brief Import options from file.
613  *
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.
617  *
618  * @param [in] filename Filename where optios are written.
619  * @return
620  * - COption::OPT_OK when succeeds
621  * - COption::OPT_NOTFOUND if file wasn't found or didn't contain values
622  */
623 int CRegOptionsMgr::ImportOptions(const String& filename)
624 {
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; };
629
630         // Query keys - returns NUL separated strings
631         DWORD len = GetPrivateProfileString(_T("WinMerge"), nullptr, _T(""),buf, BufSize, filename.c_str());
632         if (len == 0)
633                 return COption::OPT_NOTFOUND;
634
635         TCHAR *pKey = buf;
636         while (*pKey != '\0')
637         {
638                 varprop::VariantValue value = Get(pKey);
639                 if (value.GetType() == varprop::VT_BOOL)
640                 {
641                         bool boolVal = GetPrivateProfileInt(_T("WinMerge"), pKey, 0, filename.c_str()) == 1;
642                         value.SetBool(boolVal);
643                         SaveOption(pKey, boolVal);
644                 }
645                 else if (value.GetType() == varprop::VT_INT)
646                 {
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);
652                 }
653                 else if (value.GetType() == varprop::VT_STRING)
654                 {
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);
659                 }
660                 Set(pKey, value);
661
662                 pKey += _tcslen(pKey);
663
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'))
667                         pKey++;
668         }
669         return retVal;
670 }