Bunch of small fixes (coding style, very unlikely errors, warning messages)
[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)
447         {
448                 JMutexAutoLock lock(m_mutex);
449
450                 return (m_settings.find(name) != m_settings.end() || m_defaults.find(name) != m_defaults.end());
451         }
452
453         std::string get(std::string name)
454         {
455                 JMutexAutoLock lock(m_mutex);
456
457                 std::map<std::string, std::string>::iterator n;
458                 n = m_settings.find(name);
459                 if(n == m_settings.end())
460                 {
461                         n = m_defaults.find(name);
462                         if(n == m_defaults.end())
463                         {
464                                 throw SettingNotFoundException(("Setting [" + name + "] not found ").c_str());
465                         }
466                 }
467
468                 return n->second;
469         }
470
471         //////////// Get setting
472         bool getBool(std::string name)
473         {
474                 return is_yes(get(name));
475         }
476
477         bool getFlag(std::string name)
478         {
479                 try
480                 {
481                         return getBool(name);
482                 }
483                 catch(SettingNotFoundException &e)
484                 {
485                         return false;
486                 }
487         }
488
489         // Asks if empty
490         bool getBoolAsk(std::string name, std::string question, bool def)
491         {
492                 // If it is in settings
493                 if(exists(name))
494                         return getBool(name);
495
496                 std::string s;
497                 char templine[10];
498                 std::cout<<question<<" [y/N]: ";
499                 std::cin.getline(templine, 10);
500                 s = templine;
501
502                 if(s == "")
503                         return def;
504
505                 return is_yes(s);
506         }
507
508         float getFloat(std::string name)
509         {
510                 return stof(get(name));
511         }
512
513         u16 getU16(std::string name)
514         {
515                 return stoi(get(name), 0, 65535);
516         }
517
518         u16 getU16Ask(std::string name, std::string question, u16 def)
519         {
520                 // If it is in settings
521                 if(exists(name))
522                         return getU16(name);
523
524                 std::string s;
525                 char templine[10];
526                 std::cout<<question<<" ["<<def<<"]: ";
527                 std::cin.getline(templine, 10);
528                 s = templine;
529
530                 if(s == "")
531                         return def;
532
533                 return stoi(s, 0, 65535);
534         }
535
536         s16 getS16(std::string name)
537         {
538                 return stoi(get(name), -32768, 32767);
539         }
540
541         s32 getS32(std::string name)
542         {
543                 return stoi(get(name));
544         }
545
546         v3f getV3F(std::string name)
547         {
548                 v3f value;
549                 Strfnd f(get(name));
550                 f.next("(");
551                 value.X = stof(f.next(","));
552                 value.Y = stof(f.next(","));
553                 value.Z = stof(f.next(")"));
554                 return value;
555         }
556
557         v2f getV2F(std::string name)
558         {
559                 v2f value;
560                 Strfnd f(get(name));
561                 f.next("(");
562                 value.X = stof(f.next(","));
563                 value.Y = stof(f.next(")"));
564                 return value;
565         }
566
567         u64 getU64(std::string name)
568         {
569                 u64 value = 0;
570                 std::string s = get(name);
571                 std::istringstream ss(s);
572                 ss>>value;
573                 return value;
574         }
575
576         u32 getFlagStr(std::string name, FlagDesc *flagdesc, u32 *flagmask)
577         {
578                 std::string val = get(name);
579                 return (std::isdigit(val[0])) ? stoi(val) :
580                         readFlagString(val, flagdesc, flagmask);
581         }
582
583         // N.B. if getStruct() is used to read a non-POD aggregate type,
584         // the behavior is undefined.
585         bool getStruct(std::string name, std::string format, void *out, size_t olen)
586         {
587                 std::string valstr;
588
589                 try {
590                         valstr = get(name);
591                 } catch (SettingNotFoundException &e) {
592                         return false;
593                 }
594
595                 if (!deSerializeStringToStruct(valstr, format, out, olen))
596                         return false;
597
598                 return true;
599         }
600
601         //////////// Try to get value, no exception thrown
602         bool getNoEx(std::string name, std::string &val)
603         {
604                 try {
605                         val = get(name);
606                         return true;
607                 } catch (SettingNotFoundException &e) {
608                         return false;
609                 }
610         }
611
612         // N.B. getFlagStrNoEx() does not set val, but merely modifies it.  Thus,
613         // val must be initialized before using getFlagStrNoEx().  The intention of
614         // this is to simplify modifying a flags field from a default value.
615         bool getFlagStrNoEx(std::string name, u32 &val, FlagDesc *flagdesc)
616         {
617                 try {
618                         u32 flags, flagmask;
619
620                         flags = getFlagStr(name, flagdesc, &flagmask);
621
622                         val &= ~flagmask;
623                         val |=  flags;
624
625                         return true;
626                 } catch (SettingNotFoundException &e) {
627                         return false;
628                 }
629         }
630
631         bool getFloatNoEx(std::string name, float &val)
632         {
633                 try {
634                         val = getFloat(name);
635                         return true;
636                 } catch (SettingNotFoundException &e) {
637                         return false;
638                 }
639         }
640
641         bool getU16NoEx(std::string name, int &val)
642         {
643                 try {
644                         val = getU16(name);
645                         return true;
646                 } catch (SettingNotFoundException &e) {
647                         return false;
648                 }
649         }
650
651         bool getU16NoEx(std::string name, u16 &val)
652         {
653                 try {
654                         val = getU16(name);
655                         return true;
656                 } catch (SettingNotFoundException &e) {
657                         return false;
658                 }
659         }
660
661         bool getS16NoEx(std::string name, int &val)
662         {
663                 try {
664                         val = getU16(name);
665                         return true;
666                 } catch (SettingNotFoundException &e) {
667                         return false;
668                 }
669         }
670
671         bool getS16NoEx(std::string name, s16 &val)
672         {
673                 try {
674                         val = getS16(name);
675                         return true;
676                 } catch (SettingNotFoundException &e) {
677                         return false;
678                 }
679         }
680
681         bool getS32NoEx(std::string name, s32 &val)
682         {
683                 try {
684                         val = getS32(name);
685                         return true;
686                 } catch (SettingNotFoundException &e) {
687                         return false;
688                 }
689         }
690
691         bool getV3FNoEx(std::string name, v3f &val)
692         {
693                 try {
694                         val = getV3F(name);
695                         return true;
696                 } catch (SettingNotFoundException &e) {
697                         return false;
698                 }
699         }
700
701         bool getV2FNoEx(std::string name, v2f &val)
702         {
703                 try {
704                         val = getV2F(name);
705                         return true;
706                 } catch (SettingNotFoundException &e) {
707                         return false;
708                 }
709         }
710
711         bool getU64NoEx(std::string name, u64 &val)
712         {
713                 try {
714                         val = getU64(name);
715                         return true;
716                 } catch (SettingNotFoundException &e) {
717                         return false;
718                 }
719         }
720
721         //////////// Set setting
722
723         // N.B. if setStruct() is used to write a non-POD aggregate type,
724         // the behavior is undefined.
725         bool setStruct(std::string name, std::string format, 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         void setFlagStr(std::string name, u32 flags,
736                 FlagDesc *flagdesc, u32 flagmask)
737         {
738                 set(name, writeFlagString(flags, flagdesc, flagmask));
739         }
740
741         void setBool(std::string name, bool value)
742         {
743                 if(value)
744                         set(name, "true");
745                 else
746                         set(name, "false");
747         }
748
749         void setFloat(std::string name, float value)
750         {
751                 set(name, ftos(value));
752         }
753
754         void setV3F(std::string name, v3f value)
755         {
756                 std::ostringstream os;
757                 os<<"("<<value.X<<","<<value.Y<<","<<value.Z<<")";
758                 set(name, os.str());
759         }
760
761         void setV2F(std::string name, v2f value)
762         {
763                 std::ostringstream os;
764                 os<<"("<<value.X<<","<<value.Y<<")";
765                 set(name, os.str());
766         }
767
768         void setS16(std::string name, s16 value)
769         {
770                 set(name, itos(value));
771         }
772
773         void setS32(std::string name, s32 value)
774         {
775                 set(name, itos(value));
776         }
777
778         void setU64(std::string name, u64 value)
779         {
780                 std::ostringstream os;
781                 os<<value;
782                 set(name, os.str());
783         }
784
785         void clear()
786         {
787                 JMutexAutoLock lock(m_mutex);
788
789                 m_settings.clear();
790                 m_defaults.clear();
791         }
792
793         void updateValue(Settings &other, const std::string &name)
794         {
795                 JMutexAutoLock lock(m_mutex);
796
797                 if(&other == this)
798                         return;
799
800                 try{
801                         std::string val = other.get(name);
802                         m_settings[name] = val;
803                 } catch(SettingNotFoundException &e){
804                 }
805
806                 return;
807         }
808
809         void update(Settings &other)
810         {
811                 JMutexAutoLock lock(m_mutex);
812                 JMutexAutoLock lock2(other.m_mutex);
813
814                 if(&other == this)
815                         return;
816
817                 m_settings.insert(other.m_settings.begin(), other.m_settings.end());
818                 m_defaults.insert(other.m_defaults.begin(), other.m_defaults.end());
819
820                 return;
821         }
822
823         Settings & operator+=(Settings &other)
824         {
825                 JMutexAutoLock lock(m_mutex);
826                 JMutexAutoLock lock2(other.m_mutex);
827
828                 if(&other == this)
829                         return *this;
830
831                 update(other);
832
833                 return *this;
834
835         }
836
837         Settings & operator=(Settings &other)
838         {
839                 JMutexAutoLock lock(m_mutex);
840                 JMutexAutoLock lock2(other.m_mutex);
841
842                 if(&other == this)
843                         return *this;
844
845                 clear();
846                 (*this) += other;
847
848                 return *this;
849         }
850
851 private:
852         std::map<std::string, std::string> m_settings;
853         std::map<std::string, std::string> m_defaults;
854         // All methods that access m_settings/m_defaults directly should lock this.
855         JMutex m_mutex;
856 };
857
858 #endif
859