TrinityCore
Loading...
Searching...
No Matches
Config.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "Config.h"
19#include "Log.h"
20#include "StringConvert.h"
21#include <boost/filesystem/directory.hpp>
22#include <boost/filesystem/operations.hpp>
23#include <boost/property_tree/ini_parser.hpp>
24#include <algorithm>
25#include <cstdlib>
26#include <memory>
27#include <mutex>
28
29namespace bpt = boost::property_tree;
30namespace fs = boost::filesystem;
31
32namespace
33{
34 std::string _filename;
35 std::vector<std::string> _additonalFiles;
36 std::vector<std::string> _args;
37 bpt::ptree _config;
38 std::mutex _configLock;
39
40 bool LoadFile(std::string const& file, bpt::ptree& fullTree, std::string& error)
41 {
42 try
43 {
44 bpt::ini_parser::read_ini(file, fullTree);
45
46 if (fullTree.empty())
47 {
48 error = "empty file (" + file + ")";
49 return false;
50 }
51 }
52 catch (bpt::ini_parser::ini_parser_error const& e)
53 {
54 if (e.line() == 0)
55 error = e.message() + " (" + e.filename() + ")";
56 else
57 error = e.message() + " (" + e.filename() + ":" + std::to_string(e.line()) + ")";
58 return false;
59 }
60
61 return true;
62 }
63
64 // Converts ini keys to the environment variable key (upper snake case).
65 // Example of conversions:
66 // SomeConfig => SOME_CONFIG
67 // myNestedConfig.opt1 => MY_NESTED_CONFIG_OPT_1
68 // LogDB.Opt.ClearTime => LOG_DB_OPT_CLEAR_TIME
69 std::string IniKeyToEnvVarKey(std::string const& key)
70 {
71 std::string result;
72
73 const char *str = key.c_str();
74 size_t n = key.length();
75
76 char curr;
77 bool isEnd;
78 bool nextIsUpper;
79 bool currIsNumeric;
80 bool nextIsNumeric;
81
82 for (size_t i = 0; i < n; ++i)
83 {
84 curr = str[i];
85 if (curr == ' ' || curr == '.' || curr == '-')
86 {
87 result += '_';
88 continue;
89 }
90
91 isEnd = i == n - 1;
92 if (!isEnd)
93 {
94 nextIsUpper = isupper(str[i + 1]);
95
96 // handle "aB" to "A_B"
97 if (!isupper(curr) && nextIsUpper)
98 {
99 result += static_cast<char>(std::toupper(curr));
100 result += '_';
101 continue;
102 }
103
104 currIsNumeric = isNumeric(curr);
105 nextIsNumeric = isNumeric(str[i + 1]);
106
107 // handle "a1" to "a_1"
108 if (!currIsNumeric && nextIsNumeric)
109 {
110 result += static_cast<char>(std::toupper(curr));
111 result += '_';
112 continue;
113 }
114
115 // handle "1a" to "1_a"
116 if (currIsNumeric && !nextIsNumeric)
117 {
118 result += static_cast<char>(std::toupper(curr));
119 result += '_';
120 continue;
121 }
122 }
123
124 result += static_cast<char>(std::toupper(curr));
125 }
126 return result;
127 }
128
129 Optional<std::string> EnvVarForIniKey(std::string const& key)
130 {
131 std::string envKey = "TC_" + IniKeyToEnvVarKey(key);
132 char* val = std::getenv(envKey.c_str());
133 if (!val)
134 return std::nullopt;
135
136 return std::string(val);
137 }
138}
139
140bool ConfigMgr::LoadInitial(std::string file, std::vector<std::string> args,
141 std::string& error)
142{
143 std::lock_guard<std::mutex> lock(_configLock);
144
145 _filename = std::move(file);
146 _args = std::move(args);
147
148 bpt::ptree fullTree;
149 if (!LoadFile(_filename, fullTree, error))
150 return false;
151
152 // Since we're using only one section per config file, we skip the section and have direct property access
153 _config = fullTree.begin()->second;
154
155 return true;
156}
157
158bool ConfigMgr::LoadAdditionalFile(std::string file, bool keepOnReload, std::string& error)
159{
160 bpt::ptree fullTree;
161 if (!LoadFile(file, fullTree, error))
162 return false;
163
164 std::lock_guard<std::mutex> lock(_configLock);
165
166 for (bpt::ptree::value_type const& child : fullTree.begin()->second)
167 _config.put_child(bpt::ptree::path_type(child.first, '/'), child.second);
168
169 if (keepOnReload)
170 _additonalFiles.emplace_back(std::move(file));
171
172 return true;
173}
174
175bool ConfigMgr::LoadAdditionalDir(std::string const& dir, bool keepOnReload, std::vector<std::string>& loadedFiles, std::vector<std::string>& errors)
176{
177 fs::path dirPath = dir;
178 if (!fs::exists(dirPath) || !fs::is_directory(dirPath))
179 return true;
180
181 for (fs::directory_entry const& f : fs::recursive_directory_iterator(dirPath))
182 {
183 if (!fs::is_regular_file(f))
184 continue;
185
186 fs::path configFile = fs::absolute(f);
187 if (configFile.extension() != ".conf")
188 continue;
189
190 std::string fileName = configFile.generic_string();
191 std::string error;
192 if (LoadAdditionalFile(fileName, keepOnReload, error))
193 loadedFiles.push_back(std::move(fileName));
194 else
195 errors.push_back(std::move(error));
196 }
197
198 return errors.empty();
199}
200
202{
203 std::lock_guard<std::mutex> lock(_configLock);
204
205 std::vector<std::string> overriddenKeys;
206
207 for (bpt::ptree::value_type& itr: _config)
208 {
209 if (!itr.second.empty() || itr.first.empty())
210 continue;
211
212 Optional<std::string> envVar = EnvVarForIniKey(itr.first);
213 if (!envVar)
214 continue;
215
216 itr.second = bpt::ptree(*envVar);
217
218 overriddenKeys.push_back(itr.first);
219 }
220
221 return overriddenKeys;
222}
223
225{
226 static ConfigMgr instance;
227 return &instance;
228}
229
230bool ConfigMgr::Reload(std::vector<std::string>& errors)
231{
232 std::string error;
233 if (!LoadInitial(_filename, std::move(_args), error))
234 errors.push_back(std::move(error));
235
236 for (std::string const& additionalFile : _additonalFiles)
237 if (!LoadAdditionalFile(additionalFile, false, error))
238 errors.push_back(std::move(error));
239
241
242 return errors.empty();
243}
244
245template<class T>
246T ConfigMgr::GetValueDefault(std::string const& name, T def, bool quiet) const
247{
248 try
249 {
250 return _config.get<T>(bpt::ptree::path_type(name, '/'));
251 }
252 catch (bpt::ptree_bad_path const&)
253 {
254 Optional<std::string> envVar = EnvVarForIniKey(name);
255 if (envVar)
256 {
257 Optional<T> castedVar = Trinity::StringTo<T>(*envVar);
258 if (!castedVar)
259 {
260 TC_LOG_ERROR("server.loading", "Bad value defined for name {} in environment variables, going to use default instead", name);
261 return def;
262 }
263
264 if (!quiet)
265 TC_LOG_WARN("server.loading", "Missing name {} in config file {}, recovered with environment '{}' value.", name, _filename, envVar->c_str());
266
267 return *castedVar;
268 }
269 else if (!quiet)
270 {
271 TC_LOG_WARN("server.loading", "Missing name {} in config file {}, add \"{} = {}\" to this file",
272 name, _filename, name, def);
273 }
274 }
275 catch (bpt::ptree_bad_data const&)
276 {
277 TC_LOG_ERROR("server.loading", "Bad value defined for name {} in config file {}, going to use {} instead",
278 name, _filename, def);
279 }
280
281 return def;
282}
283
284template<>
285std::string ConfigMgr::GetValueDefault<std::string>(std::string const& name, std::string def, bool quiet) const
286{
287 try
288 {
289 return _config.get<std::string>(bpt::ptree::path_type(name, '/'));
290 }
291 catch (bpt::ptree_bad_path const&)
292 {
293 Optional<std::string> envVar = EnvVarForIniKey(name);
294 if (envVar)
295 {
296 if (!quiet)
297 TC_LOG_WARN("server.loading", "Missing name {} in config file {}, recovered with environment '{}' value.", name, _filename, envVar->c_str());
298
299 return *envVar;
300 }
301 else if (!quiet)
302 {
303 TC_LOG_WARN("server.loading", "Missing name {} in config file {}, add \"{} = {}\" to this file",
304 name, _filename, name, def);
305 }
306 }
307 catch (bpt::ptree_bad_data const&)
308 {
309 TC_LOG_ERROR("server.loading", "Bad value defined for name {} in config file {}, going to use {} instead",
310 name, _filename, def);
311 }
312
313 return def;
314}
315
316std::string ConfigMgr::GetStringDefault(std::string const& name, const std::string& def, bool quiet) const
317{
318 std::string val = GetValueDefault(name, def, quiet);
319 val.erase(std::remove(val.begin(), val.end(), '"'), val.end());
320 return val;
321}
322
323bool ConfigMgr::GetBoolDefault(std::string const& name, bool def, bool quiet) const
324{
325 std::string val = GetValueDefault(name, std::string(def ? "1" : "0"), quiet);
326 val.erase(std::remove(val.begin(), val.end(), '"'), val.end());
327 Optional<bool> boolVal = Trinity::StringTo<bool>(val);
328 if (boolVal)
329 return *boolVal;
330 else
331 {
332 TC_LOG_ERROR("server.loading", "Bad value defined for name {} in config file {}, going to use '{}' instead",
333 name, _filename, def ? "true" : "false");
334 return def;
335 }
336}
337
338int ConfigMgr::GetIntDefault(std::string const& name, int def, bool quiet) const
339{
340 return GetValueDefault(name, def, quiet);
341}
342
343float ConfigMgr::GetFloatDefault(std::string const& name, float def, bool quiet) const
344{
345 return GetValueDefault(name, def, quiet);
346}
347
348std::string const& ConfigMgr::GetFilename()
349{
350 std::lock_guard<std::mutex> lock(_configLock);
351 return _filename;
352}
353
354std::vector<std::string> const& ConfigMgr::GetArguments() const
355{
356 return _args;
357}
358
359std::vector<std::string> ConfigMgr::GetKeysByString(std::string const& name)
360{
361 std::lock_guard<std::mutex> lock(_configLock);
362
363 std::vector<std::string> keys;
364
365 for (bpt::ptree::value_type const& child : _config)
366 if (child.first.compare(0, name.length(), name) == 0)
367 keys.push_back(child.first);
368
369 return keys;
370}
#define TC_LOG_WARN(filterType__,...)
Definition Log.h:162
#define TC_LOG_ERROR(filterType__,...)
Definition Log.h:165
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
bool isNumeric(wchar_t wchar)
Definition Util.h:176
std::vector< std::string > OverrideWithEnvVariablesIfAny()
Overrides configuration with environment variables and returns overridden keys.
Definition Config.cpp:201
float GetFloatDefault(std::string const &name, float def, bool quiet=false) const
Definition Config.cpp:343
std::string const & GetFilename()
Definition Config.cpp:348
int GetIntDefault(std::string const &name, int def, bool quiet=false) const
Definition Config.cpp:338
bool GetBoolDefault(std::string const &name, bool def, bool quiet=false) const
Definition Config.cpp:323
std::vector< std::string > GetKeysByString(std::string const &name)
Definition Config.cpp:359
bool Reload(std::vector< std::string > &errors)
Definition Config.cpp:230
static ConfigMgr * instance()
Definition Config.cpp:224
bool LoadInitial(std::string file, std::vector< std::string > args, std::string &error)
Method used only for loading main configuration files (authserver.conf and worldserver....
Definition Config.cpp:140
std::vector< std::string > const & GetArguments() const
Definition Config.cpp:354
bool LoadAdditionalDir(std::string const &dir, bool keepOnReload, std::vector< std::string > &loadedFiles, std::vector< std::string > &errors)
Definition Config.cpp:175
T GetValueDefault(std::string const &name, T def, bool quiet) const
Definition Config.cpp:246
bool LoadAdditionalFile(std::string file, bool keepOnReload, std::string &error)
Definition Config.cpp:158
std::string GetStringDefault(std::string const &name, const std::string &def, bool quiet=false) const
Definition Config.cpp:316