34348cc06fde032ff6e0e8ef2d710321b1e15896
[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()
36 {
37         std::map<std::string, SettingsEntry>::const_iterator it;
38         for (it = m_settings.begin(); it != m_settings.end(); ++it)
39                 delete it->second.group;
40 }
41
42
43 Settings & Settings::operator += (const Settings &other)
44 {
45         update(other);
46
47         return *this;
48 }
49
50
51 Settings & Settings::operator = (const Settings &other)
52 {
53         if (&other == this)
54                 return *this;
55
56         JMutexAutoLock lock(m_mutex);
57         JMutexAutoLock lock2(other.m_mutex);
58
59         clearNoLock();
60         updateNoLock(other);
61
62         return *this;
63 }
64
65
66 std::string Settings::sanitizeString(const std::string &value)
67 {
68         std::string str = value;
69         for (const char *s = "\t\n\v\f\r\b =\""; *s; s++)
70                 str.erase(std::remove(str.begin(), str.end(), *s), str.end());
71
72         return str;
73 }
74
75
76 std::string Settings::getMultiline(std::istream &is)
77 {
78         std::string value;
79         std::string line;
80
81         while (is.good()) {
82                 std::getline(is, line);
83                 if (line == "\"\"\"")
84                         break;
85                 value += line;
86                 value.push_back('\n');
87         }
88
89         size_t len = value.size();
90         if (len)
91                 value.erase(len - 1);
92
93         return value;
94 }
95
96
97 bool Settings::parseConfigLines(std::istream &is, const std::string &end)
98 {
99         JMutexAutoLock lock(m_mutex);
100
101         std::string line, name, value;
102
103         while (is.good()) {
104                 std::getline(is, line);
105                 SettingsParseEvent event = parseConfigObject(line, end, name, value);
106
107                 switch (event) {
108                 case SPE_NONE:
109                 case SPE_INVALID:
110                 case SPE_COMMENT:
111                         break;
112                 case SPE_KVPAIR:
113                         m_settings[name] = SettingsEntry(value);
114                         break;
115                 case SPE_END:
116                         return true;
117                 case SPE_GROUP: {
118                         Settings *branch = new Settings;
119                         if (!branch->parseConfigLines(is, "}"))
120                                 return false;
121
122                         m_settings[name] = SettingsEntry(branch);
123                         break;
124                 }
125                 case SPE_MULTILINE:
126                         m_settings[name] = SettingsEntry(getMultiline(is));
127                         break;
128                 }
129         }
130
131         return end.empty();
132 }
133
134
135 bool Settings::readConfigFile(const char *filename)
136 {
137         std::ifstream is(filename);
138         if (!is.good())
139                 return false;
140
141         return parseConfigLines(is, "");
142 }
143
144
145 void Settings::writeLines(std::ostream &os, u32 tab_depth) const
146 {
147         JMutexAutoLock lock(m_mutex);
148
149         for (std::map<std::string, SettingsEntry>::const_iterator
150                         it = m_settings.begin();
151                         it != m_settings.end(); ++it) {
152                 bool is_multiline = it->second.value.find('\n') != std::string::npos;
153                 printValue(os, it->first, it->second, is_multiline, tab_depth);
154         }
155 }
156
157
158 void Settings::printValue(std::ostream &os, const std::string &name,
159         const SettingsEntry &entry, bool is_value_multiline, u32 tab_depth)
160 {
161         for (u32 i = 0; i != tab_depth; i++)
162                 os << "\t";
163         os << name << " = ";
164
165         if (is_value_multiline)
166                 os << "\"\"\"\n" << entry.value << "\n\"\"\"\n";
167         else
168                 os << entry.value << "\n";
169
170         Settings *group = entry.group;
171         if (group) {
172                 for (u32 i = 0; i != tab_depth; i++)
173                         os << "\t";
174
175                 os << name << " = {\n";
176                 group->writeLines(os, tab_depth + 1);
177
178                 for (u32 i = 0; i != tab_depth; i++)
179                         os << "\t";
180
181                 os << "}\n";
182         }
183 }
184
185
186 bool Settings::updateConfigObject(std::istream &is, std::ostream &os,
187         const std::string &end, u32 tab_depth)
188 {
189         std::map<std::string, SettingsEntry>::const_iterator it;
190         std::set<std::string> settings_in_config;
191         bool was_modified = false;
192         bool end_found = false;
193         std::string line, name, value;
194
195         // Add any settings that exist in the config file with the current value
196         // in the object if existing
197         while (is.good() && !end_found) {
198                 std::getline(is, line);
199                 SettingsParseEvent event = parseConfigObject(line, end, name, value);
200
201                 switch (event) {
202                 case SPE_END:
203                         end_found = true;
204                         break;
205                 case SPE_KVPAIR:
206                 case SPE_MULTILINE:
207                         it = m_settings.find(name);
208                         if (it != m_settings.end()) {
209                                 if (event == SPE_MULTILINE)
210                                         value = getMultiline(is);
211
212                                 if (value != it->second.value) {
213                                         value = it->second.value;
214                                         was_modified = true;
215                                 }
216                         }
217
218                         settings_in_config.insert(name);
219
220                         printValue(os, name, SettingsEntry(value),
221                                 event == SPE_MULTILINE, tab_depth);
222
223                         break;
224                 case SPE_GROUP: {
225                         Settings *group = NULL;
226                         it = m_settings.find(name);
227                         if (it != m_settings.end())
228                                 group = it->second.group;
229
230                         settings_in_config.insert(name);
231
232                         os << name << " = {\n";
233
234                         if (group) {
235                                 was_modified |= group->updateConfigObject(is, os, "}", tab_depth + 1);
236                         } else {
237                                 Settings dummy_settings;
238                                 dummy_settings.updateConfigObject(is, os, "}", tab_depth + 1);
239                         }
240
241                         for (u32 i = 0; i != tab_depth; i++)
242                                 os << "\t";
243                         os << "}\n";
244                         break;
245                 }
246                 default:
247                         os << line << (is.eof() ? "" : "\n");
248                         break;
249                 }
250         }
251
252         // Add any settings in the object that don't exist in the config file yet
253         for (it = m_settings.begin(); it != m_settings.end(); ++it) {
254                 if (settings_in_config.find(it->first) != settings_in_config.end())
255                         continue;
256
257                 was_modified = true;
258
259                 bool is_multiline = it->second.value.find('\n') != std::string::npos;
260                 printValue(os, it->first, it->second, is_multiline, tab_depth);
261         }
262
263         return was_modified;
264 }
265
266
267 bool Settings::updateConfigFile(const char *filename)
268 {
269         JMutexAutoLock lock(m_mutex);
270
271         std::ifstream is(filename);
272         std::ostringstream os(std::ios_base::binary);
273
274         if (!updateConfigObject(is, os, ""))
275                 return true;
276
277         if (!fs::safeWriteToFile(filename, os.str())) {
278                 errorstream << "Error writing configuration file: \""
279                         << filename << "\"" << std::endl;
280                 return false;
281         }
282
283         return true;
284 }
285
286
287 bool Settings::parseCommandLine(int argc, char *argv[],
288                 std::map<std::string, ValueSpec> &allowed_options)
289 {
290         int nonopt_index = 0;
291         for (int i = 1; i < argc; i++) {
292                 std::string arg_name = argv[i];
293                 if (arg_name.substr(0, 2) != "--") {
294                         // If option doesn't start with -, read it in as nonoptX
295                         if (arg_name[0] != '-'){
296                                 std::string name = "nonopt";
297                                 name += itos(nonopt_index);
298                                 set(name, arg_name);
299                                 nonopt_index++;
300                                 continue;
301                         }
302                         errorstream << "Invalid command-line parameter \""
303                                         << arg_name << "\": --<option> expected." << std::endl;
304                         return false;
305                 }
306
307                 std::string name = arg_name.substr(2);
308
309                 std::map<std::string, ValueSpec>::iterator n;
310                 n = allowed_options.find(name);
311                 if (n == allowed_options.end()) {
312                         errorstream << "Unknown command-line parameter \""
313                                         << arg_name << "\"" << std::endl;
314                         return false;
315                 }
316
317                 ValueType type = n->second.type;
318
319                 std::string value = "";
320
321                 if (type == VALUETYPE_FLAG) {
322                         value = "true";
323                 } else {
324                         if ((i + 1) >= argc) {
325                                 errorstream << "Invalid command-line parameter \""
326                                                 << name << "\": missing value" << std::endl;
327                                 return false;
328                         }
329                         value = argv[++i];
330                 }
331
332                 set(name, value);
333         }
334
335         return true;
336 }
337
338
339
340 /***********
341  * Getters *
342  ***********/
343
344
345 const SettingsEntry &Settings::getEntry(const std::string &name) const
346 {
347         JMutexAutoLock lock(m_mutex);
348
349         std::map<std::string, SettingsEntry>::const_iterator n;
350         if ((n = m_settings.find(name)) == m_settings.end()) {
351                 if ((n = m_defaults.find(name)) == m_defaults.end())
352                         throw SettingNotFoundException("Setting [" + name + "] not found.");
353         }
354         return n->second;
355 }
356
357
358 Settings *Settings::getGroup(const std::string &name) const
359 {
360         return getEntry(name).group;
361 }
362
363
364 std::string Settings::get(const std::string &name) const
365 {
366         return getEntry(name).value;
367 }
368
369
370 bool Settings::getBool(const std::string &name) const
371 {
372         return is_yes(get(name));
373 }
374
375
376 u16 Settings::getU16(const std::string &name) const
377 {
378         return stoi(get(name), 0, 65535);
379 }
380
381
382 s16 Settings::getS16(const std::string &name) const
383 {
384         return stoi(get(name), -32768, 32767);
385 }
386
387
388 s32 Settings::getS32(const std::string &name) const
389 {
390         return stoi(get(name));
391 }
392
393
394 float Settings::getFloat(const std::string &name) const
395 {
396         return stof(get(name));
397 }
398
399
400 u64 Settings::getU64(const std::string &name) const
401 {
402         u64 value = 0;
403         std::string s = get(name);
404         std::istringstream ss(s);
405         ss >> value;
406         return value;
407 }
408
409
410 v2f Settings::getV2F(const std::string &name) const
411 {
412         v2f value;
413         Strfnd f(get(name));
414         f.next("(");
415         value.X = stof(f.next(","));
416         value.Y = stof(f.next(")"));
417         return value;
418 }
419
420
421 v3f Settings::getV3F(const std::string &name) const
422 {
423         v3f value;
424         Strfnd f(get(name));
425         f.next("(");
426         value.X = stof(f.next(","));
427         value.Y = stof(f.next(","));
428         value.Z = stof(f.next(")"));
429         return value;
430 }
431
432
433 u32 Settings::getFlagStr(const std::string &name, const FlagDesc *flagdesc,
434         u32 *flagmask) const
435 {
436         std::string val = get(name);
437         return std::isdigit(val[0])
438                 ? stoi(val)
439                 : readFlagString(val, flagdesc, flagmask);
440 }
441
442
443 // N.B. if getStruct() is used to read a non-POD aggregate type,
444 // the behavior is undefined.
445 bool Settings::getStruct(const std::string &name, const std::string &format,
446         void *out, size_t olen) const
447 {
448         std::string valstr;
449
450         try {
451                 valstr = get(name);
452         } catch (SettingNotFoundException &e) {
453                 return false;
454         }
455
456         if (!deSerializeStringToStruct(valstr, format, out, olen))
457                 return false;
458
459         return true;
460 }
461
462
463 bool Settings::exists(const std::string &name) const
464 {
465         JMutexAutoLock lock(m_mutex);
466
467         return (m_settings.find(name) != m_settings.end() ||
468                 m_defaults.find(name) != m_defaults.end());
469 }
470
471
472 std::vector<std::string> Settings::getNames() const
473 {
474         std::vector<std::string> names;
475         for (std::map<std::string, SettingsEntry>::const_iterator
476                         i = m_settings.begin();
477                         i != m_settings.end(); ++i) {
478                 names.push_back(i->first);
479         }
480         return names;
481 }
482
483
484
485 /***************************************
486  * Getters that don't throw exceptions *
487  ***************************************/
488
489 bool Settings::getEntryNoEx(const std::string &name, SettingsEntry &val) const
490 {
491         try {
492                 val = getEntry(name);
493                 return true;
494         } catch (SettingNotFoundException &e) {
495                 return false;
496         }
497 }
498
499
500 bool Settings::getGroupNoEx(const std::string &name, Settings *&val) const
501 {
502         try {
503                 val = getGroup(name);
504                 return true;
505         } catch (SettingNotFoundException &e) {
506                 return false;
507         }
508 }
509
510
511 bool Settings::getNoEx(const std::string &name, std::string &val) const
512 {
513         try {
514                 val = get(name);
515                 return true;
516         } catch (SettingNotFoundException &e) {
517                 return false;
518         }
519 }
520
521
522 bool Settings::getFlag(const std::string &name) const
523 {
524         try {
525                 return getBool(name);
526         } catch(SettingNotFoundException &e) {
527                 return false;
528         }
529 }
530
531
532 bool Settings::getFloatNoEx(const std::string &name, float &val) const
533 {
534         try {
535                 val = getFloat(name);
536                 return true;
537         } catch (SettingNotFoundException &e) {
538                 return false;
539         }
540 }
541
542
543 bool Settings::getU16NoEx(const std::string &name, u16 &val) const
544 {
545         try {
546                 val = getU16(name);
547                 return true;
548         } catch (SettingNotFoundException &e) {
549                 return false;
550         }
551 }
552
553
554 bool Settings::getS16NoEx(const std::string &name, s16 &val) const
555 {
556         try {
557                 val = getS16(name);
558                 return true;
559         } catch (SettingNotFoundException &e) {
560                 return false;
561         }
562 }
563
564
565 bool Settings::getS32NoEx(const std::string &name, s32 &val) const
566 {
567         try {
568                 val = getS32(name);
569                 return true;
570         } catch (SettingNotFoundException &e) {
571                 return false;
572         }
573 }
574
575
576 bool Settings::getU64NoEx(const std::string &name, u64 &val) const
577 {
578         try {
579                 val = getU64(name);
580                 return true;
581         } catch (SettingNotFoundException &e) {
582                 return false;
583         }
584 }
585
586
587 bool Settings::getV2FNoEx(const std::string &name, v2f &val) const
588 {
589         try {
590                 val = getV2F(name);
591                 return true;
592         } catch (SettingNotFoundException &e) {
593                 return false;
594         }
595 }
596
597
598 bool Settings::getV3FNoEx(const std::string &name, v3f &val) const
599 {
600         try {
601                 val = getV3F(name);
602                 return true;
603         } catch (SettingNotFoundException &e) {
604                 return false;
605         }
606 }
607
608
609 // N.B. getFlagStrNoEx() does not set val, but merely modifies it.  Thus,
610 // val must be initialized before using getFlagStrNoEx().  The intention of
611 // this is to simplify modifying a flags field from a default value.
612 bool Settings::getFlagStrNoEx(const std::string &name, u32 &val,
613         FlagDesc *flagdesc) const
614 {
615         try {
616                 u32 flags, flagmask;
617
618                 flags = getFlagStr(name, flagdesc, &flagmask);
619
620                 val &= ~flagmask;
621                 val |=  flags;
622
623                 return true;
624         } catch (SettingNotFoundException &e) {
625                 return false;
626         }
627 }
628
629
630 /***********
631  * Setters *
632  ***********/
633
634
635 void Settings::set(const std::string &name, const std::string &value)
636 {
637         JMutexAutoLock lock(m_mutex);
638
639         m_settings[name].value = value;
640 }
641
642
643 void Settings::setGroup(const std::string &name, Settings *group)
644 {
645         JMutexAutoLock lock(m_mutex);
646
647         delete m_settings[name].group;
648         m_settings[name].group = group;
649 }
650
651
652 void Settings::setDefault(const std::string &name, const std::string &value)
653 {
654         JMutexAutoLock lock(m_mutex);
655
656         m_defaults[name].value = value;
657 }
658
659
660 void Settings::setGroupDefault(const std::string &name, Settings *group)
661 {
662         JMutexAutoLock lock(m_mutex);
663
664         delete m_defaults[name].group;
665         m_defaults[name].group = group;
666 }
667
668
669 void Settings::setBool(const std::string &name, bool value)
670 {
671         set(name, value ? "true" : "false");
672 }
673
674
675 void Settings::setS16(const std::string &name, s16 value)
676 {
677         set(name, itos(value));
678 }
679
680
681 void Settings::setS32(const std::string &name, s32 value)
682 {
683         set(name, itos(value));
684 }
685
686
687 void Settings::setU64(const std::string &name, u64 value)
688 {
689         std::ostringstream os;
690         os << value;
691         set(name, os.str());
692 }
693
694
695 void Settings::setFloat(const std::string &name, float value)
696 {
697         set(name, ftos(value));
698 }
699
700
701 void Settings::setV2F(const std::string &name, v2f value)
702 {
703         std::ostringstream os;
704         os << "(" << value.X << "," << value.Y << ")";
705         set(name, os.str());
706 }
707
708
709 void Settings::setV3F(const std::string &name, v3f value)
710 {
711         std::ostringstream os;
712         os << "(" << value.X << "," << value.Y << "," << value.Z << ")";
713         set(name, os.str());
714 }
715
716
717 void Settings::setFlagStr(const std::string &name, u32 flags,
718         const FlagDesc *flagdesc, u32 flagmask)
719 {
720         set(name, writeFlagString(flags, flagdesc, flagmask));
721 }
722
723
724 bool Settings::setStruct(const std::string &name, const std::string &format,
725         void *value)
726 {
727         std::string structstr;
728         if (!serializeStructToString(&structstr, format, value))
729                 return false;
730
731         set(name, structstr);
732         return true;
733 }
734
735
736 bool Settings::remove(const std::string &name)
737 {
738         JMutexAutoLock lock(m_mutex);
739         return m_settings.erase(name);
740 }
741
742
743 void Settings::clear()
744 {
745         JMutexAutoLock lock(m_mutex);
746         clearNoLock();
747 }
748
749
750 void Settings::updateValue(const Settings &other, const std::string &name)
751 {
752         if (&other == this)
753                 return;
754
755         JMutexAutoLock lock(m_mutex);
756
757         try {
758                 std::string val = other.get(name);
759
760                 m_settings[name] = val;
761         } catch (SettingNotFoundException &e) {
762         }
763 }
764
765
766 void Settings::update(const Settings &other)
767 {
768         if (&other == this)
769                 return;
770
771         JMutexAutoLock lock(m_mutex);
772         JMutexAutoLock lock2(other.m_mutex);
773
774         updateNoLock(other);
775 }
776
777
778 SettingsParseEvent Settings::parseConfigObject(const std::string &line,
779         const std::string &end, std::string &name, std::string &value)
780 {
781         std::string trimmed_line = trim(line);
782
783         if (trimmed_line.empty())
784                 return SPE_NONE;
785         if (trimmed_line[0] == '#')
786                 return SPE_COMMENT;
787         if (trimmed_line == end)
788                 return SPE_END;
789
790         size_t pos = trimmed_line.find('=');
791         if (pos == std::string::npos)
792                 return SPE_INVALID;
793
794         name  = trim(trimmed_line.substr(0, pos));
795         value = trim(trimmed_line.substr(pos + 1));
796
797         if (value == "{")
798                 return SPE_GROUP;
799         if (value == "\"\"\"")
800                 return SPE_MULTILINE;
801
802         return SPE_KVPAIR;
803 }
804
805
806 void Settings::updateNoLock(const Settings &other)
807 {
808         m_settings.insert(other.m_settings.begin(), other.m_settings.end());
809         m_defaults.insert(other.m_defaults.begin(), other.m_defaults.end());
810 }
811
812
813 void Settings::clearNoLock()
814 {
815         m_settings.clear();
816         m_defaults.clear();
817 }
818
819
820 void Settings::registerChangedCallback(std::string name,
821         setting_changed_callback cbf)
822 {
823         m_callbacks[name].push_back(cbf);
824 }
825
826
827 void Settings::doCallbacks(const std::string name)
828 {
829         std::vector<setting_changed_callback> tempvector;
830         {
831                 JMutexAutoLock lock(m_mutex);
832                 if (m_callbacks.find(name) != m_callbacks.end())
833                 {
834                         tempvector = m_callbacks[name];
835                 }
836         }
837
838         for (std::vector<setting_changed_callback>::iterator iter = tempvector.begin();
839                         iter != tempvector.end(); iter ++)
840         {
841                 (*iter)(name);
842         }
843 }