OSDN Git Service

0e5164ecb29786485361bf727e0a7e8952d7ee73
[winmerge-jp/winmerge-jp.git] / Src / Common / IniOptionsMgr.cpp
1 /**
2  * @file IniOptionsMgr.cpp
3  *
4  * @brief Implementation of Ini file Options management class.
5  *
6  */
7
8 #include "pch.h"
9 #include "IniOptionsMgr.h"
10 #include <Windows.h>
11 #include <process.h>
12 #include "OptionsMgr.h"
13
14 LPCWSTR lpAppName = TEXT("WinMerge");
15 LPCWSTR lpDefaultSection = TEXT("Defaults");
16
17 struct AsyncWriterThreadParams
18 {
19         AsyncWriterThreadParams(const String& name, const varprop::VariantValue& value) : name(name), value(value) {}
20         String name;
21         varprop::VariantValue value;
22 };
23
24 CIniOptionsMgr::CIniOptionsMgr(const String& filePath)
25         : m_serializing(true)
26         , m_filePath{filePath}
27         , m_dwThreadId(0)
28         , m_hThread(nullptr)
29         , m_hEvent(nullptr)
30         , m_dwQueueCount(0)
31 {
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);
39         m_hEvent = nullptr;
40 }
41
42 CIniOptionsMgr::~CIniOptionsMgr()
43 {
44         for (;;) {
45                 PostThreadMessage(m_dwThreadId, WM_QUIT, 0, 0);
46                 if (WaitForSingleObject(m_hThread, 1) != WAIT_TIMEOUT)
47                         break;
48         }
49 }
50
51 unsigned __stdcall CIniOptionsMgr::AsyncWriterThreadProc(void *pvThis)
52 {
53         CIniOptionsMgr *pThis = reinterpret_cast<CIniOptionsMgr *>(pvThis);
54         MSG msg;
55         BOOL bRet;
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)
60         {
61                 auto* pParam = reinterpret_cast<AsyncWriterThreadParams *>(msg.wParam);
62                 if (msg.message == WM_USER && pParam)
63                 {
64                         pThis->SaveValueToFile(pParam->name, pParam->value);
65                         delete pParam;
66                         InterlockedDecrement(&pThis->m_dwQueueCount);
67                 }
68         }
69         return 0;
70 }
71
72 std::map<String, String> CIniOptionsMgr::Load(const String& iniFilePath)
73 {
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)
77         {
78                 const tchar_t* p = str.data();
79                 while (*p)
80                 {
81                         const tchar_t* v = tc::tcschr(p, '=');
82                         if (!v)
83                                 break;
84                         ++v;
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));
89                         p = v + vlen + 1;
90                 }
91         }
92         
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)
95         {
96                 const tchar_t* p = str.data();
97                 while (*p)
98                 {
99                         const tchar_t* v = tc::tcschr(p, '=');
100                         if (!v)
101                                 break;
102                         ++v;
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));
107                         p = v + vlen + 1;
108                 }
109         }
110
111         return iniFileKeyValues;
112 }
113
114 int CIniOptionsMgr::LoadValueFromBuf(const String& strName, const String& textValue, varprop::VariantValue& value)
115 {
116         int retVal = COption::OPT_OK;
117         int valType = value.GetType();
118         if (valType == varprop::VT_STRING)
119         {
120                 value.SetString(textValue);
121                 retVal = Set(strName, value);
122         }
123         else if (valType == varprop::VT_INT)
124         {
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);
130         }
131         else if (valType == varprop::VT_BOOL)
132         {
133                 value.SetBool(textValue[0] == '1' ? true : false);
134                 retVal = Set(strName, value);
135         }
136         else
137                 retVal = COption::OPT_WRONG_TYPE;
138
139         return retVal;
140 }
141
142 int CIniOptionsMgr::SaveValueToFile(const String& name, const varprop::VariantValue& value)
143 {
144         BOOL retValReg = TRUE;
145         int valType = value.GetType();
146         int retVal = COption::OPT_OK;
147
148         if (valType == varprop::VT_STRING)
149         {
150                 String strVal = EscapeValue(value.GetString());
151                 LPCWSTR text = strVal.c_str();
152                 retValReg = WritePrivateProfileString(lpAppName, name.c_str(), text, GetFilePath());
153         }
154         else if (valType == varprop::VT_INT)
155         {
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());
160         }
161         else if (valType == varprop::VT_BOOL)
162         {
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());
167         }
168         else if (valType == varprop::VT_NULL)
169         {
170                 auto [strPath, strValueName] = SplitName(name);
171                 if (!strValueName.empty())
172                         retValReg = WritePrivateProfileString(lpAppName, name.c_str(), nullptr, GetFilePath());
173                 else
174                 {
175                         auto iniFileMap = Load(GetFilePath());
176                         for (auto& [key, value2] : iniFileMap)
177                         {
178                                 if (key.find(strPath) == 0 && key.length() > strPath.length() && key[strPath.length()] == '/')
179                                         retValReg = WritePrivateProfileString(lpAppName, key.c_str(), nullptr, GetFilePath());
180                         }
181                 }
182         }
183         else
184         {
185                 retVal = COption::OPT_UNKNOWN_TYPE;
186         }
187                 
188         if (!retValReg)
189         {
190                 retVal = COption::OPT_ERR;
191         }
192         return retVal;
193 }
194
195 int CIniOptionsMgr::InitOption(const String& name, const varprop::VariantValue& defaultValue)
196 {
197         // Check type & bail if null
198         int valType = defaultValue.GetType();
199         if (valType == varprop::VT_NULL)
200                 return COption::OPT_ERR;
201
202         // If we're not loading & saving options, bail
203         if (!m_serializing)
204                 return AddOption(name, defaultValue);
205
206         // Actually save value into our in-memory options table
207         int retVal = AddOption(name, defaultValue);
208
209         // Update registry if successfully saved to in-memory table
210         if (retVal == COption::OPT_OK)
211         {
212                 // check if value exist
213                 bool found = m_iniFileKeyValues.find(name) != m_iniFileKeyValues.end();
214                 if (found)
215                 {
216                         String textValue = m_iniFileKeyValues[name];
217                         varprop::VariantValue value(defaultValue);
218                         retVal = LoadValueFromBuf(name, textValue, value);
219                 }
220         }
221
222         return retVal;
223 }
224
225 int CIniOptionsMgr::InitOption(const String& name, const String& defaultValue)
226 {
227         varprop::VariantValue defValue;
228         defValue.SetString(defaultValue);
229         return InitOption(name, defValue);
230 }
231
232 int CIniOptionsMgr::InitOption(const String& name, const tchar_t* defaultValue)
233 {
234         return InitOption(name, String(defaultValue));
235 }
236
237 int CIniOptionsMgr::InitOption(const String& name, int defaultValue, bool serializable)
238 {
239         varprop::VariantValue defValue;
240         int retVal = COption::OPT_OK;
241
242         defValue.SetInt(defaultValue);
243         if (serializable)
244                 retVal = InitOption(name, defValue);
245         else
246                 AddOption(name, defValue);
247         return retVal;
248 }
249
250 int CIniOptionsMgr::InitOption(const String& name, bool defaultValue)
251 {
252         varprop::VariantValue defValue;
253         defValue.SetBool(defaultValue);
254         return InitOption(name, defValue);
255 }
256
257 int CIniOptionsMgr::SaveOption(const String& name)
258 {
259         if (!m_serializing) return COption::OPT_OK;
260
261         varprop::VariantValue value;
262         int retVal = COption::OPT_OK;
263
264         value = Get(name);
265         int valType = value.GetType();
266         if (valType == varprop::VT_NULL)
267                 retVal = COption::OPT_NOTFOUND;
268
269         if (retVal == COption::OPT_OK)
270         {
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);
275         }
276         return retVal;
277 }
278
279 /**
280  * @brief Set new value for option and save option to file
281  */
282 int CIniOptionsMgr::SaveOption(const String& name, const varprop::VariantValue& value)
283 {
284         int retVal = Set(name, value);
285         if (retVal == COption::OPT_OK)
286                 retVal = SaveOption(name);
287         return retVal;
288 }
289
290 /**
291  * @brief Set new string value for option and save option to file
292  */
293 int CIniOptionsMgr::SaveOption(const String& name, const String& value)
294 {
295         varprop::VariantValue val;
296         val.SetString(value);
297         int retVal = Set(name, val);
298         if (retVal == COption::OPT_OK)
299                 retVal = SaveOption(name);
300         return retVal;
301 }
302
303 /**
304  * @brief Set new string value for option and save option to file
305  */
306 int CIniOptionsMgr::SaveOption(const String& name, const tchar_t* value)
307 {
308         return SaveOption(name, String(value));
309 }
310
311 int CIniOptionsMgr::SaveOption(const String& name, int value)
312 {
313         varprop::VariantValue val;
314         val.SetInt(value);
315         int retVal = Set(name, val);
316         if (retVal == COption::OPT_OK)
317                 retVal = SaveOption(name);
318         return retVal;
319 }
320
321 int CIniOptionsMgr::SaveOption(const String& name, bool value)
322 {
323         varprop::VariantValue val;
324         val.SetBool(value);
325         int retVal = Set(name, val);
326         if (retVal == COption::OPT_OK)
327                 retVal = SaveOption(name);
328         return retVal;
329 }
330
331 int CIniOptionsMgr::RemoveOption(const String& name)
332 {
333         int retVal = COption::OPT_OK;
334         auto [strPath, strValueName] = SplitName(name);
335
336         if (!strValueName.empty())
337         {
338                 retVal = COptionsMgr::RemoveOption(name);
339         }
340         else
341         {
342                 for (auto it = m_optionsMap.begin(); it != m_optionsMap.end(); )
343                 {
344                         const String& key = it->first;
345                         if (key.find(strPath) == 0 && key.length() > strPath.length() && key[strPath.length()] == '/')
346                         {
347                                 m_iniFileKeyValues.erase(key);
348                                 it = m_optionsMap.erase(it);
349                         }
350                         else
351                                 ++it;
352                 }
353                 retVal = COption::OPT_OK;
354         }
355
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);
360
361         return retVal;
362 }
363
364 int CIniOptionsMgr::FlushOptions()
365 {
366         int retVal = COption::OPT_OK;
367
368         while (InterlockedCompareExchange(&m_dwQueueCount, 0, 0) != 0)
369                 Sleep(0);
370
371         return retVal;
372 }
373
374 int CIniOptionsMgr::ExportOptions(const String& filename, const bool bHexColor /*= false*/) const
375 {
376         for (auto& [key, value] : m_iniFileKeyValues)
377         {
378                 if (m_optionsMap.find(key) == m_optionsMap.end())
379                 {
380                         WritePrivateProfileString(_T("WinMerge"), key.c_str(),
381                                 EscapeValue(value).c_str(), filename.c_str());
382                 }
383         }
384         return COptionsMgr::ExportOptions(filename, bHexColor);
385 }
386
387 int CIniOptionsMgr::ImportOptions(const String& filename)
388 {
389         int retVal = COptionsMgr::ImportOptions(filename);
390         auto iniFileMap = Load(filename);
391         for (auto& [key, value] : iniFileMap)
392         {
393                 if (m_optionsMap.find(key) == m_optionsMap.end())
394                 {
395                         m_iniFileKeyValues.insert_or_assign(key, value);
396                         WritePrivateProfileString(_T("WinMerge"), key.c_str(),
397                                 EscapeValue(value).c_str(), GetFilePath());
398                 }
399         }
400         return retVal;
401 }
402