Clean up log messages everywhere
[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                                 throw SettingNotFoundException("Setting not found");
429                         }
430                 }
431
432                 return n->getValue();
433         }
434
435         bool getBool(std::string name)
436         {
437                 return is_yes(get(name));
438         }
439         
440         bool getFlag(std::string name)
441         {
442                 try
443                 {
444                         return getBool(name);
445                 }
446                 catch(SettingNotFoundException &e)
447                 {
448                         return false;
449                 }
450         }
451
452         // Asks if empty
453         bool getBoolAsk(std::string name, std::string question, bool def)
454         {
455                 // If it is in settings
456                 if(exists(name))
457                         return getBool(name);
458                 
459                 std::string s;
460                 char templine[10];
461                 std::cout<<question<<" [y/N]: ";
462                 std::cin.getline(templine, 10);
463                 s = templine;
464
465                 if(s == "")
466                         return def;
467
468                 return is_yes(s);
469         }
470
471         float getFloat(std::string name)
472         {
473                 return stof(get(name));
474         }
475
476         u16 getU16(std::string name)
477         {
478                 return stoi(get(name), 0, 65535);
479         }
480
481         u16 getU16Ask(std::string name, std::string question, u16 def)
482         {
483                 // If it is in settings
484                 if(exists(name))
485                         return getU16(name);
486                 
487                 std::string s;
488                 char templine[10];
489                 std::cout<<question<<" ["<<def<<"]: ";
490                 std::cin.getline(templine, 10);
491                 s = templine;
492
493                 if(s == "")
494                         return def;
495
496                 return stoi(s, 0, 65535);
497         }
498
499         s16 getS16(std::string name)
500         {
501                 return stoi(get(name), -32768, 32767);
502         }
503
504         s32 getS32(std::string name)
505         {
506                 return stoi(get(name));
507         }
508
509         v3f getV3F(std::string name)
510         {
511                 v3f value;
512                 Strfnd f(get(name));
513                 f.next("(");
514                 value.X = stof(f.next(","));
515                 value.Y = stof(f.next(","));
516                 value.Z = stof(f.next(")"));
517                 return value;
518         }
519
520         v2f getV2F(std::string name)
521         {
522                 v2f value;
523                 Strfnd f(get(name));
524                 f.next("(");
525                 value.X = stof(f.next(","));
526                 value.Y = stof(f.next(")"));
527                 return value;
528         }
529
530         u64 getU64(std::string name)
531         {
532                 u64 value = 0;
533                 std::string s = get(name);
534                 std::istringstream ss(s);
535                 ss>>value;
536                 return value;
537         }
538
539         void setBool(std::string name, bool value)
540         {
541                 if(value)
542                         set(name, "true");
543                 else
544                         set(name, "false");
545         }
546
547         void setS32(std::string name, s32 value)
548         {
549                 set(name, itos(value));
550         }
551
552         void setFloat(std::string name, float value)
553         {
554                 set(name, ftos(value));
555         }
556
557         void setV3F(std::string name, v3f value)
558         {
559                 std::ostringstream os;
560                 os<<"("<<value.X<<","<<value.Y<<","<<value.Z<<")";
561                 set(name, os.str());
562         }
563
564         void setV2F(std::string name, v2f value)
565         {
566                 std::ostringstream os;
567                 os<<"("<<value.X<<","<<value.Y<<")";
568                 set(name, os.str());
569         }
570
571         void setU64(std::string name, u64 value)
572         {
573                 std::ostringstream os;
574                 os<<value;
575                 set(name, os.str());
576         }
577
578         void clear()
579         {
580                 JMutexAutoLock lock(m_mutex);
581                 
582                 m_settings.clear();
583                 m_defaults.clear();
584         }
585
586         void updateValue(Settings &other, const std::string &name)
587         {
588                 JMutexAutoLock lock(m_mutex);
589                 
590                 if(&other == this)
591                         return;
592
593                 try{
594                         std::string val = other.get(name);
595                         m_settings[name] = val;
596                 } catch(SettingNotFoundException &e){
597                 }
598
599                 return;
600         }
601
602         void update(Settings &other)
603         {
604                 JMutexAutoLock lock(m_mutex);
605                 JMutexAutoLock lock2(other.m_mutex);
606                 
607                 if(&other == this)
608                         return;
609
610                 for(core::map<std::string, std::string>::Iterator
611                                 i = other.m_settings.getIterator();
612                                 i.atEnd() == false; i++)
613                 {
614                         m_settings[i.getNode()->getKey()] = i.getNode()->getValue();
615                 }
616                 
617                 for(core::map<std::string, std::string>::Iterator
618                                 i = other.m_defaults.getIterator();
619                                 i.atEnd() == false; i++)
620                 {
621                         m_defaults[i.getNode()->getKey()] = i.getNode()->getValue();
622                 }
623
624                 return;
625         }
626
627         Settings & operator+=(Settings &other)
628         {
629                 JMutexAutoLock lock(m_mutex);
630                 JMutexAutoLock lock2(other.m_mutex);
631                 
632                 if(&other == this)
633                         return *this;
634
635                 for(core::map<std::string, std::string>::Iterator
636                                 i = other.m_settings.getIterator();
637                                 i.atEnd() == false; i++)
638                 {
639                         m_settings.insert(i.getNode()->getKey(),
640                                         i.getNode()->getValue());
641                 }
642                 
643                 for(core::map<std::string, std::string>::Iterator
644                                 i = other.m_defaults.getIterator();
645                                 i.atEnd() == false; i++)
646                 {
647                         m_defaults.insert(i.getNode()->getKey(),
648                                         i.getNode()->getValue());
649                 }
650
651                 return *this;
652
653         }
654
655         Settings & operator=(Settings &other)
656         {
657                 JMutexAutoLock lock(m_mutex);
658                 JMutexAutoLock lock2(other.m_mutex);
659                 
660                 if(&other == this)
661                         return *this;
662
663                 clear();
664                 (*this) += other;
665                 
666                 return *this;
667         }
668
669 private:
670         core::map<std::string, std::string> m_settings;
671         core::map<std::string, std::string> m_defaults;
672         // All methods that access m_settings/m_defaults directly should lock this.
673         JMutex m_mutex;
674 };
675
676 #endif
677