📄 appconf.cpp
字号:
}
// other
// -----
// get absolute name
char *FileConfig::ConfigGroup::FullName() const
{
char *szFullName;
if ( m_pParent == NULL ) {
// root group has no name
return NULL;
}
else {
char *pParentFullName = m_pParent->FullName();
if ( pParentFullName == NULL ) {
szFullName = new char[Strlen(m_szName) + 1];
strcpy(szFullName, m_szName);
}
else {
size_t len = Strlen(pParentFullName);
szFullName = new char[len + Strlen(m_szName) + 2];
strcpy(szFullName, pParentFullName);
szFullName[len] = APPCONF_PATH_SEPARATOR; // unfortunately strcat doesn't
szFullName[len + 1] = '\0'; // take 'char' argument
strcat(szFullName, m_szName);
delete [] pParentFullName;
}
}
return szFullName;
}
// set dirty flag and propagate it as needed
// NB: when we set dirty, propagate to parent; when clear - to children
void FileConfig::ConfigGroup::SetDirty(Bool bDirty)
{
m_bDirty = bDirty;
if ( bDirty ) {
if ( m_pParent != NULL )
m_pParent->SetDirty();
}
else {
ConfigEntry *pEntry;
for ( pEntry = Entries(); pEntry != NULL; pEntry = pEntry->Next() )
pEntry->SetDirty(FALSE);
ConfigGroup *pGroup;
for ( pGroup = Subgroup(); pGroup != NULL; pGroup = pGroup->Next() )
pGroup->SetDirty(FALSE);
}
}
// write changed data
Bool FileConfig::ConfigGroup::flush(std::ostream *ostr)
{
// write all entries that were changed in this group
Bool bFirstDirty = TRUE;
ConfigEntry *pEntry;
for ( pEntry = Entries(); pEntry != NULL; pEntry = pEntry->Next() ) {
if ( pEntry->IsDirty() && pEntry->Value()) {
if ( bFirstDirty ) {
// output preceding comment for this group if any
if ( Comment() != NULL ) {
*ostr << Comment();
}
// output group header
char *pName = FullName();
if ( pName != NULL ) {
*ostr << '[' << pName << ']';
if ( pEntry->Comment() == NULL )
*ostr << std::endl;
delete [] pName;
}
//else: it's the top (default) group
bFirstDirty = FALSE; // only first time
}
// output preceding comment for this entry if any
if ( pEntry->Comment() != NULL ) {
*ostr << pEntry->Comment();
}
char *szFilteredValue = filterOut(pEntry->Value());
*ostr << pEntry->Name() << " = " << szFilteredValue << std::endl;
delete [] szFilteredValue;
pEntry->SetDirty(FALSE);
}
}
// flush all subgroups
ConfigGroup *pGroup;
Bool bOk = TRUE;
for ( pGroup = Subgroup(); pGroup != NULL; pGroup = pGroup->Next() ) {
if ( pGroup->IsDirty() && !pGroup->flush(ostr) ) {
bOk = FALSE;
}
}
return bOk;
}
// associate a comment with this group (comments are all we ignore,
// including real comments and whitespace)
void FileConfig::ConfigGroup::SetComment(char *szComment)
{
assert( m_szComment == NULL ); // should be done only once
m_szComment = szComment;
}
// ----------------------------------------------------------------------------
// ctor and dtor
// ----------------------------------------------------------------------------
// common part of both constructors
void FileConfig::Init()
{
// top group always exists and must be created before reading from files
m_pRootGroup = new ConfigGroup(NULL, NULL, "");
m_szComment = NULL;
m_bOk = FALSE; // not (yet) initialized ok
m_bExpandVariables = FALSE; // don't do it automatically by default
}
// ctor reads data from files
FileConfig::FileConfig(const char *szFileName, Bool bLocalOnly, Bool bUseSubDir)
{
Init();
m_bUseSubDir = bUseSubDir;
m_szFileName = new char[Strlen(szFileName) + 1];
strcpy(m_szFileName, szFileName);
// read global (system wide) file
// ------------------------------
std::ifstream inpStream;
if ( !bLocalOnly ) {
m_szFullFileName = GlobalConfigFile();
inpStream.open(m_szFullFileName, std::ios::in);
if ( inpStream ) {
m_bParsingLocal = FALSE;
m_bOk = readStream(&inpStream);
}
inpStream.close();
inpStream.clear();
}
// read local (user's) file
// ------------------------
m_szFullFileName = LocalConfigFile();
if ( m_szFullFileName != NULL ) {
inpStream.open(m_szFullFileName, std::ios::in);
if ( inpStream ) {
m_bParsingLocal = TRUE;
if ( readStream(&inpStream) ) {
m_bOk = TRUE;
}
//else: depends on global file - if it was read ok, it's ok
}
}
m_pCurGroup = m_pRootGroup;
BaseConfig::setCurrentPath("");
}
// ctor reads data from files
FileConfig::FileConfig(std::istream *iStream)
{
Init();
m_szFileName = NULL;
if ( iStream == NULL )
return;
m_bParsingLocal = TRUE;
m_bOk = readStream(iStream);
m_pCurGroup = m_pRootGroup;
BaseConfig::setCurrentPath("");
}
FileConfig::FileConfig(void)
{
Init();
m_szFileName = NULL;
}
void
FileConfig::readFile(const char *szFileName)
{
std::ifstream inpStream;
Init();
m_szFileName = new char[Strlen(szFileName) + 1];
strcpy(m_szFileName, szFileName);
m_szFullFileName = m_szFileName;
inpStream.open(m_szFullFileName, std::ios::in);
if ( inpStream ) {
m_bParsingLocal = TRUE;
if ( readStream(&inpStream) ) {
m_bOk = TRUE;
}
}
m_pCurGroup = m_pRootGroup;
BaseConfig::setCurrentPath("");
}
// dtor writes changes
FileConfig::~FileConfig()
{
if( m_szFileName != NULL ) // if empty, was intialised from stream
flush();
if ( m_szComment != NULL )
delete [] m_szComment;
delete m_pRootGroup;
if ( m_szFileName != NULL )
delete [] m_szFileName;
}
// ----------------------------------------------------------------------------
// config files standard locations
// ----------------------------------------------------------------------------
// ### buffer overflows in sight...
const char *FileConfig::GlobalConfigFile() const
{
static char s_szBuf[MAX_PATH];
// check if file has extension
Bool bNoExt = strchr(m_szFileName, '.') == NULL;
#ifdef __unix__
strcpy(s_szBuf, "/etc/");
strncat(s_szBuf, m_szFileName, MAX_PATH - 11); // "/etc/", ".conf", \0
s_szBuf[MAX_PATH - 6] = 0;
if ( bNoExt )
strcat(s_szBuf, ".conf");
#else // Windows
char szWinDir[MAX_PATH];
::GetWindowsDirectory(szWinDir, MAX_PATH);
strcpy(s_szBuf, szWinDir);
strcat(s_szBuf, "\\");
strcat(s_szBuf, m_szFileName);
if ( bNoExt )
strcat(s_szBuf, ".INI");
#endif // UNIX/Win
return s_szBuf;
}
// ### buffer overflows in sight...
const char *FileConfig::LocalConfigFile() const
{
static char s_szBuf[MAX_PATH];
int maxlen;
maxlen = MAX_PATH - 2 - strlen (m_szFileName) - 7; // "/.", m_szFileName, "/config"
#ifdef __unix__
const char *szHome = getenv("HOME");
if ( szHome == NULL ) {
// we're homeless...
LogInfo(_("can't find user's HOME, looking for config file in current directory."));
szHome = ".";
}
strncpy(s_szBuf, szHome, maxlen - 1);
s_szBuf[maxlen - 1] = 0;
strcat(s_szBuf, "/.");
strcat(s_szBuf, m_szFileName);
if(m_bUseSubDir) // look for ~/.appname/config instead of ~/.appname
{
mkdir(s_szBuf, 0755); // try to make it, just in case it
// didn't exist yet
strcat(s_szBuf, "/config");
}
#else // Windows
#ifdef __WIN32__
maxlen -= 4; // ".INI"
int wmaxlen;
const char *szHome = getenv("HOMEDRIVE");
if ( szHome == NULL )
szHome = "";
strncpy(s_szBuf, szHome, maxlen - 1);
s_szBuf[maxlen - 1] = 0;
szHome = getenv("HOMEPATH");
wmaxlen = maxlen - 1 - strlen (s_szBuf);
if ( szHome == NULL )
strncpy(s_szBuf, ".", wmaxlen);
else
strncat(s_szBuf, szHome, wmaxlen);
strcat(s_szBuf, m_szFileName);
if ( strchr(m_szFileName, '.') == NULL )
strcat(s_szBuf, ".INI");
#else // Win16
// Win 3.1 has no idea about HOME, so we use only the system wide file
if ( !bLocalOnly ) {
// we've already read it
s_szBuf = NULL;
}
else {
s_szBuf = GlobalConfigFile();
}
}
#endif // WIN16/32
#endif // UNIX/Win
return s_szBuf;
}
// ----------------------------------------------------------------------------
// read/write
// ----------------------------------------------------------------------------
// parse the stream and create all groups and entries found in it
Bool FileConfig::readStream(std::istream *istr, ConfigGroup *pRootGroup)
{
char szBuf[APPCONF_STRBUFLEN];
m_pCurGroup = pRootGroup == NULL ? m_pRootGroup : pRootGroup;
m_uiLine = 1;
for (;;) {
istr->getline(szBuf, APPCONF_STRBUFLEN, '\n');
if ( istr->eof() ) {
// last line may contain text also
// (if it's not terminated with '\n' EOF is returned)
return parseLine(szBuf);
}
if ( !istr->good() || !parseLine(szBuf) )
return FALSE;
m_uiLine++;
}
}
// parses one line of config file, returns FALSE on error
Bool FileConfig::parseLine(const char *psz)
{
size_t len; // temp var
const char *pStart = psz;
// eat whitespace
while ( isspace(*psz) ) psz++;
// ignore empty lines or comments
if ( *psz == '#' || *psz == ';' || *psz == '\0' ) {
if ( *pStart != '\0' )
AppendCommentLine(pStart);
//else
// AppendCommentLine();
return TRUE;
}
if ( *psz == '[' ) { // a new group
const char *pEnd = ++psz;
while ( *pEnd != ']' ) {
if ( IsValid(*pEnd) ) {
// continue reading the group name
pEnd++;
}
else {
if ( *pEnd != APPCONF_PATH_SEPARATOR ) {
LogError(_("file '%s': unexpected character at line %d (missing ']'?)"),
m_szFullFileName, m_uiLine);
return FALSE;
}
}
}
len = pEnd - psz;
char *szGroup = new char[len + 2];
szGroup[0] = APPCONF_PATH_SEPARATOR; // always considered as abs path
szGroup[1] = '\0';
strncat(szGroup, psz, len);
// will create it if doesn't yet exist
setCurrentPath(szGroup);
// was there a comment before it?
if ( m_szComment != NULL ) {
m_pCurGroup->SetComment(m_szComment);
m_szComment = NULL;
}
delete [] szGroup;
// are there any comments after it?
Bool bComment = FALSE;
for ( pStart = ++pEnd ; *pEnd != '\0'; pEnd++ ) {
switch ( *pEnd ) {
case '#':
case ';':
bComment = TRUE;
break;
case ' ':
case '\t':
// ignore whitespace ('\n' impossible here)
break;
default:
if ( !bComment ) {
LogWarning(_("file '%s', line %d: '%s' ignored after group header."),
m_szFullFileName, m_uiLine, pEnd);
return TRUE;
}
}
}
if ( bComment ) {
AppendCommentLine(pStart);
}
}
else { // a key
const char *pEnd = psz;
while ( IsValid(*pEnd) )
pEnd++;
len = pEnd - psz;
char *szKey = new char[len + 1];
strncpy(szKey, psz, len + 1);
szKey[len] = '\0';
while ( isspace(*pEnd) ) pEnd++; // eat whitespace
if ( *pEnd++ != '=' ) {
LogError(_("file '%s': expected '=' at line %d."),
m_szFullFileName, m_uiLine);
return FALSE;
}
while ( isspace(*pEnd) ) pEnd++; // eat whitespace
ConfigEntry *pEntry = m_pCurGroup->FindEntry(szKey);
if ( pEntry == NULL ) {
pEntry = m_pCurGroup->AddEntry(szKey);
}
/* gives erroneous warnings when the same key appears in
both global and local config files. Would be nice to
have something happen when the same key appears twice
(or more) in a section though.
else {
LogWarning(_("key '%s' has more than one value."), szKey);
}
*/
if ( m_szComment != NULL ) {
pEntry->SetComment(m_szComment);
m_szComment = NULL;
}
char *szUnfilteredValue = filterIn(pEnd);
pEntry->SetValue(szUnfilteredValue, m_bParsingLocal, TRUE);
delete [] szUnfilteredValue;
delete [] szKey;
}
return TRUE;
}
// ----------------------------------------------------------------------------
// read data (which are fully loaded in memory)
// ----------------------------------------------------------------------------
const char *FileConfig::readEntry(const char *szKey,
const char *szDefault) const
{
ConfigEntry *pEntry = m_pCurGroup->FindEntry(szKey);
if ( pEntry != NULL ) {
if ( m_bExpandVariables )
return pEntry->ExpandedValue();
else
return pEntry->Value();
}
if(m_bRecordDefaults)
((FileConfig *)this)->writeEntry(szKey,szDefault);
// entry wasn't found
return szDefault;
}
// ----------------------------------------------------------------------------
// functions which update data in memory
// ----------------------------------------------------------------------------
// create new group if this one doesn't exist yet
void FileConfig::changeCurrentPath(const char *szPath)
{
// normalize path
BaseConfig::changeCurrentPath(szPath);
szPath = getCurrentPath();
m_pCurGroup = m_pRootGroup;
// special case of empty path
if ( *szPath == '\0' )
return;
char *szGroupName = NULL;
size_t len = 0;
const char *pBegin = szPath;
const char *pEnd = pBegin + 1;
do{//while ( *pEnd != '\0' ) {
while ( *pEnd != '\0' && *pEnd != APPCONF_PATH_SEPARATOR )
pEnd++;
if ( (unsigned)(pEnd - pBegin) + 1 > len ) {
// not enough space, realloc buffer
len = pEnd - pBegin + 1;
// also delete old one
if ( szGroupName != NULL )
delete [] szGroupName;
szGroupName = new char[len];
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -