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