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