1 /*
2  * PROJECT:     ReactOS Applications Manager
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Functions to load / save settings from reg.
5  * COPYRIGHT:   Copyright 2020 He Yang (1160386205@qq.com)
6  */
7 
8 #include "rapps.h"
9 #include "settings.h"
10 
11 #define SETTINGSSUBKEY L"Software\\ReactOS\\" RAPPS_NAME
12 
13 class SettingsField
14 {
15   public:
16     virtual ~SettingsField()
17     {
18         ;
19     }
20     virtual BOOL
21     Save(CRegKey &key) = 0;
22     virtual BOOL
23     Load(CRegKey &key) = 0;
24 };
25 
26 class SettingsFieldBool : public SettingsField
27 {
28   public:
29     SettingsFieldBool(BOOL *pValue, LPCWSTR szRegName) : m_pValueStore(pValue), m_RegName(szRegName)
30     {
31     }
32 
33     virtual BOOL
34     Save(CRegKey &key) override
35     {
36         return key.SetDWORDValue(m_RegName, (DWORD)(*m_pValueStore)) == ERROR_SUCCESS;
37     }
38     virtual BOOL
39     Load(CRegKey &key) override
40     {
41         DWORD dwField;
42         LONG lResult = key.QueryDWORDValue(m_RegName, dwField);
43         if (lResult != ERROR_SUCCESS)
44         {
45             return FALSE;
46         }
47         *m_pValueStore = (BOOL)dwField;
48         return TRUE;
49     }
50 
51   private:
52     BOOL *m_pValueStore; // where to read/store the value
53     LPCWSTR m_RegName;   // key name in registery
54 };
55 
56 class SettingsFieldInt : public SettingsField
57 {
58   public:
59     SettingsFieldInt(INT *pValue, LPCWSTR szRegName) : m_pValueStore(pValue), m_RegName(szRegName)
60     {
61     }
62 
63     virtual BOOL
64     Save(CRegKey &key) override
65     {
66         return key.SetDWORDValue(m_RegName, (DWORD)(*m_pValueStore)) == ERROR_SUCCESS;
67     }
68     virtual BOOL
69     Load(CRegKey &key) override
70     {
71         DWORD dwField;
72         LONG lResult = key.QueryDWORDValue(m_RegName, dwField);
73         if (lResult != ERROR_SUCCESS)
74         {
75             return FALSE;
76         }
77         *m_pValueStore = (INT)dwField;
78         return TRUE;
79     }
80 
81   private:
82     INT *m_pValueStore; // where to read/store the value
83     LPCWSTR m_RegName;  // key name in registery
84 };
85 
86 class SettingsFieldString : public SettingsField
87 {
88   public:
89     SettingsFieldString(WCHAR *pString, ULONG cchLen, LPCWSTR szRegName)
90         : m_pStringStore(pString), m_StringLen(cchLen), m_RegName(szRegName)
91     {
92     }
93 
94     virtual BOOL
95     Save(CRegKey &key) override
96     {
97         return key.SetStringValue(m_RegName, m_pStringStore) == ERROR_SUCCESS;
98     }
99     virtual BOOL
100     Load(CRegKey &key) override
101     {
102         ULONG nChar = m_StringLen - 1; // make sure the terminating L'\0'
103         LONG lResult = key.QueryStringValue(m_RegName, m_pStringStore, &nChar);
104         return lResult == ERROR_SUCCESS;
105     }
106 
107   private:
108     WCHAR *m_pStringStore; // where to read/store the value
109     ULONG m_StringLen;     // string length, in chars
110     LPCWSTR m_RegName;     // key name in registery
111 };
112 
113 static void
114 AddInfoFields(ATL::CAtlList<SettingsField *> &infoFields, SETTINGS_INFO &settings)
115 {
116     infoFields.AddTail(new SettingsFieldBool(&(settings.bSaveWndPos), L"bSaveWndPos"));
117     infoFields.AddTail(new SettingsFieldBool(&(settings.bUpdateAtStart), L"bUpdateAtStart"));
118     infoFields.AddTail(new SettingsFieldBool(&(settings.bLogEnabled), L"bLogEnabled"));
119     infoFields.AddTail(new SettingsFieldString(settings.szDownloadDir, MAX_PATH, L"szDownloadDir"));
120     infoFields.AddTail(new SettingsFieldBool(&(settings.bDelInstaller), L"bDelInstaller"));
121     infoFields.AddTail(new SettingsFieldBool(&(settings.Maximized), L"WindowPosMaximized"));
122     infoFields.AddTail(new SettingsFieldInt(&(settings.Left), L"WindowPosLeft"));
123     infoFields.AddTail(new SettingsFieldInt(&(settings.Top), L"WindowPosTop"));
124     infoFields.AddTail(new SettingsFieldInt(&(settings.Width), L"WindowPosWidth"));
125     infoFields.AddTail(new SettingsFieldInt(&(settings.Height), L"WindowPosHeight"));
126     infoFields.AddTail(new SettingsFieldInt(&(settings.Proxy), L"ProxyMode"));
127     infoFields.AddTail(new SettingsFieldString((settings.szProxyServer), MAX_PATH, L"ProxyServer"));
128     infoFields.AddTail(new SettingsFieldString((settings.szNoProxyFor), MAX_PATH, L"NoProxyFor"));
129     infoFields.AddTail(new SettingsFieldBool(&(settings.bUseSource), L"bUseSource"));
130     infoFields.AddTail(new SettingsFieldString((settings.szSourceURL), INTERNET_MAX_URL_LENGTH, L"SourceURL"));
131 }
132 
133 static BOOL
134 SaveAllSettings(CRegKey &key, SETTINGS_INFO &settings)
135 {
136     BOOL bAllSuccess = TRUE;
137     ATL::CAtlList<SettingsField *> infoFields;
138 
139     AddInfoFields(infoFields, settings);
140 
141     POSITION InfoListPosition = infoFields.GetHeadPosition();
142     while (InfoListPosition)
143     {
144         SettingsField *Info = infoFields.GetNext(InfoListPosition);
145         if (!Info->Save(key))
146         {
147             bAllSuccess = FALSE;
148             // TODO: error log
149         }
150         delete Info;
151     }
152     return bAllSuccess;
153 }
154 
155 static BOOL
156 LoadAllSettings(CRegKey &key, SETTINGS_INFO &settings)
157 {
158     BOOL bLoadedAny = FALSE;
159     ATL::CAtlList<SettingsField *> infoFields;
160 
161     AddInfoFields(infoFields, settings);
162 
163     POSITION InfoListPosition = infoFields.GetHeadPosition();
164     while (InfoListPosition)
165     {
166         SettingsField *Info = infoFields.GetNext(InfoListPosition);
167         if (Info->Load(key))
168             bLoadedAny = TRUE;
169         //else
170         //  TODO: error log
171         delete Info;
172     }
173     return bLoadedAny;
174 }
175 
176 static void
177 GetDefaultDownloadDirectory(CStringW &szDownloadDir)
178 {
179     if (FAILED(SHGetFolderPathW(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, szDownloadDir.GetBuffer(MAX_PATH))))
180     {
181         szDownloadDir.ReleaseBuffer();
182         if (!szDownloadDir.GetEnvironmentVariableW(L"SystemDrive"))
183         {
184             szDownloadDir = L"C:";
185         }
186     }
187     else
188     {
189         szDownloadDir.ReleaseBuffer();
190     }
191 
192     PathAppendW(szDownloadDir.GetBuffer(MAX_PATH), L"\\RAPPS Downloads");
193     szDownloadDir.ReleaseBuffer();
194 }
195 
196 static VOID
197 ValidateStringSettings(PSETTINGS_INFO pSettingsInfo)
198 {
199     if (!pSettingsInfo->szDownloadDir[0])
200     {
201         CStringW szDownloadDir;
202         GetDefaultDownloadDirectory(szDownloadDir);
203 
204         CStringW::CopyChars(pSettingsInfo->szDownloadDir, _countof(pSettingsInfo->szDownloadDir),
205                             szDownloadDir.GetString(), szDownloadDir.GetLength() + 1);
206     }
207 }
208 
209 VOID
210 FillDefaultSettings(PSETTINGS_INFO pSettingsInfo)
211 {
212     ZeroMemory(pSettingsInfo, sizeof(*pSettingsInfo));
213 
214     pSettingsInfo->bSaveWndPos = TRUE;
215     pSettingsInfo->bUpdateAtStart = FALSE;
216     pSettingsInfo->bLogEnabled = TRUE;
217     pSettingsInfo->bUseSource = FALSE;
218     pSettingsInfo->bDelInstaller = FALSE;
219     pSettingsInfo->Maximized = FALSE;
220     pSettingsInfo->Left = CW_USEDEFAULT;
221     pSettingsInfo->Top = CW_USEDEFAULT;
222     pSettingsInfo->Width = 680;
223     pSettingsInfo->Height = 450;
224 
225     ValidateStringSettings(pSettingsInfo);
226 }
227 
228 BOOL
229 LoadSettings(PSETTINGS_INFO pSettingsInfo)
230 {
231     BOOL bLoadedAny = FALSE;
232 
233     FillDefaultSettings(pSettingsInfo);
234 
235     ATL::CRegKey RegKey;
236     if (RegKey.Open(HKEY_CURRENT_USER, SETTINGSSUBKEY, KEY_READ) == ERROR_SUCCESS)
237     {
238         bLoadedAny = LoadAllSettings(RegKey, *pSettingsInfo);
239     }
240 
241     ValidateStringSettings(pSettingsInfo); // Handles the case where a REG_SZ is present but empty
242 
243     if (!bLoadedAny)
244     {
245         // This the first launch, write at least one item so ParseCmdAndExecute() does not
246         // trigger another DB update in another process instance between now and SaveSettings().
247         ATL::CRegKey RegKey;
248         if (RegKey.Create(HKEY_CURRENT_USER, SETTINGSSUBKEY, NULL, REG_OPTION_NON_VOLATILE,
249                           KEY_WRITE, NULL, NULL) == ERROR_SUCCESS)
250         {
251             SettingsFieldBool field(&(pSettingsInfo->bUpdateAtStart), L"bUpdateAtStart");
252             field.Save(RegKey);
253         }
254     }
255     return bLoadedAny;
256 }
257 
258 BOOL
259 SaveSettings(HWND hwnd, PSETTINGS_INFO pSettingsInfo)
260 {
261     WINDOWPLACEMENT wp;
262     ATL::CRegKey RegKey;
263 
264     if (pSettingsInfo->bSaveWndPos)
265     {
266         wp.length = sizeof(wp);
267         GetWindowPlacement(hwnd, &wp);
268 
269         pSettingsInfo->Left = wp.rcNormalPosition.left;
270         pSettingsInfo->Top = wp.rcNormalPosition.top;
271         pSettingsInfo->Width = wp.rcNormalPosition.right - wp.rcNormalPosition.left;
272         pSettingsInfo->Height = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top;
273         pSettingsInfo->Maximized =
274             (wp.showCmd == SW_MAXIMIZE || (wp.showCmd == SW_SHOWMINIMIZED && (wp.flags & WPF_RESTORETOMAXIMIZED)));
275     }
276 
277     if (RegKey.Create(HKEY_CURRENT_USER, SETTINGSSUBKEY, NULL, REG_OPTION_NON_VOLATILE,
278                       KEY_WRITE, NULL, NULL) != ERROR_SUCCESS)
279     {
280         return FALSE;
281     }
282 
283     return SaveAllSettings(RegKey, *pSettingsInfo);
284 }
285