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