9eb2254f06423c742da95fd76a54028ac4736f84
[oweals/minetest.git] / src / settings.h
1 /*
2 Minetest-c55
3 Copyright (C) 2010-2011 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 General Public License as published by
7 the Free Software Foundation; either version 2 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 General Public License for more details.
14
15 You should have received a copy of the GNU 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 "common_irrlicht.h"
24 #include <string>
25 #include <jthread.h>
26 #include <jmutex.h>
27 #include <jmutexautolock.h>
28 #include "strfnd.h"
29 #include <iostream>
30 #include <fstream>
31 #include <sstream>
32 #include "debug.h"
33 #include "utility.h"
34 #include "log.h"
35
36 enum ValueType
37 {
38         VALUETYPE_STRING,
39         VALUETYPE_FLAG // Doesn't take any arguments
40 };
41
42 struct ValueSpec
43 {
44         ValueSpec(ValueType a_type, const char *a_help=NULL)
45         {
46                 type = a_type;
47                 help = a_help;
48         }
49         ValueType type;
50         const char *help;
51 };
52
53 class Settings
54 {
55 public:
56         Settings()
57         {
58                 m_mutex.Init();
59         }
60
61         void writeLines(std::ostream &os)
62         {
63                 JMutexAutoLock lock(m_mutex);
64                 
65                 for(core::map<std::string, std::string>::Iterator
66                                 i = m_settings.getIterator();
67                                 i.atEnd() == false; i++)
68                 {
69                         std::string name = i.getNode()->getKey();
70                         std::string value = i.getNode()->getValue();
71                         os<<name<<" = "<<value<<"\n";
72                 }
73         }
74
75         bool parseConfigLine(const std::string &line)
76         {
77                 JMutexAutoLock lock(m_mutex);
78                 
79                 std::string trimmedline = trim(line);
80                 
81                 // Ignore empty lines and comments
82                 if(trimmedline.size() == 0 || trimmedline[0] == '#')
83                         return true;
84
85                 //infostream<<"trimmedline=\""<<trimmedline<<"\""<<std::endl;
86
87                 Strfnd sf(trim(line));
88
89                 std::string name = sf.next("=");
90                 name = trim(name);
91
92                 if(name == "")
93                         return true;
94                 
95                 std::string value = sf.next("\n");
96                 value = trim(value);
97
98                 /*infostream<<"Config name=\""<<name<<"\" value=\""
99                                 <<value<<"\""<<std::endl;*/
100                 
101                 m_settings[name] = value;
102                 
103                 return true;
104         }
105
106         void parseConfigLines(std::istream &is, const std::string &endstring)
107         {
108                 for(;;){
109                         if(is.eof())
110                                 break;
111                         std::string line;
112                         std::getline(is, line);
113                         std::string trimmedline = trim(line);
114                         if(endstring != ""){
115                                 if(trimmedline == endstring)
116                                         break;
117                         }
118                         parseConfigLine(line);
119                 }
120         }
121
122         // Returns false on EOF
123         bool parseConfigObject(std::istream &is)
124         {
125                 if(is.eof())
126                         return false;
127                 
128                 /*
129                         NOTE: This function might be expanded to allow multi-line
130                               settings.
131                 */
132                 std::string line;
133                 std::getline(is, line);
134                 //infostream<<"got line: \""<<line<<"\""<<std::endl;
135
136                 return parseConfigLine(line);
137         }
138
139         /*
140                 Read configuration file
141
142                 Returns true on success
143         */
144         bool readConfigFile(const char *filename)
145         {
146                 std::ifstream is(filename);
147                 if(is.good() == false)
148                         return false;
149
150                 infostream<<"Parsing configuration file: \""
151                                 <<filename<<"\""<<std::endl;
152                                 
153                 while(parseConfigObject(is));
154                 
155                 return true;
156         }
157
158         /*
159                 Reads a configuration object from stream (usually a single line)
160                 and adds it to dst.
161                 
162                 Preserves comments and empty lines.
163
164                 Settings that were added to dst are also added to updated.
165                 key of updated is setting name, value of updated is dummy.
166
167                 Returns false on EOF
168         */
169         bool getUpdatedConfigObject(std::istream &is,
170                         core::list<std::string> &dst,
171                         core::map<std::string, bool> &updated,
172                         bool &value_changed)
173         {
174                 JMutexAutoLock lock(m_mutex);
175                 
176                 if(is.eof())
177                         return false;
178                 
179                 // NOTE: This function will be expanded to allow multi-line settings
180                 std::string line;
181                 std::getline(is, line);
182
183                 std::string trimmedline = trim(line);
184
185                 std::string line_end = "";
186                 if(is.eof() == false)
187                         line_end = "\n";
188                 
189                 // Ignore empty lines and comments
190                 if(trimmedline.size() == 0 || trimmedline[0] == '#')
191                 {
192                         dst.push_back(line+line_end);
193                         return true;
194                 }
195
196                 Strfnd sf(trim(line));
197
198                 std::string name = sf.next("=");
199                 name = trim(name);
200
201                 if(name == "")
202                 {
203                         dst.push_back(line+line_end);
204                         return true;
205                 }
206                 
207                 std::string value = sf.next("\n");
208                 value = trim(value);
209                 
210                 if(m_settings.find(name))
211                 {
212                         std::string newvalue = m_settings[name];
213                         
214                         if(newvalue != value)
215                         {
216                                 infostream<<"Changing value of \""<<name<<"\" = \""
217                                                 <<value<<"\" -> \""<<newvalue<<"\""
218                                                 <<std::endl;
219                                 value_changed = true;
220                         }
221
222                         dst.push_back(name + " = " + newvalue + line_end);
223
224                         updated[name] = true;
225                 }
226                 
227                 return true;
228         }
229
230         /*
231                 Updates configuration file
232
233                 Returns true on success
234         */
235         bool updateConfigFile(const char *filename)
236         {
237                 infostream<<"Updating configuration file: \""
238                                 <<filename<<"\""<<std::endl;
239                 
240                 core::list<std::string> objects;
241                 core::map<std::string, bool> updated;
242                 bool something_actually_changed = false;
243                 
244                 // Read and modify stuff
245                 {
246                         std::ifstream is(filename);
247                         if(is.good() == false)
248                         {
249                                 infostream<<"updateConfigFile():"
250                                                 " Error opening configuration file"
251                                                 " for reading: \""
252                                                 <<filename<<"\""<<std::endl;
253                         }
254                         else
255                         {
256                                 while(getUpdatedConfigObject(is, objects, updated,
257                                                 something_actually_changed));
258                         }
259                 }
260                 
261                 JMutexAutoLock lock(m_mutex);
262                 
263                 // If something not yet determined to have been changed, check if
264                 // any new stuff was added
265                 if(!something_actually_changed){
266                         for(core::map<std::string, std::string>::Iterator
267                                         i = m_settings.getIterator();
268                                         i.atEnd() == false; i++)
269                         {
270                                 if(updated.find(i.getNode()->getKey()))
271                                         continue;
272                                 something_actually_changed = true;
273                                 break;
274                         }
275                 }
276                 
277                 // If nothing was actually changed, skip writing the file
278                 if(!something_actually_changed){
279                         infostream<<"Skipping writing of "<<filename
280                                         <<" because content wouldn't be modified"<<std::endl;
281                         return true;
282                 }
283                 
284                 // Write stuff back
285                 {
286                         std::ofstream os(filename);
287                         if(os.good() == false)
288                         {
289                                 errorstream<<"Error opening configuration file"
290                                                 " for writing: \""
291                                                 <<filename<<"\""<<std::endl;
292                                 return false;
293                         }
294                         
295                         /*
296                                 Write updated stuff
297                         */
298                         for(core::list<std::string>::Iterator
299                                         i = objects.begin();
300                                         i != objects.end(); i++)
301                         {
302                                 os<<(*i);
303                         }
304
305                         /*
306                                 Write stuff that was not already in the file
307                         */
308                         for(core::map<std::string, std::string>::Iterator
309                                         i = m_settings.getIterator();
310                                         i.atEnd() == false; i++)
311                         {
312                                 if(updated.find(i.getNode()->getKey()))
313                                         continue;
314                                 std::string name = i.getNode()->getKey();
315                                 std::string value = i.getNode()->getValue();
316                                 infostream<<"Adding \""<<name<<"\" = \""<<value<<"\""
317                                                 <<std::endl;
318                                 os<<name<<" = "<<value<<"\n";
319                         }
320                 }
321                 
322                 return true;
323         }
324
325         /*
326                 NOTE: Types of allowed_options are ignored
327
328                 returns true on success
329         */
330         bool parseCommandLine(int argc, char *argv[],
331                         core::map<std::string, ValueSpec> &allowed_options)
332         {
333                 int i=1;
334                 for(;;)
335                 {
336                         if(i >= argc)
337                                 break;
338                         std::string argname = argv[i];
339                         if(argname.substr(0, 2) != "--")
340                         {
341                                 errorstream<<"Invalid command-line parameter \""
342                                                 <<argname<<"\": --<option> expected."<<std::endl;
343                                 return false;
344                         }
345                         i++;
346
347                         std::string name = argname.substr(2);
348
349                         core::map<std::string, ValueSpec>::Node *n;
350                         n = allowed_options.find(name);
351                         if(n == NULL)
352                         {
353                                 errorstream<<"Unknown command-line parameter \""
354                                                 <<argname<<"\""<<std::endl;
355                                 return false;
356                         }
357
358                         ValueType type = n->getValue().type;
359
360                         std::string value = "";
361                         
362                         if(type == VALUETYPE_FLAG)
363                         {
364                                 value = "true";
365                         }
366                         else
367                         {
368                                 if(i >= argc)
369                                 {
370                                         errorstream<<"Invalid command-line parameter \""
371                                                         <<name<<"\": missing value"<<std::endl;
372                                         return false;
373                                 }
374                                 value = argv[i];
375                                 i++;
376                         }
377                         
378
379                         infostream<<"Valid command-line parameter: \""
380                                         <<name<<"\" = \""<<value<<"\""
381                                         <<std::endl;
382                         set(name, value);
383                 }
384
385                 return true;
386         }
387
388         void set(std::string name, std::string value)
389         {
390                 JMutexAutoLock lock(m_mutex);
391                 
392                 m_settings[name] = value;
393         }
394
395         void set(std::string name, const char *value)
396         {
397                 JMutexAutoLock lock(m_mutex);
398
399                 m_settings[name] = value;
400         }
401
402
403         void setDefault(std::string name, std::string value)
404         {
405                 JMutexAutoLock lock(m_mutex);
406                 
407                 m_defaults[name] = value;
408         }
409
410         bool exists(std::string name)
411         {
412                 JMutexAutoLock lock(m_mutex);
413                 
414                 return (m_settings.find(name) || m_defaults.find(name));
415         }
416
417         std::string get(std::string name)
418         {
419                 JMutexAutoLock lock(m_mutex);
420                 
421                 core::map<std::string, std::string>::Node *n;
422                 n = m_settings.find(name);
423                 if(n == NULL)
424                 {
425                         n = m_defaults.find(name);
426                         if(n == NULL)
427                         {
428                                 infostream<<"Settings: Setting not found: \""
429                                                 <<name<<"\""<<std::endl;
430                                 throw SettingNotFoundException("Setting not found");
431                         }
432                 }
433
434                 return n->getValue();
435         }
436
437         bool getBool(std::string name)
438         {
439                 return is_yes(get(name));
440         }
441         
442         bool getFlag(std::string name)
443         {
444                 try
445                 {
446                         return getBool(name);
447                 }
448                 catch(SettingNotFoundException &e)
449                 {
450                         return false;
451                 }
452         }
453
454         // Asks if empty
455         bool getBoolAsk(std::string name, std::string question, bool def)
456         {
457                 // If it is in settings
458                 if(exists(name))
459                         return getBool(name);
460                 
461                 std::string s;
462                 char templine[10];
463                 std::cout<<question<<" [y/N]: ";
464                 std::cin.getline(templine, 10);
465                 s = templine;
466
467                 if(s == "")
468                         return def;
469
470                 return is_yes(s);
471         }
472
473         float getFloat(std::string name)
474         {
475                 return stof(get(name));
476         }
477
478         u16 getU16(std::string name)
479         {
480                 return stoi(get(name), 0, 65535);
481         }
482
483         u16 getU16Ask(std::string name, std::string question, u16 def)
484         {
485                 // If it is in settings
486                 if(exists(name))
487                         return getU16(name);
488                 
489                 std::string s;
490                 char templine[10];
491                 std::cout<<question<<" ["<<def<<"]: ";
492                 std::cin.getline(templine, 10);
493                 s = templine;
494
495                 if(s == "")
496                         return def;
497
498                 return stoi(s, 0, 65535);
499         }
500
501         s16 getS16(std::string name)
502         {
503                 return stoi(get(name), -32768, 32767);
504         }
505
506         s32 getS32(std::string name)
507         {
508                 return stoi(get(name));
509         }
510
511         v3f getV3F(std::string name)
512         {
513                 v3f value;
514                 Strfnd f(get(name));
515                 f.next("(");
516                 value.X = stof(f.next(","));
517                 value.Y = stof(f.next(","));
518                 value.Z = stof(f.next(")"));
519                 return value;
520         }
521
522         v2f getV2F(std::string name)
523         {
524                 v2f value;
525                 Strfnd f(get(name));
526                 f.next("(");
527                 value.X = stof(f.next(","));
528                 value.Y = stof(f.next(")"));
529                 return value;
530         }
531
532         u64 getU64(std::string name)
533         {
534                 u64 value = 0;
535                 std::string s = get(name);
536                 std::istringstream ss(s);
537                 ss>>value;
538                 return value;
539         }
540
541         void setBool(std::string name, bool value)
542         {
543                 if(value)
544                         set(name, "true");
545                 else
546                         set(name, "false");
547         }
548
549         void setS32(std::string name, s32 value)
550         {
551                 set(name, itos(value));
552         }
553
554         void setFloat(std::string name, float value)
555         {
556                 set(name, ftos(value));
557         }
558
559         void setV3F(std::string name, v3f value)
560         {
561                 std::ostringstream os;
562                 os<<"("<<value.X<<","<<value.Y<<","<<value.Z<<")";
563                 set(name, os.str());
564         }
565
566         void setV2F(std::string name, v2f value)
567         {
568                 std::ostringstream os;
569                 os<<"("<<value.X<<","<<value.Y<<")";
570                 set(name, os.str());
571         }
572
573         void setU64(std::string name, u64 value)
574         {
575                 std::ostringstream os;
576                 os<<value;
577                 set(name, os.str());
578         }
579
580         void clear()
581         {
582                 JMutexAutoLock lock(m_mutex);
583                 
584                 m_settings.clear();
585                 m_defaults.clear();
586         }
587
588         void updateValue(Settings &other, const std::string &name)
589         {
590                 JMutexAutoLock lock(m_mutex);
591                 
592                 if(&other == this)
593                         return;
594
595                 try{
596                         std::string val = other.get(name);
597                         m_settings[name] = val;
598                 } catch(SettingNotFoundException &e){
599                 }
600
601                 return;
602         }
603
604         void update(Settings &other)
605         {
606                 JMutexAutoLock lock(m_mutex);
607                 JMutexAutoLock lock2(other.m_mutex);
608                 
609                 if(&other == this)
610                         return;
611
612                 for(core::map<std::string, std::string>::Iterator
613                                 i = other.m_settings.getIterator();
614                                 i.atEnd() == false; i++)
615                 {
616                         m_settings[i.getNode()->getKey()] = i.getNode()->getValue();
617                 }
618                 
619                 for(core::map<std::string, std::string>::Iterator
620                                 i = other.m_defaults.getIterator();
621                                 i.atEnd() == false; i++)
622                 {
623                         m_defaults[i.getNode()->getKey()] = i.getNode()->getValue();
624                 }
625
626                 return;
627         }
628
629         Settings & operator+=(Settings &other)
630         {
631                 JMutexAutoLock lock(m_mutex);
632                 JMutexAutoLock lock2(other.m_mutex);
633                 
634                 if(&other == this)
635                         return *this;
636
637                 for(core::map<std::string, std::string>::Iterator
638                                 i = other.m_settings.getIterator();
639                                 i.atEnd() == false; i++)
640                 {
641                         m_settings.insert(i.getNode()->getKey(),
642                                         i.getNode()->getValue());
643                 }
644                 
645                 for(core::map<std::string, std::string>::Iterator
646                                 i = other.m_defaults.getIterator();
647                                 i.atEnd() == false; i++)
648                 {
649                         m_defaults.insert(i.getNode()->getKey(),
650                                         i.getNode()->getValue());
651                 }
652
653                 return *this;
654
655         }
656
657         Settings & operator=(Settings &other)
658         {
659                 JMutexAutoLock lock(m_mutex);
660                 JMutexAutoLock lock2(other.m_mutex);
661                 
662                 if(&other == this)
663                         return *this;
664
665                 clear();
666                 (*this) += other;
667                 
668                 return *this;
669         }
670
671 private:
672         core::map<std::string, std::string> m_settings;
673         core::map<std::string, std::string> m_defaults;
674         // All methods that access m_settings/m_defaults directly should lock this.
675         JMutex m_mutex;
676 };
677
678 #endif
679