]> git.lizzy.rs Git - dragonfireclient.git/blob - src/settings.h
1a29ef00a398fd4460e90d8c2b2b6d5da06978d1
[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         void setS32(std::string name, s32 value)
578         {
579                 set(name, itos(value));
580         }
581
582         void setFloat(std::string name, float value)
583         {
584                 set(name, ftos(value));
585         }
586
587         void setV3F(std::string name, v3f value)
588         {
589                 std::ostringstream os;
590                 os<<"("<<value.X<<","<<value.Y<<","<<value.Z<<")";
591                 set(name, os.str());
592         }
593
594         void setV2F(std::string name, v2f value)
595         {
596                 std::ostringstream os;
597                 os<<"("<<value.X<<","<<value.Y<<")";
598                 set(name, os.str());
599         }
600
601         void setU64(std::string name, u64 value)
602         {
603                 std::ostringstream os;
604                 os<<value;
605                 set(name, os.str());
606         }
607
608         void clear()
609         {
610                 JMutexAutoLock lock(m_mutex);
611                 
612                 m_settings.clear();
613                 m_defaults.clear();
614         }
615
616         void updateValue(Settings &other, const std::string &name)
617         {
618                 JMutexAutoLock lock(m_mutex);
619                 
620                 if(&other == this)
621                         return;
622
623                 try{
624                         std::string val = other.get(name);
625                         m_settings[name] = val;
626                 } catch(SettingNotFoundException &e){
627                 }
628
629                 return;
630         }
631
632         void update(Settings &other)
633         {
634                 JMutexAutoLock lock(m_mutex);
635                 JMutexAutoLock lock2(other.m_mutex);
636                 
637                 if(&other == this)
638                         return;
639
640                 for(core::map<std::string, std::string>::Iterator
641                                 i = other.m_settings.getIterator();
642                                 i.atEnd() == false; i++)
643                 {
644                         m_settings[i.getNode()->getKey()] = i.getNode()->getValue();
645                 }
646                 
647                 for(core::map<std::string, std::string>::Iterator
648                                 i = other.m_defaults.getIterator();
649                                 i.atEnd() == false; i++)
650                 {
651                         m_defaults[i.getNode()->getKey()] = i.getNode()->getValue();
652                 }
653
654                 return;
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                 for(core::map<std::string, std::string>::Iterator
666                                 i = other.m_settings.getIterator();
667                                 i.atEnd() == false; i++)
668                 {
669                         m_settings.insert(i.getNode()->getKey(),
670                                         i.getNode()->getValue());
671                 }
672                 
673                 for(core::map<std::string, std::string>::Iterator
674                                 i = other.m_defaults.getIterator();
675                                 i.atEnd() == false; i++)
676                 {
677                         m_defaults.insert(i.getNode()->getKey(),
678                                         i.getNode()->getValue());
679                 }
680
681                 return *this;
682
683         }
684
685         Settings & operator=(Settings &other)
686         {
687                 JMutexAutoLock lock(m_mutex);
688                 JMutexAutoLock lock2(other.m_mutex);
689                 
690                 if(&other == this)
691                         return *this;
692
693                 clear();
694                 (*this) += other;
695                 
696                 return *this;
697         }
698
699 private:
700         core::map<std::string, std::string> m_settings;
701         core::map<std::string, std::string> m_defaults;
702         // All methods that access m_settings/m_defaults directly should lock this.
703         JMutex m_mutex;
704 };
705
706 #endif
707