1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include "../stdmisc.h"
18 #include "nel/misc/config_file.h"
21 #include <sys/types.h>
24 #include "nel/misc/file.h"
25 #include "nel/misc/debug.h"
26 #include "nel/misc/path.h"
27 #include "nel/misc/i18n.h"
28 #include "nel/misc/mem_stream.h"
36 using namespace NLMISC
;
38 extern void cfrestart (FILE *); // used to reinit the file
39 extern int cfparse (void *); // used to parse the file
41 extern int cf_CurrentLine
;
42 extern char *cf_CurrentFile
;
43 extern bool cf_Ignore
;
44 extern bool cf_OverwriteExistingVariable
;
45 extern CMemStream cf_ifile
;
47 // put true if you want that the config file class check type when you call asFunctions
48 // (for example, check when you call asInt() that the variable is an int).
49 // when it's false, the function will convert to the wanted type (if he can)
50 const bool CheckType
= false;
51 bool LoadRoot
= false;
56 const char *CConfigFile::CVar::TypeName
[] = { "Integer", "String", "Float", "Boolean" };
58 int CConfigFile::CVar::asInt (int index
) const
60 if (CheckType
&& Type
!= T_INT
) throw EBadType (Name
, Type
, T_INT
);
65 if (index
>= (int)StrValues
.size () || index
< 0) throw EBadSize (Name
, (int)StrValues
.size (), index
);
67 NLMISC::fromString(StrValues
[index
], ret
);
71 if (index
>= (int)RealValues
.size () || index
< 0) throw EBadSize (Name
, (int)RealValues
.size (), index
);
72 return (int)RealValues
[index
];
74 if (index
>= (int)IntValues
.size () || index
< 0) throw EBadSize (Name
, (int)IntValues
.size (), index
);
75 return IntValues
[index
];
79 double CConfigFile::CVar::asDouble (int index
) const
81 if (CheckType
&& Type
!= T_REAL
) throw EBadType (Name
, Type
, T_REAL
);
85 if (index
>= (int)IntValues
.size () || index
< 0) throw EBadSize (Name
, (int)IntValues
.size (), index
);
86 return (double)IntValues
[index
];
89 if (index
>= (int)StrValues
.size () || index
< 0) throw EBadSize (Name
, (int)StrValues
.size (), index
);
91 NLMISC::fromString(StrValues
[index
], val
);
95 if (index
>= (int)RealValues
.size () || index
< 0) throw EBadSize (Name
, (int)RealValues
.size (), index
);
96 return RealValues
[index
];
100 float CConfigFile::CVar::asFloat (int index
) const
102 return (float) asDouble (index
);
105 std::string
CConfigFile::CVar::asString (int index
) const
107 if (CheckType
&& Type
!= T_STRING
) throw EBadType (Name
, Type
, T_STRING
);
111 if (index
>= (int)IntValues
.size () || index
< 0) throw EBadSize (Name
, (int)IntValues
.size (), index
);
112 return toString(IntValues
[index
]);
114 if (index
>= (int)RealValues
.size () || index
< 0) throw EBadSize (Name
, (int)RealValues
.size (), index
);
115 return toString(RealValues
[index
]);
117 if (index
>= (int)StrValues
.size () || index
< 0) throw EBadSize (Name
, (int)StrValues
.size (), index
);
118 return StrValues
[index
];
122 bool CConfigFile::CVar::asBool (int index
) const
127 if (index
>= (int)StrValues
.size () || index
< 0) throw EBadSize (Name
, (int)StrValues
.size (), index
);
128 if(StrValues
[index
] == "true")
137 if (index
>= (int)RealValues
.size () || index
< 0) throw EBadSize (Name
, (int)RealValues
.size (), index
);
138 if ((int)RealValues
[index
] == 1)
147 if (index
>= (int)IntValues
.size () || index
< 0) throw EBadSize (Name
, (int)IntValues
.size (), index
);
148 if (IntValues
[index
] == 1)
159 void CConfigFile::CVar::setAsInt (int val
, int index
)
161 if (Type
!= T_INT
) throw EBadType (Name
, Type
, T_INT
);
162 else if (index
> (int)IntValues
.size () || index
< 0) throw EBadSize (Name
, (int)IntValues
.size (), index
);
163 else if (index
== (int)IntValues
.size ()) IntValues
.push_back(val
);
164 else IntValues
[index
] = val
;
168 void CConfigFile::CVar::setAsDouble (double val
, int index
)
170 if (Type
!= T_REAL
) throw EBadType (Name
, Type
, T_REAL
);
171 else if (index
> (int)RealValues
.size () || index
< 0) throw EBadSize (Name
, (int)RealValues
.size (), index
);
172 else if (index
== (int)RealValues
.size ()) RealValues
.push_back(val
);
173 else RealValues
[index
] = val
;
177 void CConfigFile::CVar::setAsFloat (float val
, int index
)
179 setAsDouble (val
, index
);
182 void CConfigFile::CVar::setAsString (const std::string
&val
, int index
)
184 if (Type
!= T_STRING
) throw EBadType (Name
, Type
, T_STRING
);
185 else if (index
> (int)StrValues
.size () || index
< 0) throw EBadSize (Name
, (int)StrValues
.size (), index
);
186 else if (index
== (int)StrValues
.size ()) StrValues
.push_back(val
);
187 else StrValues
[index
] = val
;
191 void CConfigFile::CVar::forceAsInt (int val
)
201 void CConfigFile::CVar::forceAsDouble (double val
)
205 RealValues
.resize(1);
211 void CConfigFile::CVar::forceAsString (const std::string
&val
)
221 void CConfigFile::CVar::setAsInt (const std::vector
<int> &vals
)
223 if (Type
!= T_INT
) throw EBadType (Name
, Type
, T_INT
);
224 else IntValues
= vals
;
228 void CConfigFile::CVar::setAsDouble (const std::vector
<double> &vals
)
230 if (Type
!= T_REAL
) throw EBadType (Name
, Type
, T_REAL
);
231 else RealValues
= vals
;
235 void CConfigFile::CVar::setAsFloat (const std::vector
<float> &vals
)
237 if (Type
!= T_REAL
) throw EBadType (Name
, Type
, T_REAL
);
241 RealValues
.resize (vals
.size ());
242 for (uint i
= 0; i
< vals
.size (); i
++)
243 RealValues
[i
] = (double)vals
[i
];
248 void CConfigFile::CVar::setAsString (const std::vector
<std::string
> &vals
)
250 if (Type
!= T_STRING
) throw EBadType (Name
, Type
, T_STRING
);
251 else StrValues
= vals
;
255 bool CConfigFile::CVar::operator== (const CVar
& var
) const
257 if (Type
== var
.Type
)
261 case T_INT
: return IntValues
== var
.IntValues
; break;
262 case T_REAL
: return RealValues
== var
.RealValues
; break;
263 case T_STRING
: return StrValues
== var
.StrValues
; break;
270 bool CConfigFile::CVar::operator!= (const CVar
& var
) const
272 return !(*this==var
);
275 void CConfigFile::CVar::add (const CVar
&var
)
277 if (Type
== var
.Type
)
281 case T_INT
: IntValues
.insert (IntValues
.end(), var
.IntValues
.begin(), var
.IntValues
.end()); break;
282 case T_REAL
: RealValues
.insert (RealValues
.end(), var
.RealValues
.begin(), var
.RealValues
.end()); break;
283 case T_STRING
: StrValues
.insert (StrValues
.end(), var
.StrValues
.begin(), var
.StrValues
.end()); break;
289 uint
CConfigFile::CVar::size () const
293 case T_INT
: return (uint
)IntValues
.size ();
294 case T_REAL
: return (uint
)RealValues
.size ();
295 case T_STRING
: return (uint
)StrValues
.size ();
300 CConfigFile::~CConfigFile ()
302 if (_ConfigFiles
== NULL
|| (*_ConfigFiles
).empty ()) return;
304 vector
<CConfigFile
*>::iterator it
= find ((*_ConfigFiles
).begin (), (*_ConfigFiles
).end (), this);
305 if (it
!= (*_ConfigFiles
).end ())
307 (*_ConfigFiles
).erase (it
);
310 if ((*_ConfigFiles
).empty())
317 void CConfigFile::load (const string
&fileName
, bool lookupPaths
)
319 char *locale
= setlocale(LC_NUMERIC
, NULL
);
321 if (!locale
|| strcmp(locale
, "C"))
323 nlerror("Numeric locale not defined to C, an external library possibly redefined it!");
328 nlwarning ("CF: Can't load a empty file name configfile");
333 FileNames
.push_back (fileName
);
335 if (_ConfigFiles
== NULL
)
337 _ConfigFiles
= new std::vector
<CConfigFile
*>;
339 (*CConfigFile::_ConfigFiles
).push_back (this);
340 reparse (lookupPaths
);
342 /* _FileName.clear ();
343 _FileName.push_back (fileName);
345 if (_ConfigFiles == NULL)
347 _ConfigFiles = new std::vector<CConfigFile *>;
349 (*CConfigFile::_ConfigFiles).push_back (this);
352 // If we find a linked config file, load it but don't overload already existing variable
353 CVar *var = getVarPtr ("RootConfigFilename");
356 string RootConfigFilename = var->asString();
357 nlinfo ("RootConfigFilename variable found in the '%s' config file, parse it (%s)", fileName.c_str(), RootConfigFilename.c_str());
359 string path = CFile::getPath(fileName);
364 path += RootConfigFilename;
366 reparse (path.c_str());
372 bool CConfigFile::loaded()
374 return !CConfigFile::FileNames
.empty();
377 uint32
CConfigFile::getVarCount()
379 return (uint32
)_Vars
.size();
383 void CConfigFile::reparse (bool lookupPaths
)
385 if (FileNames
.empty())
387 nlwarning ("CF: Can't reparse config file because file name is empty");
391 string fn
= FileNames
[0];
394 LastModified
.clear ();
402 fn
= CPath::lookup(fn
, true);
406 fn
= NLMISC::CPath::getFullPath(fn
, false);
408 nldebug ("CF: Adding config file '%s' in the config file", fn
.c_str());
409 FileNames
.push_back (fn
);
410 LastModified
.push_back (CFile::getFileModificationDate(fn
));
412 if (!CPath::lookup(fn
, false).empty())
415 CI18N::readTextFile(fn
, content
, true, true);
416 string utf8
= content
.toUtf8();
419 stream
.serialBuffer((uint8
*)(utf8
.data()), (uint
)utf8
.size());
421 if (!cf_ifile
.isReading())
428 cf_CurrentFile
= NULL
;
430 cf_OverwriteExistingVariable
= (FileNames
.size()==1);
431 LoadRoot
= (FileNames
.size()>1);
432 bool parsingOK
= (cfparse (&(_Vars
)) == 0);
436 // write the result of preprocessing in a temp file
437 string debugFileName
;
438 debugFileName
+= "debug_";
439 debugFileName
+= CFile::getFilename(fn
);
441 CI18N::writeTextFile(debugFileName
, content
, true);
442 nlwarning ("CF: Parsing error in file %s line %d, look in '%s' for a preprocessed version of the config file",
445 debugFileName
.c_str());
446 throw EParseError (fn
, cf_CurrentLine
);
449 if (cf_CurrentFile
!= NULL
)
450 free(cf_CurrentFile
);
452 // reset all 'FromLocalFile' flag on created vars before reading next root cfg
453 for (uint i
=0; i
<_Vars
.size(); ++i
)
455 _Vars
[i
].FromLocalFile
= false;
460 nlwarning ("CF: Config file '%s' not found in the path '%s'", fn
.c_str(), CPath::getCurrentPath().c_str());
461 throw EFileNotFound (fn
);
463 // cf_ifile.close ();
466 // If we find a linked config file, load it but don't overload already existing variable
467 CVar
*var
= getVarPtr ("RootConfigFilename");
470 string RootConfigFilename
= var
->asString();
472 if (!NLMISC::CFile::fileExists(RootConfigFilename
))
474 // file is not found, try with the path of the master cfg
475 string path
= NLMISC::CPath::standardizePath (NLMISC::CFile::getPath(FileNames
[0]));
476 RootConfigFilename
= path
+ RootConfigFilename
;
479 RootConfigFilename
= NLMISC::CPath::getFullPath(RootConfigFilename
, false);
481 if (RootConfigFilename
!= fn
)
483 nlinfo ("CF: RootConfigFilename variable found in the '%s' config file, parse the root config file '%s'", fn
.c_str(), RootConfigFilename
.c_str());
484 fn
= RootConfigFilename
;
493 if (_Callback
!= NULL
)
496 /* if (filename == NULL)
498 _LastModified = getLastModified ();
500 nlassert (!_FileName.empty());
502 if (cf_ifile.open (_FileName[0]))
504 // if we clear all the array, we'll lost the callback on variable and all information
509 cf_OverwriteExistingVariable = true;
511 bool parsingOK = (cfparse (&(_Vars)) == 0);
515 nlwarning ("Parsing error in file %s line %d", _FileName.c_str(), cf_CurrentLine);
516 throw EParseError (_FileName, cf_CurrentLine);
521 nlwarning ("ConfigFile '%s' not found in the path '%s'", _FileName.c_str(), CPath::getCurrentPath().c_str());
522 throw EFileNotFound (_FileName);
527 nlassert (strlen(filename)>0);
529 // load external config filename, don't overwrite existing variable
530 if (cf_ifile.open (filename))
535 cf_OverwriteExistingVariable = false;
537 bool parsingOK = (cfparse (&(_Vars)) == 0);
541 nlwarning ("Parsing error in file %s line %d", filename, cf_CurrentLine);
542 throw EParseError (filename, cf_CurrentLine);
547 nlwarning ("RootConfigFilename '%s' not found", _FileName.c_str());
553 if (_Callback != NULL)
562 CConfigFile::CVar
&CConfigFile::getVar (const std::string
&varName
)
564 CVar
*var
= getVarPtr (varName
);
566 throw EUnknownVar (getFilename(), varName
);
572 CConfigFile::CVar
*CConfigFile::getVarPtr (const std::string
&varName
)
575 for (i
= 0; i
< _Vars
.size(); i
++)
577 // the type could be T_UNKNOWN if we add a callback on this name but this var is not in the config file
578 if (_Vars
[i
].Name
== varName
&& (_Vars
[i
].Type
!= CVar::T_UNKNOWN
|| _Vars
[i
].Comp
))
582 // if not found, add it in the array if necessary
583 for (i
= 0; i
< UnknownVariables
.size(); i
++)
584 if(UnknownVariables
[i
] == varName
)
586 if (i
== UnknownVariables
.size())
587 UnknownVariables
.push_back(varName
);
592 bool CConfigFile::exists (const std::string
&varName
)
594 for (uint i
= 0; i
< _Vars
.size(); i
++)
596 // the type could be T_UNKNOWN if we add a callback on this name but this var is not in the config file
597 if (_Vars
[i
].Name
== varName
&& (_Vars
[i
].Type
!= CVar::T_UNKNOWN
|| _Vars
[i
].Comp
))
605 void CConfigFile::save () const
607 char *locale
= setlocale(LC_NUMERIC
, NULL
);
609 if (!locale
|| strcmp(locale
, "C"))
611 nlerror("Numeric locale not defined to C, an external library possibly redefined it!");
614 FILE *fp
= nlfopen (getFilename(), "w");
617 nlwarning ("CF: Couldn't create %s file", getFilename().c_str ());
621 // write the UTF-8 bom in order to be able to re-read a config file with
623 /* ace: we need to test this before commit it
624 static char utf8Header[] = {char(0xef), char(0xbb), char(0xbf), 0};
625 fprintf(fp, utf8Header);
628 for(int i
= 0; i
< (int)_Vars
.size(); i
++)
635 fprintf(fp
, "%-20s = {", _Vars
[i
].Name
.c_str());
636 switch (_Vars
[i
].Type
)
638 case CConfigFile::CVar::T_INT
:
640 for (int it
=0; it
< (int)_Vars
[i
].IntValues
.size(); it
++)
642 if (it
%_Vars
[i
].SaveWrap
== 0)
646 fprintf(fp
, "%d%s", _Vars
[i
].IntValues
[it
], it
<(int)_Vars
[i
].IntValues
.size()-1?", ":" ");
650 case CConfigFile::CVar::T_STRING
:
652 for (int st
=0; st
< (int)_Vars
[i
].StrValues
.size(); st
++)
654 if (st
%_Vars
[i
].SaveWrap
== 0)
658 fprintf(fp
, "\"%s\"%s", _Vars
[i
].StrValues
[st
].c_str(), st
<(int)_Vars
[i
].StrValues
.size()-1?", ":" ");
662 case CConfigFile::CVar::T_REAL
:
664 for (int rt
=0; rt
< (int)_Vars
[i
].RealValues
.size(); rt
++)
666 if (rt
%_Vars
[i
].SaveWrap
== 0)
670 fprintf(fp
, "%.10f%s", _Vars
[i
].RealValues
[rt
], rt
<(int)_Vars
[i
].RealValues
.size()-1?", ":" ");
676 fprintf(fp
, "\n};\n");
680 switch (_Vars
[i
].Type
)
682 case CConfigFile::CVar::T_INT
:
683 fprintf(fp
, "%-20s = %d;\n", _Vars
[i
].Name
.c_str(), _Vars
[i
].IntValues
[0]);
685 case CConfigFile::CVar::T_STRING
:
686 fprintf(fp
, "%-20s = \"%s\";\n", _Vars
[i
].Name
.c_str(), _Vars
[i
].StrValues
[0].c_str());
688 case CConfigFile::CVar::T_REAL
:
689 fprintf(fp
, "%-20s = %.10f;\n", _Vars
[i
].Name
.c_str(), _Vars
[i
].RealValues
[0]);
699 void CConfigFile::display () const
704 void CConfigFile::display (CLog
*log
) const
708 log
->displayRawNL ("Config file %s have %d variables and %d root config file:", getFilename().c_str(), _Vars
.size(), FileNames
.size()-1);
709 log
->displayRaw ("Root config files: ");
710 for(int i
= 1; i
< (int)FileNames
.size(); i
++)
712 log
->displayRaw (FileNames
[i
].c_str());
714 log
->displayRawNL ("");
715 log
->displayRawNL ("------------------------------------------------------");
716 for(int i
= 0; i
< (int)_Vars
.size(); i
++)
718 log
->displayRaw ((_Vars
[i
].Callback
==NULL
)?" ":"CB ");
719 log
->displayRaw ((_Vars
[i
].Root
)?"Root ":" ");
722 switch (_Vars
[i
].Type
)
724 case CConfigFile::CVar::T_INT
:
726 log
->displayRaw ("%-20s { ", _Vars
[i
].Name
.c_str());
727 for (int it
=0; it
< (int)_Vars
[i
].IntValues
.size(); it
++)
729 log
->displayRaw ("'%d' ", _Vars
[i
].IntValues
[it
]);
731 log
->displayRawNL ("}");
734 case CConfigFile::CVar::T_STRING
:
736 log
->displayRaw ("%-20s { ", _Vars
[i
].Name
.c_str());
737 for (int st
=0; st
< (int)_Vars
[i
].StrValues
.size(); st
++)
739 log
->displayRaw ("\"%s\" ", _Vars
[i
].StrValues
[st
].c_str());
741 log
->displayRawNL ("}");
744 case CConfigFile::CVar::T_REAL
:
746 log
->displayRaw ("%-20s { " , _Vars
[i
].Name
.c_str());
747 for (int rt
=0; rt
< (int)_Vars
[i
].RealValues
.size(); rt
++)
749 log
->displayRaw ("`%f` ", _Vars
[i
].RealValues
[rt
]);
751 log
->displayRawNL ("}");
754 case CConfigFile::CVar::T_UNKNOWN
:
756 log
->displayRawNL ("%-20s { }" , _Vars
[i
].Name
.c_str());
761 log
->displayRawNL ("%-20s <default case comp> (%d)" , _Vars
[i
].Name
.c_str(), _Vars
[i
].Type
);
768 switch (_Vars
[i
].Type
)
770 case CConfigFile::CVar::T_INT
:
771 log
->displayRawNL ("%-20s '%d'", _Vars
[i
].Name
.c_str(), _Vars
[i
].IntValues
[0]);
773 case CConfigFile::CVar::T_STRING
:
774 log
->displayRawNL ("%-20s \"%s\"", _Vars
[i
].Name
.c_str(), _Vars
[i
].StrValues
[0].c_str());
776 case CConfigFile::CVar::T_REAL
:
777 log
->displayRawNL ("%-20s `%f`", _Vars
[i
].Name
.c_str(), _Vars
[i
].RealValues
[0]);
779 case CConfigFile::CVar::T_UNKNOWN
:
780 log
->displayRawNL ("%-20s <Unknown>", _Vars
[i
].Name
.c_str());
784 log
->displayRawNL ("%-20s <default case> (%d)" , _Vars
[i
].Name
.c_str(), _Vars
[i
].Type
);
792 void CConfigFile::setCallback (void (*cb
)())
795 if( !FileNames
.empty() )
796 nlinfo ("CF: Setting callback to reload the file '%s' when modified externally", getFilename().c_str());
799 void CConfigFile::setCallback (const string
&VarName
, void (*cb
)(CConfigFile::CVar
&var
))
801 for (vector
<CVar
>::iterator it
= _Vars
.begin (); it
!= _Vars
.end (); it
++)
803 if (VarName
== (*it
).Name
)
806 //nldebug("CF: Setting callback to reload the variable '%s' in the file '%s' when modified externally", VarName.c_str(), getFilename().c_str());
810 // VarName doesn't exist, add it now for the future
814 Var
.Type
= CVar::T_UNKNOWN
;
816 _Vars
.push_back (Var
);
817 //nldebug("CF: Setting callback to reload the variable '%s' in the file '%s' when modified externally (currently unknown)", VarName.c_str(), getFilename().c_str());
820 // ***************************************************************************
823 vector
<CConfigFile
*> *CConfigFile::_ConfigFiles
= NULL
;
825 uint32
CConfigFile::_Timeout
= 1000;
827 void CConfigFile::checkConfigFiles ()
829 if (_ConfigFiles
== NULL
) return;
831 static time_t LastCheckTime
= time (NULL
);
832 if (_Timeout
> 0 && (float)(time (NULL
) - LastCheckTime
)*1000.0f
< (float)_Timeout
) return;
834 LastCheckTime
= time (NULL
);
837 for (vector
<CConfigFile
*>::iterator it
= (*_ConfigFiles
).begin (); it
!= (*_ConfigFiles
).end (); it
++)
840 nlassert ((*it
)->FileNames
.size() == (*it
)->LastModified
.size());
841 for (uint i
= 0; i
< (*it
)->FileNames
.size(); i
++)
843 if ((*it
)->LastModified
[i
] != CFile::getFileModificationDate((*it
)->FileNames
[i
]))
846 (*it
)->LastModified
[i
] = CFile::getFileModificationDate((*it
)->FileNames
[i
]);
855 catch (const EConfigFile
&e
)
857 nlwarning ("CF: Exception will re-read modified config file '%s': %s", (*it
)->getFilename().c_str(), e
.what ());
863 void CConfigFile::setTimeout (uint32 timeout
)
868 void CConfigFile::clear()
873 void CConfigFile::clearVars ()
875 for (vector
<CVar
>::iterator it
= _Vars
.begin (); it
!= _Vars
.end (); it
++)
877 (*it
).Type
= CVar::T_UNKNOWN
;
881 uint
CConfigFile::getNumVar () const
883 return (uint
)_Vars
.size ();
886 CConfigFile::CVar
*CConfigFile::getVar (uint varId
)
888 return &(_Vars
[varId
]);
891 CConfigFile::CVar
*CConfigFile::insertVar (const std::string
&varName
, const CVar
&varToCopy
)
894 CVar
*var
= getVarPtr (varName
);
897 _Vars
.push_back (varToCopy
);
898 var
= &(_Vars
.back ());