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