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