Split settings into seperate source and header files
[oweals/minetest.git] / src / settings.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "settings.h"
21 #include "irrlichttypes_bloated.h"
22 #include "exceptions.h"
23 #include "jthread/jmutexautolock.h"
24 #include "strfnd.h"
25 #include <iostream>
26 #include <fstream>
27 #include <sstream>
28 #include "debug.h"
29 #include "log.h"
30 #include "util/serialize.h"
31 #include "filesys.h"
32 #include <cctype>
33
34
35 Settings & Settings::operator += (const Settings &other)
36 {
37         update(other);
38
39         return *this;
40 }
41
42
43 Settings & Settings::operator = (const Settings &other)
44 {
45         if (&other == this)
46                 return *this;
47
48         JMutexAutoLock lock(m_mutex);
49         JMutexAutoLock lock2(other.m_mutex);
50
51         clearNoLock();
52         updateNoLock(other);
53
54         return *this;
55 }
56
57
58 bool Settings::parseConfigLines(std::istream &is,
59                 const std::string &end)
60 {
61         JMutexAutoLock lock(m_mutex);
62
63         std::string name, value;
64         bool end_found = false;
65
66         while (is.good() && !end_found) {
67                 if (parseConfigObject(is, name, value, end, end_found)) {
68                         m_settings[name] = value;
69                 }
70         }
71         if (!end.empty() && !end_found) {
72                 return false;
73         }
74         return true;
75 }
76
77
78 bool Settings::readConfigFile(const char *filename)
79 {
80         std::ifstream is(filename);
81         if (!is.good())
82                 return false;
83
84         JMutexAutoLock lock(m_mutex);
85
86         std::string name, value;
87
88         while (is.good()) {
89                 if (parseConfigObject(is, name, value)) {
90                         m_settings[name] = value;
91                 }
92         }
93
94         return true;
95 }
96
97
98 void Settings::writeLines(std::ostream &os) const
99 {
100         JMutexAutoLock lock(m_mutex);
101
102         for (std::map<std::string, std::string>::const_iterator
103                         i = m_settings.begin();
104                         i != m_settings.end(); ++i) {
105                 os << i->first << " = " << i->second << '\n';
106         }
107 }
108
109
110 bool Settings::updateConfigFile(const char *filename)
111 {
112         std::list<std::string> objects;
113         std::set<std::string> updated;
114         bool changed = false;
115
116         JMutexAutoLock lock(m_mutex);
117
118         // Read the file and check for differences
119         {
120                 std::ifstream is(filename);
121                 while (is.good()) {
122                         getUpdatedConfigObject(is, objects,
123                                         updated, changed);
124                 }
125         }
126
127         // If something not yet determined to have been changed, check if
128         // any new stuff was added
129         if (!changed) {
130                 for (std::map<std::string, std::string>::const_iterator
131                                 i = m_settings.begin();
132                                 i != m_settings.end(); ++i) {
133                         if (updated.find(i->first) == updated.end()) {
134                                 changed = true;
135                                 break;
136                         }
137                 }
138         }
139
140         // If nothing was actually changed, skip writing the file
141         if (!changed) {
142                 return true;
143         }
144
145         // Write stuff back
146         {
147                 std::ostringstream ss(std::ios_base::binary);
148
149                 // Write changes settings
150                 for (std::list<std::string>::const_iterator
151                                 i = objects.begin();
152                                 i != objects.end(); ++i) {
153                         ss << (*i);
154                 }
155
156                 // Write new settings
157                 for (std::map<std::string, std::string>::const_iterator
158                                 i = m_settings.begin();
159                                 i != m_settings.end(); ++i) {
160                         if (updated.find(i->first) != updated.end())
161                                 continue;
162                         ss << i->first << " = " << i->second << '\n';
163                 }
164
165                 if (!fs::safeWriteToFile(filename, ss.str())) {
166                         errorstream << "Error writing configuration file: \""
167                                         << filename << "\"" << std::endl;
168                         return false;
169                 }
170         }
171
172         return true;
173 }
174
175
176 bool Settings::parseCommandLine(int argc, char *argv[],
177                 std::map<std::string, ValueSpec> &allowed_options)
178 {
179         int nonopt_index = 0;
180         for (int i = 1; i < argc; i++) {
181                 std::string arg_name = argv[i];
182                 if (arg_name.substr(0, 2) != "--") {
183                         // If option doesn't start with -, read it in as nonoptX
184                         if (arg_name[0] != '-'){
185                                 std::string name = "nonopt";
186                                 name += itos(nonopt_index);
187                                 set(name, arg_name);
188                                 nonopt_index++;
189                                 continue;
190                         }
191                         errorstream << "Invalid command-line parameter \""
192                                         << arg_name << "\": --<option> expected." << std::endl;
193                         return false;
194                 }
195
196                 std::string name = arg_name.substr(2);
197
198                 std::map<std::string, ValueSpec>::iterator n;
199                 n = allowed_options.find(name);
200                 if (n == allowed_options.end()) {
201                         errorstream << "Unknown command-line parameter \""
202                                         << arg_name << "\"" << std::endl;
203                         return false;
204                 }
205
206                 ValueType type = n->second.type;
207
208                 std::string value = "";
209
210                 if (type == VALUETYPE_FLAG) {
211                         value = "true";
212                 } else {
213                         if ((i + 1) >= argc) {
214                                 errorstream << "Invalid command-line parameter \""
215                                                 << name << "\": missing value" << std::endl;
216                                 return false;
217                         }
218                         value = argv[i++];
219                 }
220
221                 set(name, value);
222         }
223
224         return true;
225 }
226
227
228
229 /***********
230  * Getters *
231  ***********/
232
233
234 std::string Settings::get(const std::string &name) const
235 {
236         JMutexAutoLock lock(m_mutex);
237
238         std::map<std::string, std::string>::const_iterator n;
239         if ((n = m_settings.find(name)) == m_settings.end()) {
240                 if ((n = m_defaults.find(name)) == m_defaults.end()) {
241                         throw SettingNotFoundException("Setting [" + name + "] not found.");
242                 }
243         }
244         return n->second;
245 }
246
247
248 bool Settings::getBool(const std::string &name) const
249 {
250         return is_yes(get(name));
251 }
252
253
254 u16 Settings::getU16(const std::string &name) const
255 {
256         return stoi(get(name), 0, 65535);
257 }
258
259
260 s16 Settings::getS16(const std::string &name) const
261 {
262         return stoi(get(name), -32768, 32767);
263 }
264
265
266 s32 Settings::getS32(const std::string &name) const
267 {
268         return stoi(get(name));
269 }
270
271
272 float Settings::getFloat(const std::string &name) const
273 {
274         return stof(get(name));
275 }
276
277
278 u64 Settings::getU64(const std::string &name) const
279 {
280         u64 value = 0;
281         std::string s = get(name);
282         std::istringstream ss(s);
283         ss >> value;
284         return value;
285 }
286
287
288 v2f Settings::getV2F(const std::string &name) const
289 {
290         v2f value;
291         Strfnd f(get(name));
292         f.next("(");
293         value.X = stof(f.next(","));
294         value.Y = stof(f.next(")"));
295         return value;
296 }
297
298
299 v3f Settings::getV3F(const std::string &name) const
300 {
301         v3f value;
302         Strfnd f(get(name));
303         f.next("(");
304         value.X = stof(f.next(","));
305         value.Y = stof(f.next(","));
306         value.Z = stof(f.next(")"));
307         return value;
308 }
309
310
311 u32 Settings::getFlagStr(const std::string &name, const FlagDesc *flagdesc,
312                 u32 *flagmask) const
313 {
314         std::string val = get(name);
315         return std::isdigit(val[0])
316                 ? stoi(val)
317                 : readFlagString(val, flagdesc, flagmask);
318 }
319
320
321 // N.B. if getStruct() is used to read a non-POD aggregate type,
322 // the behavior is undefined.
323 bool Settings::getStruct(const std::string &name, const std::string &format,
324                 void *out, size_t olen) const
325 {
326         std::string valstr;
327
328         try {
329                 valstr = get(name);
330         } catch (SettingNotFoundException &e) {
331                 return false;
332         }
333
334         if (!deSerializeStringToStruct(valstr, format, out, olen))
335                 return false;
336
337         return true;
338 }
339
340
341 bool Settings::exists(const std::string &name) const
342 {
343         JMutexAutoLock lock(m_mutex);
344
345         return (m_settings.find(name) != m_settings.end() ||
346                 m_defaults.find(name) != m_defaults.end());
347 }
348
349
350 std::vector<std::string> Settings::getNames() const
351 {
352         std::vector<std::string> names;
353         for (std::map<std::string, std::string>::const_iterator
354                         i = m_settings.begin();
355                         i != m_settings.end(); ++i) {
356                 names.push_back(i->first);
357         }
358         return names;
359 }
360
361
362
363 /***************************************
364  * Getters that don't throw exceptions *
365  ***************************************/
366
367
368 bool Settings::getNoEx(const std::string &name, std::string &val) const
369 {
370         try {
371                 val = get(name);
372                 return true;
373         } catch (SettingNotFoundException &e) {
374                 return false;
375         }
376 }
377
378
379 bool Settings::getFlag(const std::string &name) const
380 {
381         try {
382                 return getBool(name);
383         } catch(SettingNotFoundException &e) {
384                 return false;
385         }
386 }
387
388
389 bool Settings::getFloatNoEx(const std::string &name, float &val) const
390 {
391         try {
392                 val = getFloat(name);
393                 return true;
394         } catch (SettingNotFoundException &e) {
395                 return false;
396         }
397 }
398
399
400 bool Settings::getU16NoEx(const std::string &name, u16 &val) const
401 {
402         try {
403                 val = getU16(name);
404                 return true;
405         } catch (SettingNotFoundException &e) {
406                 return false;
407         }
408 }
409
410
411 bool Settings::getS16NoEx(const std::string &name, s16 &val) const
412 {
413         try {
414                 val = getS16(name);
415                 return true;
416         } catch (SettingNotFoundException &e) {
417                 return false;
418         }
419 }
420
421
422 bool Settings::getS32NoEx(const std::string &name, s32 &val) const
423 {
424         try {
425                 val = getS32(name);
426                 return true;
427         } catch (SettingNotFoundException &e) {
428                 return false;
429         }
430 }
431
432
433 bool Settings::getU64NoEx(const std::string &name, u64 &val) const
434 {
435         try {
436                 val = getU64(name);
437                 return true;
438         } catch (SettingNotFoundException &e) {
439                 return false;
440         }
441 }
442
443
444 bool Settings::getV2FNoEx(const std::string &name, v2f &val) const
445 {
446         try {
447                 val = getV2F(name);
448                 return true;
449         } catch (SettingNotFoundException &e) {
450                 return false;
451         }
452 }
453
454
455 bool Settings::getV3FNoEx(const std::string &name, v3f &val) const
456 {
457         try {
458                 val = getV3F(name);
459                 return true;
460         } catch (SettingNotFoundException &e) {
461                 return false;
462         }
463 }
464
465
466 // N.B. getFlagStrNoEx() does not set val, but merely modifies it.  Thus,
467 // val must be initialized before using getFlagStrNoEx().  The intention of
468 // this is to simplify modifying a flags field from a default value.
469 bool Settings::getFlagStrNoEx(const std::string &name, u32 &val, FlagDesc *flagdesc) const
470 {
471         try {
472                 u32 flags, flagmask;
473
474                 flags = getFlagStr(name, flagdesc, &flagmask);
475
476                 val &= ~flagmask;
477                 val |=  flags;
478
479                 return true;
480         } catch (SettingNotFoundException &e) {
481                 return false;
482         }
483 }
484
485
486         
487 /***********
488  * Setters *
489  ***********/
490
491
492 void Settings::set(const std::string &name, std::string value)
493 {
494         JMutexAutoLock lock(m_mutex);
495
496         m_settings[name] = value;
497 }
498
499
500 void Settings::set(const std::string &name, const char *value)
501 {
502         JMutexAutoLock lock(m_mutex);
503
504         m_settings[name] = value;
505 }
506
507
508 void Settings::setDefault(const std::string &name, std::string value)
509 {
510         JMutexAutoLock lock(m_mutex);
511
512         m_defaults[name] = value;
513 }
514
515
516 void Settings::setBool(const std::string &name, bool value)
517 {
518         set(name, value ? "true" : "false");
519 }
520
521
522 void Settings::setS16(const std::string &name, s16 value)
523 {
524         set(name, itos(value));
525 }
526
527
528 void Settings::setS32(const std::string &name, s32 value)
529 {
530         set(name, itos(value));
531 }
532
533
534 void Settings::setU64(const std::string &name, u64 value)
535 {
536         std::ostringstream os;
537         os << value;
538         set(name, os.str());
539 }
540
541
542 void Settings::setFloat(const std::string &name, float value)
543 {
544         set(name, ftos(value));
545 }
546
547
548 void Settings::setV2F(const std::string &name, v2f value)
549 {
550         std::ostringstream os;
551         os << "(" << value.X << "," << value.Y << ")";
552         set(name, os.str());
553 }
554
555
556 void Settings::setV3F(const std::string &name, v3f value)
557 {
558         std::ostringstream os;
559         os << "(" << value.X << "," << value.Y << "," << value.Z << ")";
560         set(name, os.str());
561 }
562
563
564 void Settings::setFlagStr(const std::string &name, u32 flags,
565         const FlagDesc *flagdesc, u32 flagmask)
566 {
567         set(name, writeFlagString(flags, flagdesc, flagmask));
568 }
569
570
571 bool Settings::setStruct(const std::string &name, const std::string &format, void *value)
572 {
573         std::string structstr;
574         if (!serializeStructToString(&structstr, format, value))
575                 return false;
576
577         set(name, structstr);
578         return true;
579 }
580
581
582 bool Settings::remove(const std::string &name)
583 {
584         JMutexAutoLock lock(m_mutex);
585         return m_settings.erase(name);
586 }
587
588
589 void Settings::clear()
590 {
591         JMutexAutoLock lock(m_mutex);
592         clearNoLock();
593 }
594
595
596 void Settings::updateValue(const Settings &other, const std::string &name)
597 {
598         if (&other == this)
599                 return;
600
601         JMutexAutoLock lock(m_mutex);
602
603         try {
604                 std::string val = other.get(name);
605                 m_settings[name] = val;
606         } catch (SettingNotFoundException &e) {
607         }
608 }
609
610
611 void Settings::update(const Settings &other)
612 {
613         if (&other == this)
614                 return;
615
616         JMutexAutoLock lock(m_mutex);
617         JMutexAutoLock lock2(other.m_mutex);
618
619         updateNoLock(other);
620 }
621
622
623 inline bool Settings::parseConfigObject(std::istream &is,
624                 std::string &name, std::string &value)
625 {
626         bool end_found = false;
627         return parseConfigObject(is, name, value, "", end_found);
628 }
629
630
631 // NOTE: This function might be expanded to allow multi-line settings.
632 bool Settings::parseConfigObject(std::istream &is,
633                 std::string &name, std::string &value,
634                 const std::string &end, bool &end_found)
635 {
636         std::string line;
637         std::getline(is, line);
638         std::string trimmed_line = trim(line);
639
640         // Ignore empty lines and comments
641         if (trimmed_line.empty() || trimmed_line[0] == '#') {
642                 value = trimmed_line;
643                 return false;
644         }
645         if (trimmed_line == end) {
646                 end_found = true;
647                 return false;
648         }
649
650         Strfnd sf(trimmed_line);
651
652         name = trim(sf.next("="));
653         if (name.empty()) {
654                 value = trimmed_line;
655                 return false;
656         }
657
658         value = trim(sf.next("\n"));
659
660         return true;
661 }
662
663
664 void Settings::getUpdatedConfigObject(std::istream &is,
665                 std::list<std::string> &dst,
666                 std::set<std::string> &updated,
667                 bool &changed)
668 {
669         std::string name, value;
670         if (!parseConfigObject(is, name, value)) {
671                 dst.push_back(value + '\n');
672                 return;
673         }
674
675         if (m_settings.find(name) != m_settings.end()) {
676                 std::string new_value = m_settings[name];
677
678                 if (new_value != value) {
679                         changed = true;
680                 }
681
682                 dst.push_back(name + " = " + new_value + '\n');
683                 updated.insert(name);
684         } else { // File contains a setting which is not in m_settings
685                 changed = true;
686         }
687 }
688
689
690 void Settings::updateNoLock(const Settings &other)
691 {
692         m_settings.insert(other.m_settings.begin(), other.m_settings.end());
693         m_defaults.insert(other.m_defaults.begin(), other.m_defaults.end());
694 }
695
696
697 void Settings::clearNoLock()
698 {
699         m_settings.clear();
700         m_defaults.clear();
701 }
702