openmohaa/code/qcommon/configurator.cpp
2024-09-20 23:08:53 +02:00

988 lines
20 KiB
C++

/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// configurator.cpp: Config class for INI files.
#include "configurator.h"
CLASS_DECLARATION( Class, Configurator, NULL )
{
{ NULL, NULL }
};
Configurator::Configurator( const char *filename )
{
Parse( filename );
}
Configurator::Configurator()
{
m_bNoWrite = false;
m_bNeedWrite = false;
}
Configurator::~Configurator()
{
Close();
}
size_t Configurator::GetLine( char *dest, const char *data, size_t size )
{
const char *p = data;
size_t i = 0;
while( *p == '\n' )
p++;
if( p >= data + size )
return 0;
while( *p && *p != '\n' && i < size )
dest[ i++ ] = *p++;
if( i < size )
dest[ i++ ] = '\n';
dest[ i ] = 0;
return p - data;
}
void Configurator::RemoveSection( configSection_t *section )
{
configKey_t *key;
int i;
for( i = 0; i < section->key.NumObjects(); i++ )
{
key = section->key[ i ];
RemoveKey( section, key );
}
m_reverseSections.RemoveObject( section );
m_sections.remove( section->name );
}
void Configurator::RemoveKey( configSection_t *section, configKey_t *key )
{
section->key.RemoveObject( key );
delete key;
}
configSection_t *Configurator::CreateSection( const char *section )
{
configSection_t *s = &m_sections.addKeyValue( section );
if( s->key.NumObjects() )
{
RemoveSection( s );
s = &m_sections.addKeyValue( section );
}
else
{
m_reverseSections.AddObject( s );
}
s->name = section;
return s;
}
int Configurator::GetKeyArray( char *key )
{
int count;
size_t len;
int arrayindex = -1;
char *p1, *p2;
len = strlen( key ) + 1;
p1 = new char[ len ];
p2 = new char[ len ];
count = sscanf( key, "%[^[] [%[^]]", p1, p2 );
if( count == 2 )
{
// Check for an array [num]
strcpy( key, p1 );
arrayindex = atoi( p2 );
if( arrayindex < 0 || arrayindex > MAX_CONFIGURATOR_ARRAY_SIZE )
{
arrayindex = -2;
}
}
delete[] p1;
delete[] p2;
return arrayindex;
}
int Configurator::GetKeyArray( str& key )
{
int count;
size_t len;
int arrayindex = -1;
str arrayval;
char *p1, *p2;
len = key.length() + 1;
p1 = new char[ len ];
p2 = new char[ len ];
count = sscanf( key, "%[^[] [%[^]]", p1, p2 );
if( count == 2 )
{
// Check for an array [num]
arrayval = p2;
arrayindex = atoi( arrayval.c_str() );
key = p1;
if( arrayindex < 0 || arrayindex > MAX_CONFIGURATOR_ARRAY_SIZE )
{
arrayindex = -2;
}
}
delete[] p1;
delete[] p2;
return arrayindex;
}
configKey_t *Configurator::CreateKey( configSection_t *section, const char *key, unsigned int *index )
{
configKey_t *k;
str keyname;
int arrayindex;
if( index ) *index = 0;
keyname = key;
arrayindex = GetKeyArray( keyname );
if( arrayindex == -2 )
{
return NULL;
}
else if( arrayindex >= 0 )
{
if( index ) *index = arrayindex;
}
k = FindKey( section, keyname );
if( k )
{
if( arrayindex == -1 || !k->value.NumObjects() )
{
RemoveKey( section, k );
k = NULL;
}
}
if( !k )
{
k = new configKey_t;
section->key.AddObject( k );
k->name = keyname;
k->m_bArray = arrayindex != -1;
k->m_bNeedWrite = false;
}
if( arrayindex >= k->value.MaxObjects() )
{
k->value.Resize( ( arrayindex + 1 ) * 2);
}
return k;
}
configSection_t *Configurator::FindSection( const char *section )
{
configSection_t *s = m_sections.findKeyValue( section );
return s;
}
configKey_t *Configurator::FindKey( configSection_t *section, const char *key )
{
configKey_t *k;
int i;
for( i = 0; i < section->key.NumObjects(); i++ )
{
k = section->key[ i ];
if( !k->name.icmp( key ) )
{
return k;
}
}
return NULL;
}
int Configurator::CutLine( char *data )
{
char *p = data;
while( *p == '\n' )
*p++ = 0;
while( *p && *p != '\n' )
*p++ = 0;
return p - data;
}
bool Configurator::SetupLine( char *line, int& lineno, size_t& len, size_t& last )
{
lineno++;
len = ( int )strlen( line ) - 1;
if( len <= 0 )
{
return false;
}
if( line[ len ] != '\n' )
{
last = 0;
glbs.DPrintf( "Configurator::SetupLine: input line too long in %s (line %d)\n", m_filename.c_str(), lineno );
return false;
}
while( ( len >= 0 ) && ( ( line[ len ] == '\n' ) || ( isspace( line[ len ] ) ) ) )
{
line[ len ] = 0;
len--;
}
if( line[ len ] == '\\' )
{
last = len;
return false;
}
else
{
last = 0;
}
return true;
}
void Configurator::WriteData( char **data, size_t *size )
{
configSection_t *section;
configKey_t *key;
configValue_t *val;
str value;
char *oldData = *data;
char *currentData = *data;
size_t currentSize = *size;
size_t len, tlen = 0;
int i, j, k;
size_t offset, offset2;
bool bFound;
for( i = 1; i <= m_reverseSections.NumObjects(); i++ )
{
section = m_reverseSections.ObjectAt( i );
if( !FindData( LINE_SECTION, section->name, NULL, &offset, currentData, currentSize ) )
{
oldData = currentData;
len = ( offset ? 2 : 0 ) + 1 + section->name.length() + 1; // \n\n + [ + section + ]
currentSize += len;
currentData = new char[ currentSize + 1 ];
strncpy( currentData, oldData, offset );
Com_sprintf( currentData + offset, currentSize + 1 - offset, offset ? "\n\n[%s]" : "[%s]", section->name.c_str() );
Q_strncpyz( currentData + offset + len, oldData + offset, currentSize + 1 - offset - len );
if( currentData != oldData ) delete[] oldData;
}
for( j = 0; j < section->key.NumObjects(); j++ )
{
key = section->key[ j ];
if( !key->m_bNeedWrite )
continue;
if( key->m_bArray )
{
for( k = 0; k < key->value.NumObjects(); k++ )
{
val = &key->value[ k ];
if( !val->m_bNeedWrite )
continue;
value = val->value;
bFound = FindData( LINE_VALUE, section->name, key->name + "[" + k + "]", &offset, currentData, currentSize );
{
int digits, l;
oldData = currentData;
l = k;
digits = 0;
if( !l )
l = 1;
while( l )
{
l /= 10;
digits++;
}
if( bFound )
{
len = CutLine( currentData + offset );
currentSize -= len;
offset2 = offset + len;
}
else
{
offset2 = offset;
}
len = 1 + key->name.length() + 1 + digits + 1 + 1 + value.length(); // \n + name + [ + digits + ] + = + value
currentSize += len;
currentData = new char[ currentSize + 1 ];
strncpy( currentData, oldData, offset );
Com_sprintf( currentData + offset, currentSize + 1 - offset, "\n%s[%d]=%s", key->name.c_str(), k, value.c_str() );
Q_strncpyz( currentData + offset + len, oldData + offset2, currentSize + 1 - offset - len );
if( currentData != oldData ) delete[] oldData;
}
}
}
else
{
val = &key->value[ 0 ];
if( !val->m_bNeedWrite )
continue;
value = val->value;
bFound = FindData( LINE_VALUE, section->name, key->name, &offset, currentData, currentSize );
{
oldData = currentData;
if( bFound )
{
len = CutLine( currentData + offset );
currentSize -= len;
offset2 = offset + len;
}
else
{
offset2 = offset;
}
len = 1 + key->name.length() + 1 + value.length(); // \n + name + = + value
currentSize += len;
currentData = new char[ currentSize + 1 ];
strncpy( currentData, oldData, offset );
Com_sprintf( currentData + offset, currentSize + 1 - offset "\n%s=%s", key->name.c_str(), value.c_str() );
Q_strncpyz( currentData + offset + len, oldData + offset2, currentSize + 1 - offset - len );
if( currentData != oldData ) delete[] oldData;
}
}
}
}
if( currentData[ currentSize - 1 ] != '\n' )
{
currentData[ currentSize ] = '\n';
currentSize++;
}
*data = currentData;
*size = currentSize;
}
void Configurator::WriteData2( char **data, size_t *size )
{
configSection_t *section;
configKey_t *key;
configValue_t *val;
str value;
char *oldData = *data;
char *currentData = *data;
size_t currentSize = *size;
char *p = currentData;
size_t tlen = 0;
int i, j, k;
for( i = 1; i <= m_reverseSections.NumObjects(); i++ )
{
section = m_reverseSections.ObjectAt( i );
if( i == 1 )
{
tlen += 1 + section->name.length() + 1 + 1; // [ + name + ] + \n
}
else
{
tlen += 1 + 1 + section->name.length() + 1 + 1; // \n + [ name + ] + \n
}
if( tlen > currentSize )
{
oldData = currentData;
currentSize = tlen + 1024;
currentData = new char[ currentSize ];
p = currentData + ( p - oldData );
Q_strncpyz( currentData, oldData, currentSize );
delete[] oldData;
}
if( i == 1 )
{
p += Com_sprintf( p, currentSize - ( p - currentData ), "[%s]\n", section->name.c_str() );
}
else
{
p += Com_sprintf( p, currentSize - ( p - currentData ), "\n[%s]\n", section->name.c_str() );
}
for( j = 0; j < section->key.NumObjects(); j++ )
{
key = section->key[ j ];
if( !key->m_bNeedWrite )
continue;
if( key->m_bArray )
{
for( k = 0; k < key->value.NumObjects(); k++ )
{
val = &key->value[ k ];
if( !val->m_bNeedWrite )
continue;
value = val->value;
{
int digits, l;
l = k;
digits = 0;
if( !l )
l = 1;
while( l )
{
l /= 10;
digits++;
}
tlen += key->name.length() + 1 + digits + 1 + 1 + value.length() + 1; // name + [ + digits + ] + = + value + \n
if( tlen > currentSize )
{
oldData = currentData;
currentSize = tlen + 1024;
currentData = new char[ currentSize ];
p = currentData + ( p - oldData );
strcpy( currentData, oldData );
delete[] oldData;
}
p += Com_sprintf( p, currentSize - ( p - currentData ), "%s[%d]=%s\n", key->name.c_str(), k, value.c_str() );
}
}
}
else
{
val = &key->value[ 0 ];
if( !val->m_bNeedWrite )
continue;
value = val->value;
tlen += key->name.length() + 1 + value.length() + 1; // name + = + value + \n
if( tlen > currentSize )
{
oldData = currentData;
currentSize = tlen + 1024;
currentData = new char[ currentSize ];
p = currentData + ( p - oldData );
strcpy( currentData, oldData );
delete[] oldData;
}
p += Com_sprintf( p, currentSize - ( p - currentData ), "%s=%s\n", key->name.c_str(), value.c_str() );
}
}
}
*data = currentData;
*size = tlen;
}
bool Configurator::FindData( int type, const char *s, const char *k, size_t *offset, const char *data, size_t size )
{
static char line[ MAX_STRING_TOKENS + 1 ];
static char section[ MAX_STRING_TOKENS + 1 ];
static char key[ MAX_STRING_TOKENS + 1 ];
static char val[ MAX_STRING_TOKENS + 1 ];
size_t last = 0;
size_t len;
size_t olen = 0;
size_t tlen = 0;
int lineno = 0;
int arrayindex;
bool bFoundSection = false;
*offset = 0;
while( ( olen = GetLine( line + last, data + tlen, MAX_STRING_TOKENS - last ) ) != 0 )
{
tlen += olen;
if( !SetupLine( line + last, lineno, len, last ) )
{
continue;
}
switch( ParseLine( line, section, key, val ) )
{
case LINE_SECTION:
if( bFoundSection )
{
// Return the last line of the section to append
*offset = tlen - olen;
return false;
}
if( !Q_stricmp( s, section ) )
{
bFoundSection = true;
if( type == LINE_SECTION )
{
*offset = tlen;
return true;
}
}
break;
case LINE_VALUE:
arrayindex = GetKeyArray( key );
if( arrayindex >= 0 )
{
Com_sprintf( key, sizeof( key ), "%s[%d]", key, arrayindex );
}
if( type == LINE_VALUE && bFoundSection && !Q_stricmp( k, key ) )
{
*offset = tlen - olen;
return true;
}
break;
default:
break;
}
}
*offset = tlen;
return false;
}
void Configurator::ParseData( const char *data, size_t size )
{
static char line[ MAX_STRING_TOKENS + 1 ];
static char section[ MAX_STRING_TOKENS + 1 ];
static char key[ MAX_STRING_TOKENS + 1 ];
static char val[ MAX_STRING_TOKENS + 1 ];
size_t last = 0;
size_t len;
size_t tlen = 0;
int lineno = 0;
unsigned int index = 0;
configSection_t *current;
configKey_t *k;
configValue_t v;
while( ( len = GetLine( line + last, data + tlen, MAX_STRING_TOKENS - last ) ) != 0 )
{
tlen += len;
if( !SetupLine( line + last, lineno, len, last ) )
{
continue;
}
switch( ParseLine( line, section, key, val ) )
{
case LINE_EMPTY:
case LINE_COMMENT:
break;
case LINE_SECTION:
current = CreateSection( section );
break;
case LINE_VALUE:
k = CreateKey( current, key, &index );
if( !k )
{
glbs.DPrintf( "Configurator::ParseData: Could not create key:\n" );
goto __error;
}
v.m_bNeedWrite = false;
v.value = val;
k->value.AddObjectAt( index + 1, v );
break;
case LINE_ERROR:
glbs.DPrintf( "Configurator::ParseData: syntax error:\n" );
__error:
glbs.DPrintf( "(%s, %d):", m_filename.c_str(), lineno );
glbs.DPrintf( "%s\n", line );
break;
default:
break;
}
}
}
int Configurator::ParseLine( char *line, char *section, char *key, char *value )
{
size_t len;
int result;
len = strlen( line );
if( len < 1 )
{
result = LINE_EMPTY;
}
else if( line[ 0 ] == ';' )
{
result = LINE_COMMENT;
}
else if( line[ 0 ] == '[' && line[ len - 1 ] == ']' )
{
sscanf( line, "[%[^]]", section );
strcpy( section, strstrip( section ) );
strlwc( section );
result = LINE_SECTION;
}
else if( ( sscanf( line, "%[^=] = \"%[^\"]\"", key, value ) ) == 2
|| ( sscanf( line, "%[^=] = %[^;]", key, value ) ) == 2 )
{
strcpy( key, strstrip( key ) );
strlwc( key );
strcpy( value, strstrip( value ) );
// sscanf cannot handle empty "" values
if( !strcmp( value, "\"\"" ) )
{
value = "";
}
result = LINE_VALUE;
}
else if( ( sscanf( line, "%[^=] = %[;]", key, value ) ) == 2
|| ( sscanf( line, "%[^=] %[=]", key, value ) ) == 2 )
{
strcpy( key, strstrip( key ) );
strlwc( key );
value[ 0 ] = 0;
result = LINE_VALUE;
}
else
{
result = LINE_ERROR;
}
return result;
}
str Configurator::GetValue( const char *section, const char *key, str defaultValue, int index )
{
configKey_t *k = GetKey( section, key, index );
configValue_t val;
if( !k )
{
return defaultValue;
}
if( index < 0 ) index = 0;
if( k->m_bNeedWrite )
{
val.m_bNeedWrite = true;
val.value = defaultValue;
k->value.AddObjectAt( index + 1, val );
}
return k->value[ index ].value;
}
configKey_t *Configurator::GetKey( const char *section, const char *key, int index )
{
configSection_t *s;
configKey_t *k;
bool bArray = index >= 0;
if( index < 0 ) index = 0;
s = FindSection( section );
if( !s )
{
if( m_bNoWrite )
{
return NULL;
}
s = CreateSection( section );
s->m_bNeedWrite = true;
m_bNeedWrite = true;
}
k = FindKey( s, key );
if( !k )
{
if( m_bNoWrite )
{
return NULL;
}
str keyname;
if( bArray )
{
keyname = str( key ) + "[" + str( index ) + "]";
}
else
{
keyname = key;
}
k = CreateKey( s, keyname, NULL );
k->m_bNeedWrite = true;
m_bNeedWrite = true;
}
else if( index >= k->value.NumObjects() )
{
k->m_bNeedWrite = true;
m_bNeedWrite = true;
}
return k;
}
//======================================
// Configurator::GetString
//======================================
str Configurator::GetString( const char *section, const char *key, str defaultValue, int index )
{
str value = GetValue( section, key, defaultValue, index );
return value;
}
//======================================
// Configurator::GetInteger
//======================================
int Configurator::GetInteger( const char *section, const char *key, int defaultValue, int index )
{
str value = GetValue( section, key, defaultValue, index );
return atoi( value.c_str() );
}
//======================================
// Configurator::GetFloat
//======================================
float Configurator::GetFloat( const char *section, const char *key, float defaultValue, int index )
{
str value = GetValue( section, key, defaultValue, index );
return atof( value.c_str() );
}
//======================================
// Configurator::SetString
//======================================
void Configurator::SetString( const char *section, const char *key, str value, int index )
{
configKey_t *k = GetKey( section, key, index );
configValue_t v;
if( !k )
return;
if( index < 0 ) index = 0;
if( index >= k->value.NumObjects() || k->value[ index ].value != value )
{
m_bNeedWrite = true;
v.m_bNeedWrite = true;
v.value = value;
k->m_bNeedWrite = true;
k->value.AddObjectAt( index + 1, v );
}
}
//======================================
// Configurator::SetInteger
//======================================
void Configurator::SetInteger( const char *section, const char *key, int value, int index )
{
return SetString( section, key, value, index );
}
//======================================
// Configurator::SetFloat
//======================================
void Configurator::SetFloat( const char *section, const char *key, float value, int index )
{
return SetString( section, key, value, index );
}
//======================================
// Configurator::SetWrite
//======================================
void Configurator::SetWrite( bool bWrite )
{
m_bNoWrite = !bWrite;
}
//======================================
// Configurator::Parse
//======================================
void Configurator::Parse( const char *filename )
{
FILE *file;
size_t size;
char *buffer;
m_bNoWrite = false;
m_bNeedWrite = false;
this->m_filename = filename;
file = fopen( filename, "r" );
if( !file )
{
return;
}
fseek( file, 0, SEEK_END );
size = ftell( file );
rewind( file );
buffer = new char[ size + 1 ];
size = fread( buffer, 1, size, file );
buffer[ size ] = 0;
fclose( file );
ParseData( buffer, size );
}
//======================================
// Configurator::Close
//======================================
void Configurator::Close( void )
{
FILE *file;
char *buffer;
int i;
size_t size;
if( !m_filename.length() || !m_bNeedWrite )
{
goto _cleanup;
}
file = fopen( m_filename.c_str(), "r+" );
if( !file )
{
file = fopen( m_filename.c_str(), "w+" );
if( !file )
{
return;
}
file = freopen( m_filename.c_str(), "r+", file );
if( !file )
{
return;
}
}
fseek( file, 0, SEEK_END );
size = ftell( file );
rewind( file );
buffer = new char[ size + 1 ];
size = fread( buffer, 1, size, file );
buffer[ size ] = 0;
glbs.DPrintf( "Configurator::Close: Writing data to %s...\n", m_filename.c_str() );
WriteData( &buffer, &size );
glbs.DPrintf( "Configurator::Close: Done.\n" );
file = freopen( m_filename.c_str(), "w+", file );
fwrite( buffer, size, 1, file );
fclose( file );
m_filename = "";
delete[] buffer;
_cleanup:
for( i = m_reverseSections.NumObjects(); i > 0; i-- )
{
RemoveSection( m_reverseSections.ObjectAt( i ) );
}
}
void test_config( void )
{
Configurator config( "test.ini" );
str value;
value = config.GetString( "section1", "testvar1", "100" );
glbs.DPrintf( "value = %s\n", value.c_str() );
value = config.GetString( "section1", "testarray1", "100", 0 );
glbs.DPrintf( "array1 = %s\n", value.c_str() );
value = config.GetString( "section1", "testarray1", "100", 1 );
glbs.DPrintf( "array2 = %s\n", value.c_str() );
value = config.GetString( "section1", "testarray1", "100", 2 );
glbs.DPrintf( "array3 = %s\n", value.c_str() );
value = config.GetString( "section1", "testforcewrite", "500" );
glbs.DPrintf( "testforcewrite = %s\n", value.c_str() );
value = config.GetString( "section2", "testforcewrite2", "500" );
glbs.DPrintf( "testforcewrite2 = %s\n", value.c_str() );
//for( int i = 0; i < 2048; i++ )
//{
// value = config.GetString( "section1", "testforcewrite5", "2048", i );
//}
config.SetString( "section1", "testforcewrite5", "newstring", 2048 );
config.Close();
}