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