Main Page   Namespace List   Class Hierarchy   Compound List   File List   Compound Members   File Members  

appconf.cpp

Go to the documentation of this file.
00001 
00002 #include "Core/precomp.h"
00003 
00004 /*****************************************************************************\
00005  * Project:   CppLib: C++ library for Windows/UNIX platfroms                 *
00006  * File:      config.cpp - implementation of Config class                    *
00007  *---------------------------------------------------------------------------*
00008  * Language:  C++                                                            *
00009  * Platfrom:  any (tested under Windows NT)                                  *
00010  *---------------------------------------------------------------------------*
00011  * (c) Karsten Ballüder & Vadim Zeitlin                                      *
00012  *     Ballueder@usa.net  Vadim.zeitlin@dptmaths.ens-cachan.fr               *
00013  *---------------------------------------------------------------------------*
00014  * Classes:                                                                  *
00015  *  Config  - manages configuration files or registry database               *
00016  *---------------------------------------------------------------------------*
00017  * History:                                                                  *
00018  *  25.10.97  adapted from wxConfig by Karsten Ballüder                      *
00019  *  09.11.97  corrected bug in RegistryConfig::enumSubgroups                 *
00020  *     --- for further changes see appconf.h or the CVS log ---              *    
00021 \*****************************************************************************/
00022 
00023 /**********************************************************************\
00024  *                                                                    *
00025  * This library is free software; you can redistribute it and/or      *
00026  * modify it under the terms of the GNU Library General Public        *
00027  * License as published by the Free Software Foundation; either       *
00028  * version 2 of the License, or (at your option) any later version.   *
00029  *                                                                    *
00030  * This library is distributed in the hope that it will be useful,    *
00031  * but WITHOUT ANY WARRANTY; without even the implied warranty of     *
00032  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
00033  * Library General Public License for more details.                   *
00034  *                                                                    *
00035  * You should have received a copy of the GNU Library General Public  *
00036  * License along with this library; if not, write to the Free         *
00037  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
00038  *                                                                    *
00039 \**********************************************************************/
00040 
00041 //static const char
00042 //*cvs_id = "$Id: appconf.cpp,v 1.2 2000/05/03 18:29:00 mbn Exp $";
00043 
00044 // ============================================================================
00045 // headers, constants, private declarations
00046 // ============================================================================
00047 
00048 // ----------------------------------------------------------------------------
00049 // headers
00050 // ----------------------------------------------------------------------------
00051 
00052 // standard headers
00053 #ifdef    __WIN32__
00054 #       ifdef  _MSC_VER
00055     // nonstandard extension used : nameless struct/union
00056 #               pragma warning(disable: 4201)  
00057 #       endif  // VC++
00058 
00059 #       include  <windows.h>
00060 #endif    // WIN32
00061 
00062 #ifdef    __unix__
00063 #       include <sys/param.h>
00064 #       include <sys/stat.h>
00065 #       include <unistd.h>
00066 #       define MAX_PATH MAXPATHLEN
00067 #endif
00068 
00069 #include  <fcntl.h>
00070 #include  <sys/types.h>
00071 #include  <iostream>
00072 #include  <fstream.h>
00073 #include  <string.h>
00074 #include  <cctype>
00075 #include  <stdio.h>
00076 #include  <stdlib.h>
00077 #include  <stdarg.h>
00078 #include  <assert.h>
00079 
00080 // our headers
00081 #include  "appconf.h"
00082 
00083 // ----------------------------------------------------------------------------
00084 // some debug/error reporting functions
00085 // ----------------------------------------------------------------------------
00086 
00087 #if     APPCONF_USE_GETTEXT
00088 #       include <libintl.h>
00089 #       define  _(x)    dgettext(APPCONF_DOMAIN,x)
00090 #else
00091 #       define  _(x)    (x)
00092 #endif
00093 
00094 using namespace std;
00095 
00096 #ifndef   __WXWIN__
00097 
00098 // in general, these messages could be treated all differently
00099 // define a standard log function, to be called like printf()
00100 #ifndef LogInfo
00101 #       define   LogInfo     LogError
00102 #endif
00103 
00104 // define a standard error log function, to be called like printf()
00105 #ifndef LogWarning
00106 #       define   LogWarning  LogError
00107 #endif
00108 
00109 // logs an error message (with a name like that it's really strange)
00110 // message length is limited to 1Kb
00111 void LogError(const char *pszFormat, ...)
00112 {
00113   char szBuf[1025];
00114 
00115   va_list argptr;
00116   va_start(argptr, pszFormat);
00117   vsprintf(szBuf, pszFormat, argptr);
00118   va_end(argptr);
00119 
00120   strcat(szBuf, "\n");
00121   fputs(szBuf, stderr);
00122 }
00123 
00124 #endif
00125 
00126 #ifdef  __WIN32__
00127 
00128 const char *SysError()
00129 {
00130   static char s_szBuf[1024];
00131 
00132   // get error message from system
00133   LPVOID lpMsgBuf;
00134   FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
00135                 NULL, GetLastError(), 
00136                 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
00137                 (LPTSTR)&lpMsgBuf,
00138                 0, NULL);
00139 
00140   // copy it to our buffer and free memory
00141   strncpy(s_szBuf, (const char *)lpMsgBuf, sizeof(s_szBuf)/sizeof(char));
00142   LocalFree(lpMsgBuf);
00143 
00144   // returned string is capitalized and ended with '\n' - no good
00145   s_szBuf[0] = (char)tolower(s_szBuf[0]);
00146   size_t len = strlen(s_szBuf);
00147   if ( (len > 0) && (s_szBuf[len - 1] == '\n') )
00148     s_szBuf[len - 1] = '\0';
00149 
00150   return s_szBuf;
00151 }
00152 
00153 #endif
00154 
00155 // ----------------------------------------------------------------------------
00156 // global functions
00157 // ----------------------------------------------------------------------------
00158 inline size_t Strlen(const char *pc) { return pc == NULL ? 0 : strlen(pc); }
00159 inline Bool   IsValid(char c) { return isalnum(c) || strchr("_/-!.*%", c); }
00160 inline Bool   IsCSym (char c) { return isalnum(c) || ( c == '_');          }
00161 inline size_t Min(size_t n1, size_t n2) { return n1 < n2 ? n1 : n2; }
00162 
00163 #define   SIZE(array)       (sizeof(array)/sizeof(array[0]))
00164 
00165 #if APPCONF_CASE_SENSITIVE
00166 # define StrCmp(s1,s2)  strcmp((s1),(s2))
00167 #else
00168 # ifdef   __unix__
00169     extern "C" int strcasecmp(const char *s1, const char *s2); // it's not ansi
00170 #   define  StrCmp(s1,s2)  strcasecmp((s1),(s2))
00171 # else
00172 #   ifdef _MSC_VER
00173 #     define  StrCmp(s1,s2)  _stricmp((s1),(s2))
00174 #   else
00175 #     error "Please define 'stricmp' function for your compiler."
00176 #   endif // compilers
00177 # endif   // strcasecmp/strcimp
00178 #endif
00179 
00180 // perform environment variable substitution
00181 char *ExpandEnvVars(const char *psz)
00182 {
00183   char *szNewValue = new char[strlen(psz)+1];
00184   strcpy(szNewValue, psz);
00185   return szNewValue;
00186   
00187   // produces internal comp error on rh 5.2 -- mbn
00188 /*
00189   // don't change the values the enum elements: they must be equal
00190   // to the matching [closing] delimiter.
00191   enum Bracket
00192   { 
00193     Bracket_None, 
00194     Bracket_Normal  = ')', 
00195     Bracket_Curly   = '}' 
00196   };
00197           
00198   // max size for an environment variable name is fixed
00199   char szVarName[256];
00200 
00201   // first calculate the length of the resulting string
00202   size_t nNewLen = 0;
00203   const char *pcIn;
00204   for ( pcIn = psz; *pcIn != '\0'; pcIn++ ) {
00205     switch ( *pcIn ) {
00206       case '$':
00207         {
00208           Bracket bracket;
00209           switch ( *++pcIn ) {
00210             case '(': 
00211               bracket = Bracket_Normal; 
00212               pcIn++;                   // skip the bracket
00213               break;
00214 
00215             case '{':
00216               bracket = Bracket_Curly;
00217               pcIn++;                   // skip the bracket
00218               break;
00219 
00220             default:
00221               bracket = Bracket_None;
00222           }
00223           const char *pcStart = pcIn;
00224 
00225           while ( IsCSym(*pcIn) ) pcIn++;
00226 
00227           size_t nCopy = Min(pcIn - pcStart, SIZE(szVarName));
00228           strncpy(szVarName, pcStart, nCopy);
00229           szVarName[nCopy] = '\0';
00230 
00231           if ( bracket != Bracket_None ) {
00232             if ( *pcIn != (char)bracket ) {
00233               // # what to do? we decide to give warning and ignore 
00234               //   the opening bracket
00235               LogWarning(_("'%c' expected in '%s' after '${%s'"), 
00236                          (char)bracket, psz, szVarName);
00237               pcIn--;
00238             }
00239           }
00240           else {
00241             // everything is ok but we took one extra character
00242             pcIn--;
00243           }
00244 
00245           // Strlen() acceps NULL as well
00246           nNewLen += Strlen(getenv(szVarName));
00247         }
00248         break;
00249 
00250       case '\\':
00251         pcIn++;
00252         // fall through
00253 
00254       default:
00255         nNewLen++;
00256     }
00257   }
00258 
00259   // # we always realloc buffer (could reuse the old one if nNewLen < nOldLen)
00260   char *szNewValue = new char[nNewLen + 1];
00261   char *pcOut = szNewValue;
00262 
00263   // now copy it to the new location replacing the variables with their values
00264   for ( pcIn = psz; *pcIn != '\0'; pcIn++ ) {
00265     switch ( *pcIn ) {
00266       case '$':
00267         {
00268           Bracket bracket;
00269           switch ( *++pcIn ) {
00270             case '(': 
00271               bracket = Bracket_Normal; 
00272               pcIn++;                   // skip the bracket
00273               break;
00274 
00275             case '{':
00276               bracket = Bracket_Curly;
00277               pcIn++;                   // skip the bracket
00278               break;
00279 
00280             default:
00281               bracket = Bracket_None;
00282           }
00283           const char *pcStart = pcIn;
00284 
00285           while ( IsCSym(*pcIn) ) pcIn++;
00286 
00287           size_t nCopy = Min(pcIn - pcStart, SIZE(szVarName));
00288           strncpy(szVarName, pcStart, nCopy);
00289           szVarName[nCopy] = '\0';
00290 
00291           if ( bracket != Bracket_None ) {
00292             if ( *pcIn != (char)bracket ) {
00293               // warning message already given, just ignore opening bracket
00294               pcIn--;
00295             }
00296           }
00297           else {
00298             // everything is ok but we took one extra character
00299             pcIn--;
00300           }
00301 
00302           const char *pszValue = getenv(szVarName);
00303           if ( pszValue != NULL ) {
00304             strcpy(pcOut, pszValue);
00305             pcOut += strlen(pszValue);
00306           }
00307         }
00308         break;
00309 
00310       case '\\':
00311         pcIn++;
00312         // fall through
00313 
00314       default:
00315         *pcOut++ = *pcIn;
00316     }
00317   }
00318 
00319   *pcOut = '\0';
00320 
00321   return szNewValue;
00322 */
00323 }
00324 
00325 // ============================================================================
00326 // implementation of the class BaseConfig
00327 // ============================================================================
00328 
00329 // ----------------------------------------------------------------------------
00330 // ctor and dtor
00331 // ----------------------------------------------------------------------------
00332 
00333 BaseConfig::BaseConfig()
00334 {
00335    m_szCurrentPath = NULL;
00336    m_bRecordDefaults = FALSE;
00337 }
00338 
00339 BaseConfig::~BaseConfig()
00340 {
00341   if ( m_szCurrentPath != NULL )
00342     delete [] m_szCurrentPath;
00343 }
00344 
00345 void
00346 BaseConfig::recordDefaults(Bool enable)
00347 {
00348    m_bRecordDefaults = enable;
00349 }
00350 
00351 // ----------------------------------------------------------------------------
00352 // handle long int and double values
00353 // ----------------------------------------------------------------------------
00354 
00355 Bool
00356 BaseConfig::writeEntry(const char *szKey, long int Value)
00357 {
00358    char buffer[APPCONF_STRBUFLEN]; // ugly
00359    sprintf(buffer, "%ld", Value);
00360    return writeEntry(szKey,buffer);
00361 }
00362 
00363 Bool
00364 BaseConfig::writeEntry(const char *szKey, double Value)
00365 {
00366    char buffer[APPCONF_STRBUFLEN]; // ugly
00367    sprintf(buffer,"%g", Value);
00368    return writeEntry(szKey,buffer);
00369 }       
00370 
00371 long int
00372 BaseConfig::readEntry(const char *szKey, long int Default) const
00373 {
00374    const char *cptr = readEntry(szKey,(const char *)NULL);
00375    if(cptr)
00376       return atol(cptr);
00377    else
00378    {
00379       if(m_bRecordDefaults)
00380          ((BaseConfig *)this)->writeEntry(szKey,Default);
00381       return Default;
00382    }
00383 }
00384 
00385 double
00386 BaseConfig::readEntry(const char *szKey, double Default) const
00387 {
00388    const char *cptr = readEntry(szKey,(const char *)NULL);
00389    if(cptr)
00390       return atof(cptr);
00391    else
00392    {
00393       if(m_bRecordDefaults)
00394          ((BaseConfig *)this)->writeEntry(szKey,Default);
00395       return Default;
00396    }
00397 }
00398 
00399 // ----------------------------------------------------------------------------
00400 // set/get current path
00401 // ----------------------------------------------------------------------------
00402 
00403 // this function resolves all ".." (but not '/'!) in the path
00404 // returns pointer to dynamically allocated buffer, free with "delete []"
00405 // ## code here is inefficient and difficult to understand, to rewrite
00406 char *BaseConfig::normalizePath(const char *szStartPath, const char *szPath)
00407 {
00408   char    *szNormPath;
00409 
00410   // array grows in chunks of this size
00411 #define   COMPONENTS_INITIAL    (10)
00412 
00413   char   **aszPathComponents;   // component is something between 2 '/'
00414   size_t   nComponents = 0,
00415            nMaxComponents;
00416 
00417   aszPathComponents = new char *[nMaxComponents = COMPONENTS_INITIAL];
00418 
00419   const char *pcStart;   // start of last component
00420   const char *pcIn;
00421 
00422   // concatenate the two adding APPCONF_PATH_SEPARATOR to the end if not there
00423   size_t len = Strlen(szStartPath);
00424   size_t nOldLen = len + Strlen(szPath) + 1;
00425   szNormPath = new char[nOldLen + 1];
00426   strcpy(szNormPath, szStartPath);
00427   szNormPath[len++] = APPCONF_PATH_SEPARATOR;
00428   szNormPath[len] = '\0';
00429   strcat(szNormPath, szPath);
00430 
00431   // break combined path in components
00432   Bool bEnd = FALSE;
00433   for ( pcStart = pcIn = szNormPath; !bEnd; pcIn++ ) {
00434     if ( *pcIn == APPCONF_PATH_SEPARATOR || *pcIn == '\0' ) {
00435       if ( *pcIn == '\0' )
00436         bEnd = TRUE;
00437 
00438       // another component - is it "." or ".."?
00439       if ( *pcStart == '.' ) {
00440         if ( pcIn == pcStart + 1 ) {
00441           // "./" - ignore
00442           pcStart = pcIn + 1;
00443           continue;
00444         }
00445         else if ( (pcIn == pcStart + 2) && (*(pcStart + 1) == '.') ) {
00446           // "../" found - delete last component
00447           if ( nComponents > 0 ) {
00448             delete [] aszPathComponents[--nComponents];
00449           }
00450           else {
00451             LogWarning(_("extra '..' in the path '%s'."), szPath);
00452           }
00453 
00454           pcStart = pcIn + 1;
00455           continue;
00456         }
00457       }
00458       else if ( pcIn == pcStart ) {
00459         pcStart = pcIn + 1;
00460         continue;
00461       }
00462 
00463       // normal component, add to the list
00464 
00465       // grow array?
00466       if ( nComponents == nMaxComponents ) {    
00467         // realloc array
00468         char **aszOld = aszPathComponents;
00469         nMaxComponents += COMPONENTS_INITIAL;
00470         aszPathComponents = new char *[nMaxComponents];
00471 
00472         // move data
00473         memmove(aszPathComponents, aszOld, 
00474                 sizeof(aszPathComponents[0]) * nComponents);
00475 
00476         // free old
00477         delete [] aszOld;
00478       }
00479 
00480       // do add
00481       aszPathComponents[nComponents] = new char[pcIn - pcStart + 1];
00482       strncpy(aszPathComponents[nComponents], pcStart, pcIn - pcStart);
00483       aszPathComponents[nComponents][pcIn - pcStart] = '\0';
00484       nComponents++;
00485 
00486       pcStart = pcIn + 1;
00487     }
00488   }
00489 
00490   if ( nComponents == 0 ) {
00491     // special case
00492     szNormPath[0] = '\0';
00493   }
00494   else {
00495     // put all components together
00496     len = 0;
00497     for ( size_t n = 0; n < nComponents; n++ ) {
00498       // add '/' before each new component except the first one
00499       if ( len != 0 ) {
00500         szNormPath[len++] = APPCONF_PATH_SEPARATOR;
00501       }
00502       szNormPath[len] = '\0';
00503 
00504       // concatenate
00505       strcat(szNormPath, aszPathComponents[n]);
00506 
00507       // update length
00508       len += strlen(aszPathComponents[n]);
00509 
00510       // and free memory
00511       delete [] aszPathComponents[n];
00512     }
00513   }
00514 
00515   delete [] aszPathComponents;
00516 
00517   return szNormPath;
00518 }
00519 
00520 void BaseConfig::changeCurrentPath(const char *szPath)
00521 {
00522   // special case (default value)
00523   if ( Strlen(szPath) == 0 ) {
00524     if ( m_szCurrentPath != NULL ) {
00525       delete [] m_szCurrentPath;
00526       m_szCurrentPath = NULL;
00527     }
00528   }    
00529   else {
00530     char *szNormPath;
00531 
00532     // if absolute path, start from top, otherwise from current
00533     if ( *szPath == APPCONF_PATH_SEPARATOR )
00534       szNormPath = normalizePath("", szPath + 1);
00535     else
00536       szNormPath = normalizePath(m_szCurrentPath ? m_szCurrentPath : "", szPath);
00537 
00538     size_t len = Strlen(szNormPath);
00539     if ( m_szCurrentPath == NULL || len > strlen(m_szCurrentPath) ) {
00540       // old buffer too small, alloc new one
00541       if ( m_szCurrentPath != NULL )
00542         delete [] m_szCurrentPath;
00543       m_szCurrentPath = new char[len + 1];
00544     }
00545 
00546     // copy and delete pointer returned by normalizePath
00547     strcpy(m_szCurrentPath, szNormPath);
00548 
00549     delete [] szNormPath;
00550   }
00551 }
00552 
00553 void BaseConfig::setCurrentPath(const char *szPath)
00554 {
00555   changeCurrentPath();                // goto root
00556   changeCurrentPath(szPath);      // now use relative change
00557 }
00558 
00559 const char *BaseConfig::getCurrentPath() const
00560 {
00561   return m_szCurrentPath == NULL ? "" : m_szCurrentPath;
00562 }
00563 
00564 // ----------------------------------------------------------------------------
00565 // filters (NB: filterOut(filterIn()) shouldn't change the string)
00566 // ----------------------------------------------------------------------------
00567 
00568 // called before writing
00569 char *BaseConfig::filterOut(const char *szValue)
00570 {
00571   // quote entire string if it starts with space or with quote
00572   Bool bDoQuote = isspace(*szValue) || (*szValue == '"');
00573 
00574   // calculate the length of resulting string
00575   size_t len = Strlen(szValue);
00576 
00577   const char *pcIn = szValue;
00578   while ( *pcIn ) {
00579     switch ( *pcIn++ ) {
00580       case '"':
00581         if ( !bDoQuote )
00582           break;
00583         //else: fall through
00584 
00585       case '\n':
00586       case '\t':
00587       case '\\':
00588         // one for extra '\'
00589         len++;
00590         break;
00591     }
00592   }
00593 
00594   if ( bDoQuote )
00595     len += 2;
00596 
00597   char *szBuf = new char[len + 1];
00598   char *pcOut = szBuf;
00599 
00600   if ( bDoQuote )
00601     *pcOut++ = '"';
00602 
00603   char c;
00604   for ( pcIn = szValue; *pcIn != '\0'; pcIn++ ) {
00605     switch ( *pcIn ) {
00606       case '\n':
00607         c = 'n';
00608         break;
00609 
00610       case '\t':
00611         c = 't';
00612         break;
00613 
00614       case '\\':
00615         c = '\\';
00616         break;
00617 
00618       case '"':
00619         if ( bDoQuote )
00620           c = '"';
00621         //else: fall through
00622 
00623       default:
00624         *pcOut++ = *pcIn;
00625         continue;   // skip special character procession
00626     }
00627 
00628     // we get here only for special characters
00629     *pcOut++ = '\\';
00630     *pcOut++ = c;
00631   }
00632 
00633   if ( bDoQuote )
00634     *pcOut++ = '"';
00635 
00636   *pcOut = '\0';
00637 
00638   return szBuf;
00639 }
00640 
00641 // called after reading
00642 char *BaseConfig::filterIn(const char *szValue)
00643 {
00644   // it will be a bit smaller, but who cares
00645   char *szBuf = new char[Strlen(szValue) + 1];
00646 
00647   const char *pcIn  = szValue;
00648   char *pcOut = szBuf;
00649 
00650   Bool bQuoted = *pcIn == '"';
00651   if ( bQuoted )
00652     pcIn++;
00653 
00654   while ( *pcIn != '\0' ) {
00655     switch ( *pcIn ) {
00656       case '\\':
00657         switch ( *++pcIn ) {
00658           case 'n':
00659             *pcOut++ = '\n';
00660             break;
00661 
00662           case 't':
00663             *pcOut++ = '\t';
00664             break;
00665 
00666           case '\\':
00667             *pcOut++ = '\\';
00668             break;
00669 
00670           case '"':
00671           default:
00672             // ignore '\'
00673             *pcOut++ = *pcIn;
00674         }
00675         break;
00676 
00677       case '"':
00678         if ( bQuoted ) {
00679           if ( *(pcIn + 1) != '\0' ) {
00680             LogWarning(_("invalid string '%s' in configuration file."), szValue);
00681           }
00682           break;
00683         }
00684         //else: fall through
00685 
00686       default:
00687         *pcOut++ = *pcIn;
00688     }
00689 
00690     pcIn++;
00691   }
00692 
00693   *pcOut = '\0';
00694 
00695   return szBuf;
00696 }
00697 
00698 // ----------------------------------------------------------------------------
00699 // implementation of Enumerator subclass
00700 // ----------------------------------------------------------------------------
00701 BaseConfig::Enumerator::Enumerator(size_t nCount, Bool bOwnsStrings)
00702 {
00703   m_bOwnsStrings = bOwnsStrings;
00704   m_aszData      = new char *[nCount];
00705   m_nCount       = 0;
00706 }
00707 
00708 // free memory
00709 BaseConfig::Enumerator::~Enumerator()
00710 {
00711   if ( m_bOwnsStrings ) {
00712     for ( size_t n = 0; n < m_nCount; n++ )
00713       delete [] m_aszData[n];
00714   }
00715 
00716   delete [] m_aszData;
00717 }
00718 
00719 void BaseConfig::Enumerator::AddString(char *sz)
00720 {
00721   m_aszData[m_nCount++] = sz;
00722 }
00723 
00724 void BaseConfig::Enumerator::AddString(const char *sz)
00725 {
00726   assert( !m_bOwnsStrings );
00727 
00728   // we won't delete it (see assert above), so we can cast in "char *"
00729   m_aszData[m_nCount++] = (char *)sz;
00730 }
00731 
00732 // remove duplicate strings
00733 void BaseConfig::Enumerator::MakeUnique()
00734 {
00735   char  **aszUnique = new char *[m_nCount];
00736   size_t  nUnique = 0;
00737   
00738   Bool bUnique;
00739   for ( size_t n = 0; n < m_nCount; n++ ) {
00740     bUnique = TRUE;
00741     for ( size_t n2 = n + 1; n2 < m_nCount; n2++ ) {
00742       if ( StrCmp(m_aszData[n], m_aszData[n2]) == 0 ) {
00743         bUnique = FALSE;
00744         break;
00745       }
00746     }
00747 
00748     if ( bUnique )
00749       aszUnique[nUnique++] = m_aszData[n];
00750     else if ( m_bOwnsStrings )
00751       delete [] m_aszData[n];
00752   }
00753 
00754   delete [] m_aszData;
00755   m_aszData = aszUnique;
00756   m_nCount = nUnique;
00757 }
00758 
00759 // ============================================================================
00760 // implementation of FileConfig class
00761 // ============================================================================
00762 
00763 // ----------------------------------------------------------------------------
00764 // FileConfig::ConfigEntry class
00765 // ----------------------------------------------------------------------------
00766 
00767 // ctor
00768 FileConfig::ConfigEntry::ConfigEntry(ConfigGroup *pParent, 
00769                                      ConfigEntry *pNext, 
00770                                      const char *szName)
00771 {
00772   m_pParent     = pParent;
00773   m_pNext       = pNext;
00774   m_szExpValue  =
00775   m_szValue     = 
00776   m_szComment   = NULL;
00777   m_bDirty      = 
00778   m_bLocal      = FALSE;
00779 
00780   // check for special prefix
00781   if ( *szName == APPCONF_IMMUTABLE_PREFIX ) {
00782     m_bImmutable = TRUE;
00783     szName++;             // skip prefix
00784   }
00785   else
00786     m_bImmutable = FALSE;
00787 
00788   m_szName  = new char[Strlen(szName) + 1]; 
00789   strcpy(m_szName, szName);
00790 }
00791 
00792 // dtor
00793 FileConfig::ConfigEntry::~ConfigEntry()
00794 {
00795   if ( m_szName != NULL )
00796     delete [] m_szName;
00797 
00798   if ( m_szValue != NULL )
00799     delete [] m_szValue;
00800     
00801   if ( m_szComment != NULL )
00802     delete [] m_szComment;
00803 
00804   if ( m_szExpValue != NULL )
00805     delete [] m_szExpValue;
00806 }
00807 
00808 // set value and dirty flag
00809 void FileConfig::ConfigEntry::SetValue(const char *szValue, 
00810                                        Bool bLocal, Bool bFromFile)
00811 {
00812    if ( m_szExpValue != NULL ) {
00813       delete [] m_szExpValue;
00814       m_szExpValue = NULL;
00815    }
00816 
00817    if ( m_szValue != NULL ) {
00818     // immutable changes can't be overriden (only check it here, because
00819     // they still can be set the first time)
00820     if ( m_bImmutable ) {
00821       LogWarning(_("attempt to change an immutable entry '%s' ignored."),
00822                  m_szName);
00823       return;
00824     }
00825 
00826     delete [] m_szValue;
00827   }
00828 
00829   // immutable entries are never changed and if an entry was just read
00830   // from file it shouldn't be dirty neither
00831   if ( !m_bImmutable && !bFromFile )
00832     SetDirty();
00833 
00834   m_bLocal = bLocal;
00835   if ( bLocal ) {
00836     // to ensure that local entries are saved we make them always dirty
00837     SetDirty();
00838   }
00839 
00840   if ( szValue != NULL )
00841   {
00842      m_szValue = new char[Strlen(szValue) + 1];
00843      strcpy(m_szValue, szValue);
00844   }
00845   else
00846   {
00847      m_szValue = NULL;
00848      SetDirty(FALSE);
00849   }
00850 }
00851 
00852 // set comment associated with this entry
00853 void FileConfig::ConfigEntry::SetComment(char *szComment)
00854 {
00855   assert( m_szComment == NULL );  // should be done only once
00856   
00857   // ... because we don't copy, just take the pointer
00858   m_szComment = szComment;
00859 }
00860 
00861 // set dirty flag
00862 void FileConfig::ConfigEntry::SetDirty(Bool bDirty)
00863 {
00864   // ## kludge: local entries are always dirty, otherwise they would be lost
00865   m_bDirty = m_bLocal ? TRUE : bDirty;
00866   if ( m_bDirty )
00867     m_pParent->SetDirty();
00868 }
00869 
00870 const char *FileConfig::ConfigEntry::ExpandedValue()
00871 {
00872   if ( m_szExpValue == NULL ) {
00873     // we have only to do expansion once
00874     m_szExpValue = ExpandEnvVars(m_szValue);
00875   }
00876 
00877   return m_szExpValue;
00878 }
00879 
00880 // ----------------------------------------------------------------------------
00881 // FileConfig::ConfigGroup class
00882 // ----------------------------------------------------------------------------
00883 
00884 // ctor & dtor
00885 // -----------
00886 
00887 FileConfig::ConfigGroup::ConfigGroup(ConfigGroup *pParent, 
00888                                      ConfigGroup *pNext, 
00889                                      const char *szName)
00890 { 
00891   m_pParent     = pParent; 
00892   m_pNext       = pNext; 
00893   m_pEntries    =
00894   m_pLastEntry  = NULL;
00895   m_pSubgroups  =
00896   m_pLastGroup  = NULL;
00897   m_bDirty      = FALSE;    // no entries yet
00898 
00899   m_szComment   = NULL;
00900   m_szName      = new char[Strlen(szName) + 1]; 
00901   strcpy(m_szName, szName); }
00902 
00903 FileConfig::ConfigGroup::~ConfigGroup()
00904 {
00905   // delete all entries
00906   ConfigEntry *pEntry, *pNextEntry;
00907   for ( pEntry = m_pEntries; pEntry != NULL; pEntry = pNextEntry ) {
00908     pNextEntry = pEntry->Next();
00909     delete pEntry;
00910   }
00911 
00912   // delete all subgroups
00913   ConfigGroup *pGroup, *pNextGroup;
00914   for ( pGroup = m_pSubgroups; pGroup != NULL; pGroup = pNextGroup ) {
00915     pNextGroup = pGroup->Next();
00916     delete pGroup;
00917   }
00918 
00919   if ( m_szName != NULL )
00920     delete [] m_szName;
00921 }
00922 
00923 // find
00924 // ----
00925 
00926 FileConfig::ConfigGroup *
00927             FileConfig::ConfigGroup::FindSubgroup(const char *szName) const
00928 {
00929   ConfigGroup *pGroup;
00930   for ( pGroup = m_pSubgroups; pGroup != NULL; pGroup = pGroup->Next() ) {
00931     if ( !StrCmp(pGroup->Name(), szName) )
00932       return pGroup;
00933   }
00934 
00935   return NULL;
00936 }
00937 
00938 FileConfig::ConfigEntry *
00939             FileConfig::ConfigGroup::FindEntry(const char *szName) const
00940 {
00941   ConfigEntry *pEntry;
00942   for ( pEntry = m_pEntries; pEntry != NULL; pEntry = pEntry->Next() ) {
00943     if ( !StrCmp(pEntry->Name(), szName) )
00944       return pEntry;
00945   }
00946 
00947   return NULL;
00948 }
00949 
00950 // add
00951 // ---
00952 
00953 // add an item at the bottom of the stack (i.e. it's a LILO really)
00954 FileConfig::ConfigGroup *FileConfig::ConfigGroup::AddSubgroup(const char *szName)
00955 {
00956   ConfigGroup *pGroup = new ConfigGroup(this, NULL, szName);
00957 
00958   if ( m_pSubgroups == NULL ) {
00959     m_pSubgroups = 
00960     m_pLastGroup = pGroup;
00961   }
00962   else {
00963     m_pLastGroup = m_pLastGroup->m_pNext = pGroup;
00964   }
00965 
00966   return pGroup;
00967 }
00968 
00969 // add an item at the bottom
00970 FileConfig::ConfigEntry *FileConfig::ConfigGroup::AddEntry(const char *szName)
00971 {
00972   ConfigEntry *pEntry = new ConfigEntry(this, NULL, szName);
00973   
00974   if ( m_pEntries == NULL ) {
00975     m_pEntries   = 
00976     m_pLastEntry = pEntry;
00977   }
00978   else {
00979     m_pLastEntry->SetNext(pEntry);
00980     m_pLastEntry = pEntry;
00981   }
00982 
00983   return pEntry;
00984 }
00985 
00986 // delete
00987 // ------
00988 
00989 Bool FileConfig::ConfigGroup::DeleteSubgroup(const char *szName)
00990 {
00991   ConfigGroup *pGroup, *pPrevGroup = NULL;
00992   for ( pGroup = Subgroup(); pGroup != NULL; pGroup = pGroup->Next() ) {
00993     if ( StrCmp(pGroup->Name(), szName) == 0 ) {
00994       break;
00995     }
00996 
00997     pPrevGroup = pGroup;
00998   }
00999 
01000   if ( pGroup == NULL )
01001     return FALSE;
01002 
01003   // remove the next element in the linked list
01004   if ( pPrevGroup == NULL ) {
01005     m_pSubgroups = pGroup->Next();
01006   }
01007   else {
01008     pPrevGroup->m_pNext = pGroup->Next();
01009   }
01010 
01011   // adjust pointer to the last element
01012   if ( pGroup->Next() == NULL ) {
01013     m_pLastGroup = pPrevGroup == NULL ? m_pSubgroups : pPrevGroup;
01014   }
01015   
01016   // shouldn't have any entries/subgroups or they would be never deleted
01017   // resulting in memory leaks (or we then should delete them here)
01018   assert( pGroup->Entries() == NULL && pGroup->Subgroup() == NULL );
01019   delete pGroup;
01020 
01021   return TRUE;
01022 }
01023 
01024 Bool FileConfig::ConfigGroup::DeleteEntry(const char *szName)
01025 {
01026   ConfigEntry *pEntry, *pPrevEntry = NULL;
01027   for ( pEntry = Entries(); pEntry != NULL; pEntry = pEntry->Next() ) {
01028     if ( StrCmp(pEntry->Name(), szName) == 0 ) {
01029       break;
01030     }
01031 
01032     pPrevEntry = pEntry;
01033   }
01034 
01035   if ( pEntry == NULL )
01036     return FALSE;
01037 
01038   // remove the element from the linked list
01039   if ( pPrevEntry == NULL ) {
01040     m_pEntries = pEntry->Next();
01041   }
01042   else {
01043     pPrevEntry->SetNext(pEntry->Next());
01044   }
01045 
01046   // adjust the pointer to the last element
01047   if ( pEntry->Next() == NULL ) {
01048     m_pLastEntry = pPrevEntry == NULL ? m_pEntries : pPrevEntry;
01049   }
01050 
01051   // ... and free memory
01052   delete pEntry;
01053 
01054   m_pParent->SetDirty();
01055 
01056   return TRUE;
01057 }
01058 
01059 // deletes this group if it has no more entries/subgroups
01060 Bool FileConfig::DeleteIfEmpty()
01061 {
01062   // check if there any other subgroups/entries left
01063   if ( m_pCurGroup->Entries() != NULL || m_pCurGroup->Subgroup() != NULL )
01064     return FALSE;
01065 
01066   if ( m_pCurGroup->Parent() == NULL ) {
01067     // top group, can't delete it but mark it as not dirty,
01068     // so that local config file will not even be created
01069     m_pCurGroup->SetDirty(FALSE);
01070   }
01071   else {
01072     // delete current group
01073     const char *szName = m_pCurGroup->Name();
01074     m_pCurGroup = m_pCurGroup->Parent();
01075     m_pCurGroup->DeleteSubgroup(szName);
01076   }
01077 
01078   // recursively call ourselves
01079   DeleteIfEmpty();
01080 
01081   return TRUE;
01082 }
01083 
01084 // other
01085 // -----
01086 
01087 // get absolute name
01088 char *FileConfig::ConfigGroup::FullName() const
01089 {
01090   char *szFullName;
01091   if ( m_pParent == NULL ) {
01092     // root group has no name
01093     return NULL;
01094   }
01095   else {
01096     char *pParentFullName = m_pParent->FullName();
01097     if ( pParentFullName == NULL ) {
01098       szFullName = new char[Strlen(m_szName) + 1];
01099       strcpy(szFullName, m_szName);
01100     }
01101     else {
01102       size_t len = Strlen(pParentFullName);
01103       szFullName = new char[len + Strlen(m_szName) + 2];
01104       strcpy(szFullName, pParentFullName);
01105       szFullName[len] = APPCONF_PATH_SEPARATOR;   // unfortunately strcat doesn't
01106       szFullName[len + 1] = '\0';                 // take 'char' argument
01107       strcat(szFullName, m_szName);
01108       delete [] pParentFullName;
01109     }
01110   }
01111 
01112   return szFullName;
01113 }
01114 
01115 // set dirty flag and propagate it as needed
01116 // NB: when we set dirty, propagate to parent; when clear - to children
01117 void FileConfig::ConfigGroup::SetDirty(Bool bDirty)
01118 {
01119   m_bDirty = bDirty;
01120   if ( bDirty ) {
01121     if ( m_pParent != NULL )
01122       m_pParent->SetDirty();
01123   }
01124   else {
01125     ConfigEntry *pEntry;
01126     for ( pEntry = Entries(); pEntry != NULL; pEntry = pEntry->Next() )
01127       pEntry->SetDirty(FALSE);
01128 
01129     ConfigGroup *pGroup;
01130     for ( pGroup = Subgroup(); pGroup != NULL; pGroup = pGroup->Next() )
01131       pGroup->SetDirty(FALSE);
01132   }
01133 }
01134 
01135 // write changed data
01136 Bool FileConfig::ConfigGroup::flush(std::ostream *ostr)
01137 {
01138   // write all entries that were changed in this group
01139   Bool bFirstDirty = TRUE;
01140   ConfigEntry *pEntry;
01141   for ( pEntry = Entries(); pEntry != NULL; pEntry = pEntry->Next() ) {
01142     if ( pEntry->IsDirty() && pEntry->Value()) {
01143       if ( bFirstDirty ) {
01144         // output preceding comment for this group if any
01145         if ( Comment() != NULL ) {
01146           *ostr << Comment();
01147         }
01148         
01149         // output group header
01150         char *pName = FullName();
01151         if ( pName != NULL ) {
01152           *ostr << '[' << pName << ']';
01153           if ( pEntry->Comment() == NULL )
01154             *ostr << endl;
01155           delete [] pName;
01156         }
01157         //else: it's the top (default) group
01158 
01159         bFirstDirty = FALSE;  // only first time
01160       }
01161 
01162       // output preceding comment for this entry if any
01163       if ( pEntry->Comment() != NULL ) {
01164         *ostr << pEntry->Comment();
01165       }
01166       
01167       char *szFilteredValue = filterOut(pEntry->Value());
01168       *ostr << pEntry->Name() << " = " << szFilteredValue << endl;
01169       delete [] szFilteredValue;
01170 
01171       pEntry->SetDirty(FALSE);
01172     }
01173   }
01174 
01175   // flush all subgroups
01176   ConfigGroup *pGroup;
01177   Bool bOk = TRUE;
01178   for ( pGroup = Subgroup(); pGroup != NULL; pGroup = pGroup->Next() ) {
01179     if ( pGroup->IsDirty() && !pGroup->flush(ostr) ) {
01180       bOk = FALSE;
01181     }
01182   }
01183 
01184   return bOk;
01185 }
01186 
01187 
01188 // associate a comment with this group (comments are all we ignore,
01189 // including real comments and whitespace)
01190 void FileConfig::ConfigGroup::SetComment(char *szComment)
01191 {
01192   assert( m_szComment == NULL );  // should be done only once
01193   
01194   m_szComment = szComment;
01195 }
01196 
01197 // ----------------------------------------------------------------------------
01198 // ctor and dtor
01199 // ----------------------------------------------------------------------------
01200 
01201 // common part of both constructors
01202 void FileConfig::Init()
01203 {
01204   // top group always exists and must be created before reading from files
01205   m_pRootGroup = new ConfigGroup(NULL, NULL, "");
01206 
01207   m_szComment = NULL;
01208   m_bOk = FALSE;                // not (yet) initialized ok
01209 
01210   m_bExpandVariables = FALSE;   // don't do it automatically by default
01211 }
01212 
01213 // ctor reads data from files
01214 FileConfig::FileConfig(const char *szFileName, Bool bLocalOnly, Bool bUseSubDir)
01215 {
01216   Init();
01217   
01218   m_bUseSubDir = bUseSubDir;
01219   
01220   m_szFileName = new char[Strlen(szFileName) + 1];
01221   strcpy(m_szFileName, szFileName);
01222   
01223   // read global (system wide) file
01224   // ------------------------------
01225   ifstream inpStream;
01226   if ( !bLocalOnly ) {
01227     m_szFullFileName = GlobalConfigFile();
01228     inpStream.open(m_szFullFileName, ios::in);
01229     if ( inpStream ) {
01230       m_bParsingLocal = FALSE;
01231       m_bOk = readStream(&inpStream);
01232     }
01233 
01234     inpStream.close();
01235     inpStream.clear();
01236   }
01237 
01238   // read local (user's) file
01239   // ------------------------
01240   m_szFullFileName = LocalConfigFile();
01241   if ( m_szFullFileName != NULL ) {
01242     inpStream.open(m_szFullFileName, ios::in);
01243     if ( inpStream ) {
01244       m_bParsingLocal = TRUE;
01245       if ( readStream(&inpStream) ) {
01246         m_bOk = TRUE;
01247       }
01248       //else: depends on global file - if it was read ok, it's ok
01249     }
01250   }
01251 
01252   m_pCurGroup = m_pRootGroup;
01253   BaseConfig::setCurrentPath("");
01254 }
01255 
01256 // ctor reads data from files
01257 FileConfig::FileConfig(istream *iStream)
01258 {
01259   Init();
01260   
01261   m_szFileName = NULL;
01262 
01263   if ( iStream == NULL )
01264      return;
01265 
01266   m_bParsingLocal = TRUE;
01267   m_bOk = readStream(iStream);
01268   
01269   m_pCurGroup = m_pRootGroup;
01270   BaseConfig::setCurrentPath("");
01271 }
01272 
01273 FileConfig::FileConfig(void)
01274 {
01275    Init();
01276    m_szFileName = NULL;
01277 }
01278 
01279 void
01280 FileConfig::readFile(const char *szFileName)
01281 {
01282   ifstream inpStream;
01283 
01284   Init();
01285 
01286    m_szFileName = new char[Strlen(szFileName) + 1];
01287    strcpy(m_szFileName, szFileName);
01288 
01289    m_szFullFileName = m_szFileName;
01290    inpStream.open(m_szFullFileName, ios::in);
01291    if ( inpStream ) {
01292       m_bParsingLocal = TRUE;
01293       if ( readStream(&inpStream) ) {
01294          m_bOk = TRUE;
01295       }
01296    }
01297 
01298    m_pCurGroup = m_pRootGroup;
01299    BaseConfig::setCurrentPath("");
01300 }
01301 
01302 // dtor writes changes
01303 FileConfig::~FileConfig()
01304 {
01305   if( m_szFileName != NULL )    // if empty, was intialised from stream
01306     flush();
01307   
01308   if ( m_szComment != NULL )
01309     delete [] m_szComment;
01310     
01311   delete m_pRootGroup;
01312   
01313   if ( m_szFileName != NULL )
01314      delete m_szFileName;
01315 }
01316 
01317 // ----------------------------------------------------------------------------
01318 // config files standard locations
01319 // ----------------------------------------------------------------------------
01320 
01321 // ### buffer overflows in sight...
01322 const char *FileConfig::GlobalConfigFile() const
01323 {
01324   static char s_szBuf[MAX_PATH];
01325 
01326   // check if file has extension
01327   Bool bNoExt = strchr(m_szFileName, '.') == NULL;
01328 
01329 #ifdef  __unix__
01330     strcpy(s_szBuf, "/etc/");
01331     strcat(s_szBuf, m_szFileName);
01332     if ( bNoExt )
01333       strcat(s_szBuf, ".conf");
01334 #else   // Windows
01335     char szWinDir[MAX_PATH];
01336     ::GetWindowsDirectory(szWinDir, MAX_PATH);
01337     strcpy(s_szBuf, szWinDir);
01338     strcat(s_szBuf, "\\");
01339     strcat(s_szBuf, m_szFileName);
01340     if ( bNoExt )
01341       strcat(s_szBuf, ".INI");
01342 #endif  // UNIX/Win
01343 
01344   return s_szBuf;
01345 }
01346 
01347 // ### buffer overflows in sight...
01348 const char *FileConfig::LocalConfigFile() const
01349 {
01350   static char s_szBuf[MAX_PATH];
01351 
01352 #ifdef  __unix__
01353     const char *szHome = getenv("HOME");
01354     if ( szHome == NULL ) {
01355       // we're homeless...
01356       LogInfo(_("can't find user's HOME, looking for config file in current directory."));
01357       szHome = ".";
01358     }
01359     strcpy(s_szBuf, szHome);
01360     strcat(s_szBuf, "/.");
01361     strcat(s_szBuf, m_szFileName);
01362     if(m_bUseSubDir) // look for ~/.appname/config instead of ~/.appname
01363     {
01364        mkdir(s_szBuf, 0755);    // try to make it, just in case it
01365                                 // didn't exist yet
01366        strcat(s_szBuf, "/config");
01367     }
01368 #else   // Windows
01369 #ifdef  __WIN32__
01370       const char *szHome = getenv("HOMEDRIVE");
01371       if ( szHome == NULL )
01372         szHome = "";
01373       strcpy(s_szBuf, szHome);
01374       szHome = getenv("HOMEPATH");
01375       if ( szHome == NULL )
01376         strcpy(s_szBuf, ".");
01377       else
01378         strcat(s_szBuf, szHome);
01379       strcat(s_szBuf, m_szFileName);
01380       if ( strchr(m_szFileName, '.') == NULL )
01381         strcat(s_szBuf, ".INI");
01382 #else   // Win16
01383       // Win 3.1 has no idea about HOME, so we use only the system wide file
01384       if ( !bLocalOnly ) {
01385         // we've already read it
01386         s_szBuf = NULL;
01387       }
01388       else {
01389         s_szBuf = GlobalConfigFile();
01390       }
01391     }
01392 #endif  // WIN16/32
01393 #endif  // UNIX/Win
01394 
01395   return s_szBuf;
01396 }
01397 
01398 // ----------------------------------------------------------------------------
01399 // read/write
01400 // ----------------------------------------------------------------------------
01401 
01402 // parse the stream and create all groups and entries found in it
01403 Bool FileConfig::readStream(istream *istr, ConfigGroup *pRootGroup)
01404 {
01405   char szBuf[APPCONF_STRBUFLEN];
01406 
01407   m_pCurGroup = pRootGroup == NULL ? m_pRootGroup : pRootGroup;
01408 
01409   m_uiLine = 1;
01410 
01411   for (;;) {
01412     istr->getline(szBuf, APPCONF_STRBUFLEN, '\n');
01413     if ( istr->eof() ) {
01414     // last line may contain text also
01415     // (if it's not terminated with '\n' EOF is returned)
01416       return parseLine(szBuf);
01417     }
01418 
01419     if ( !istr->good() || !parseLine(szBuf) )
01420       return FALSE;      
01421 
01422     m_uiLine++;
01423   }
01424 }
01425 
01426 // parses one line of config file, returns FALSE on error
01427 Bool FileConfig::parseLine(const char *psz)
01428 {
01429   size_t len; // temp var
01430 
01431   const char *pStart = psz;
01432   
01433   // eat whitespace
01434   while ( isspace(*psz) ) psz++;
01435 
01436   // ignore empty lines or comments
01437   if ( *psz == '#' || *psz == ';' || *psz == '\0' ) {
01438     if ( *pStart != '\0' )
01439       AppendCommentLine(pStart);
01440     //else
01441     //  AppendCommentLine();
01442     return TRUE;
01443   }
01444 
01445   if ( *psz == '[' ) {          // a new group
01446     const char *pEnd = ++psz;
01447 
01448     while ( *pEnd != ']' ) {
01449       if ( IsValid(*pEnd) ) {
01450         // continue reading the group name
01451         pEnd++;
01452       }
01453       else {
01454         if ( *pEnd != APPCONF_PATH_SEPARATOR ) {
01455           LogError(_("file '%s': unexpected character at line %d (missing ']'?)"),
01456                            m_szFullFileName, m_uiLine);
01457           return FALSE;
01458         }
01459       }
01460     }
01461 
01462     len = pEnd - psz;
01463     char *szGroup = new char[len + 2];
01464     szGroup[0] = APPCONF_PATH_SEPARATOR;   // always considered as abs path
01465     szGroup[1] = '\0';
01466     strncat(szGroup, psz, len);
01467     
01468     // will create it if doesn't yet exist
01469     setCurrentPath(szGroup);
01470 
01471     // was there a comment before it?
01472     if ( m_szComment != NULL ) {
01473       m_pCurGroup->SetComment(m_szComment);
01474       m_szComment = NULL;
01475     }
01476     
01477     delete [] szGroup;
01478     
01479     // are there any comments after it?
01480     Bool bComment = FALSE;
01481     for ( pStart = ++pEnd ; *pEnd != '\0'; pEnd++ ) {
01482       switch ( *pEnd ) {
01483         case '#':
01484         case ';':
01485           bComment = TRUE;
01486           break;
01487         
01488         case ' ':
01489         case '\t':
01490           // ignore whitespace ('\n' impossible here)
01491           break;
01492           
01493         default:
01494           if ( !bComment ) {
01495             LogWarning(_("file '%s', line %d: '%s' ignored after group header."), 
01496                        m_szFullFileName, m_uiLine, pEnd);
01497             return TRUE;
01498           }
01499       }
01500     }
01501       
01502     if ( bComment ) {
01503       AppendCommentLine(pStart);
01504     }
01505   }
01506   else {                        // a key
01507     const char *pEnd = psz;
01508     while ( IsValid(*pEnd) )
01509       pEnd++;
01510 
01511     len = pEnd - psz;
01512     char *szKey = new char[len + 1];
01513     strncpy(szKey, psz, len + 1);
01514     szKey[len] = '\0';
01515 
01516     while ( isspace(*pEnd) ) pEnd++;  // eat whitespace
01517 
01518     if ( *pEnd++ != '=' ) {
01519       LogError(_("file '%s': expected '=' at line %d."), 
01520                m_szFullFileName, m_uiLine);
01521       return FALSE;
01522     }
01523 
01524     while ( isspace(*pEnd) ) pEnd++;  // eat whitespace
01525 
01526     ConfigEntry *pEntry = m_pCurGroup->FindEntry(szKey);
01527 
01528     if ( pEntry == NULL ) {
01529       pEntry = m_pCurGroup->AddEntry(szKey);
01530     }
01531     /* gives erroneous warnings when the same key appears in
01532        both global and local config files. Would be nice to
01533        have something happen when the same key appears twice
01534        (or more) in a section though.
01535   
01536       else {
01537         LogWarning(_("key '%s' has more than one value."), szKey);
01538       }
01539 
01540     */
01541 
01542     if ( m_szComment != NULL ) {
01543       pEntry->SetComment(m_szComment);
01544       m_szComment = NULL;
01545     }
01546     
01547     char *szUnfilteredValue = filterIn(pEnd);
01548     pEntry->SetValue(szUnfilteredValue, m_bParsingLocal, TRUE);
01549 
01550     delete [] szUnfilteredValue;
01551     delete [] szKey;
01552   }
01553 
01554   return TRUE;
01555 }
01556 
01557 // ----------------------------------------------------------------------------
01558 // read data (which are fully loaded in memory)
01559 // ----------------------------------------------------------------------------
01560 
01561 const char *FileConfig::readEntry(const char *szKey, 
01562                                   const char *szDefault) const
01563 {
01564   ConfigEntry *pEntry = m_pCurGroup->FindEntry(szKey);
01565 
01566   if ( pEntry != NULL ) {
01567     if ( m_bExpandVariables )
01568       return pEntry->ExpandedValue();
01569     else
01570       return pEntry->Value();
01571   }
01572 
01573   if(m_bRecordDefaults)
01574      ((FileConfig *)this)->writeEntry(szKey,szDefault);
01575 
01576   // entry wasn't found
01577   return szDefault;
01578 }
01579 
01580 // ----------------------------------------------------------------------------
01581 // functions which update data in memory
01582 // ----------------------------------------------------------------------------
01583 
01584 // create new group if this one doesn't exist yet
01585 void FileConfig::changeCurrentPath(const char *szPath)
01586 {
01587   // normalize path
01588   BaseConfig::changeCurrentPath(szPath);
01589   szPath = getCurrentPath();
01590 
01591   m_pCurGroup = m_pRootGroup;
01592 
01593   // special case of empty path
01594   if ( *szPath == '\0' )
01595     return;
01596     
01597   char   *szGroupName = NULL;
01598   size_t  len = 0;
01599 
01600   const char *pBegin = szPath;
01601   const char *pEnd   = pBegin + 1;
01602 
01603   do{//while ( *pEnd != '\0' ) {
01604     while ( *pEnd != '\0' && *pEnd != APPCONF_PATH_SEPARATOR )
01605       pEnd++;
01606 
01607     if ( (unsigned)(pEnd - pBegin) + 1 > len ) {
01608       // not enough space, realloc buffer
01609       len = pEnd - pBegin + 1;
01610 
01611       // also delete old one
01612       if ( szGroupName != NULL )
01613         delete [] szGroupName;
01614 
01615       szGroupName = new char[len];
01616     }
01617 
01618     strncpy(szGroupName, pBegin, len);
01619     szGroupName[len - 1] = '\0';
01620 
01621     ConfigGroup *pGroup = m_pCurGroup->FindSubgroup(szGroupName);
01622     
01623     if ( pGroup == NULL ) {
01624       // not found: insert new group in the list
01625       m_pCurGroup = m_pCurGroup->AddSubgroup(szGroupName);
01626     }
01627     else {
01628       m_pCurGroup = pGroup;
01629     }
01630 
01631     if ( *pEnd == APPCONF_PATH_SEPARATOR ) {
01632       pBegin = ++pEnd;
01633     }
01634   }while ( *pEnd != '\0' );
01635 
01636   if ( szGroupName != NULL )
01637     delete [] szGroupName;
01638 }
01639 
01640 // set value of a key, creating it if doesn't yet exist
01641 Bool FileConfig::writeEntry(const char *szKey, const char *szValue)
01642 {
01643   ConfigEntry *pEntry = m_pCurGroup->FindEntry(szKey);
01644   if ( pEntry == NULL ) {
01645     pEntry = m_pCurGroup->AddEntry(szKey);
01646   }
01647 
01648   pEntry->SetValue(szValue);
01649 
01650   return TRUE;
01651 }
01652 
01653 // delete entry and the parent group if it's the last entry in this group
01654 Bool FileConfig::deleteEntry(const char *szKey)
01655 {
01656   Bool bDeleted = m_pCurGroup->DeleteEntry(szKey);
01657 
01658   DeleteIfEmpty();
01659 
01660   return bDeleted;
01661 }
01662 
01663 // ----------------------------------------------------------------------------
01664 // functions which update data on disk (or wherever...)
01665 // ----------------------------------------------------------------------------
01666 
01667 Bool FileConfig::flush(Bool bCurrentOnly)
01668 {
01669   ConfigGroup *pRootGroup = bCurrentOnly ? m_pCurGroup : m_pRootGroup;
01670 
01671   if ( m_pRootGroup->IsDirty() ) {
01672     fstream outStream(LocalConfigFile(), ios::out);
01673 
01674     Bool bOk = pRootGroup->flush(&outStream);
01675     
01676     // special case: [comment and blank] lines following the last entry
01677     // were not yet written to the file, do it now.
01678     if ( m_szComment != NULL ) {
01679       outStream << m_szComment;
01680     }
01681     
01682     outStream.sync();
01683 
01684     return bOk;
01685   }
01686   else
01687     return TRUE;
01688 }
01689 
01690 Bool FileConfig::flush(std::ostream *oStream, Bool bCurrentOnly)
01691 {
01692   ConfigGroup *pRootGroup = bCurrentOnly ? m_pCurGroup : m_pRootGroup;
01693 
01694   if ( m_pRootGroup->IsDirty() ) {
01695     
01696     Bool bOk = pRootGroup->flush(oStream);
01697     
01698     // special case: [comment and blank] lines following the last entry
01699     // were not yet written to the file, do it now.
01700     if ( m_szComment != NULL ) {
01701       *oStream << m_szComment;
01702     }
01703     
01704     return bOk;
01705   }
01706   else
01707     return TRUE;
01708 }
01709 
01710 // ----------------------------------------------------------------------------
01711 // Comments management
01712 // ## another kludge: this function is always called, but does nothing when
01713 //    parsing global file (we can't write these comments back anyhow)
01714 // ----------------------------------------------------------------------------
01715 void FileConfig::AppendCommentLine(const char *szComment)
01716 {
01717   if ( !m_bParsingLocal )
01718     return;
01719 
01720   size_t len = Strlen(m_szComment) + strlen(szComment) + 1;
01721     
01722   char *szNewComment = new char[len + 1];
01723   if ( m_szComment == NULL ) {
01724     szNewComment[0] = '\0';
01725   }
01726   else {
01727     strcpy(szNewComment, m_szComment);
01728     delete [] m_szComment;
01729   }  
01730   strcat(szNewComment, szComment);
01731   strcat(szNewComment, "\n");
01732   
01733   m_szComment = szNewComment;
01734 }
01735 
01736 // ----------------------------------------------------------------------------
01737 // Enumeration of entries/subgroups
01738 // ----------------------------------------------------------------------------
01739 
01740 BaseConfig::Enumerator *FileConfig::enumSubgroups() const
01741 {
01742   size_t nGroups = 0;
01743   ConfigGroup *pGroup = m_pCurGroup->Subgroup();
01744   while ( pGroup != NULL ) {
01745     nGroups++;
01746     pGroup = pGroup->Next();
01747   }
01748 
01749   BaseConfig::Enumerator *pEnum = new Enumerator(nGroups, FALSE);
01750 
01751   pGroup = m_pCurGroup->Subgroup();
01752   for ( size_t n = 0; n < nGroups; n++ ) {
01753     pEnum->AddString(pGroup->Name());
01754     pGroup = pGroup->Next();
01755   }
01756 
01757   return pEnum;
01758 }
01759 
01760 BaseConfig::Enumerator *FileConfig::enumEntries() const
01761 {
01762   size_t nEntries = 0;
01763   ConfigEntry *pEntry = m_pCurGroup->Entries();
01764   while ( pEntry != NULL ) {
01765     nEntries++;
01766     pEntry = pEntry->Next();
01767   }
01768 
01769   BaseConfig::Enumerator *pEnum = new Enumerator(nEntries, FALSE);
01770 
01771   pEntry = m_pCurGroup->Entries();
01772   for ( size_t n = 0; n < nEntries; n++ ) {
01773     pEnum->AddString(pEntry->Name());
01774     pEntry = pEntry->Next();
01775   }
01776 
01777   return pEnum;
01778 }
01779 
01780 // ============================================================================
01781 // implementation of the class RegistryConfig
01782 // ============================================================================
01783 #ifdef  __WIN32__
01784 
01785 // ----------------------------------------------------------------------------
01786 // ctor & dtor
01787 // ----------------------------------------------------------------------------
01788 
01789 // ctor opens our root key under HKLM
01790 RegistryConfig::RegistryConfig(const char *szRootKey)
01791 {
01792   static const char szPrefix[] = "Software";
01793 
01794   char *szKey = new char[strlen(szPrefix) + Strlen(szRootKey) + 2];
01795   strcpy(szKey, szPrefix);
01796   strcat(szKey, "\\");
01797   strcat(szKey, szRootKey);
01798 
01799   // don't create if doesn't exist
01800   LONG lRc = RegOpenKey(HKEY_LOCAL_MACHINE, szKey, &m_hGlobalRootKey);
01801   m_bOk = lRc == ERROR_SUCCESS;
01802   if ( !m_bOk )
01803     m_hGlobalRootKey = NULL;
01804 
01805   m_hGlobalCurKey = m_hGlobalRootKey;
01806 
01807   // create if doesn't exist
01808   lRc = RegCreateKey(HKEY_CURRENT_USER, szKey, &m_hLocalRootKey);
01809   m_hLocalCurKey = m_hLocalRootKey;
01810   if ( !m_bOk )
01811     m_bOk = lRc;
01812 
01813   m_pLastRead = NULL;
01814 
01815   delete [] szKey;
01816 }
01817 
01818 // free resources
01819 RegistryConfig::~RegistryConfig()
01820 {
01821   if ( m_pLastRead != NULL )
01822     delete [] m_pLastRead;
01823 
01824   if ( m_hGlobalRootKey != NULL )
01825     RegCloseKey(m_hGlobalRootKey);
01826   if ( m_hLocalRootKey != NULL )
01827     RegCloseKey(m_hLocalRootKey);
01828 
01829   // don't close same key twice
01830   if ( m_hGlobalCurKey != NULL && m_hGlobalCurKey != m_hGlobalRootKey )
01831     RegCloseKey(m_hGlobalCurKey);
01832   if ( m_hLocalCurKey != NULL && m_hLocalCurKey != m_hLocalRootKey )
01833     RegCloseKey(m_hLocalCurKey);
01834 }
01835 
01836 // ----------------------------------------------------------------------------
01837 // read/write entry - no buffering, data goes directly to the registry
01838 // ----------------------------------------------------------------------------
01839 
01840 // helper for readEntry which needs to read both the global and local ones
01841 // ### MT unsafe because of m_pLastRead
01842 const char *RegistryConfig::ReadValue(void *hKey, const char *szValue) const
01843 {
01844   // get size of data first
01845   DWORD dwSize;
01846   LONG lRc = RegQueryValueEx(hKey, szValue, 0, NULL, NULL, &dwSize);
01847 
01848   if ( lRc != ERROR_SUCCESS )
01849     return NULL;
01850 
01851   char *szBuffer = new char[dwSize];
01852   DWORD dwType;
01853   lRc = RegQueryValueEx(hKey, szValue, 0, &dwType, 
01854                         (unsigned char *)szBuffer, &dwSize);
01855 
01856   if ( lRc != ERROR_SUCCESS ) {
01857     // it's not normal for it to fail now (i.e. after the first call was ok)
01858     LogWarning(_("can't query the value '%s' (%s)."), szValue, SysError());
01859 
01860     return NULL;
01861   }
01862 
01863   // always automatically expand strings of this type
01864   if ( dwType == REG_EXPAND_SZ ) {
01865     // first get length
01866     DWORD dwLen = ExpandEnvironmentStrings(szBuffer, NULL, 0);    
01867 
01868     // alloc memory and expand
01869     char *szBufferExp = new char[dwLen + 1];
01870     ExpandEnvironmentStrings(szBuffer, szBufferExp, dwLen + 1);
01871 
01872     // copy to the original buffer
01873     delete [] szBuffer;
01874     szBuffer = szBufferExp;
01875   }
01876 
01877   // free old pointer (last thing we returned from here)
01878   if ( m_pLastRead != NULL )
01879     delete [] m_pLastRead;
01880 
01881   // const_cast
01882   ((RegistryConfig *)this)->m_pLastRead = filterIn((const char *)szBuffer);
01883   delete [] szBuffer;
01884 
01885   return m_pLastRead;
01886 }
01887 
01888 // read value from the current key
01889 // first try the user's setting, than the system.
01890 const char *RegistryConfig::readEntry(const char *szKey, 
01891                                       const char *szDefault) const
01892 {
01893   const char *pRetValue = ReadValue(m_hLocalCurKey, szKey);
01894 
01895   // if no local value exist and current path exists in global part, try it
01896   if ( pRetValue == NULL && m_hGlobalCurKey != NULL )
01897     pRetValue = ReadValue(m_hGlobalCurKey, szKey);
01898 
01899   if(pRetValue != NULL)
01900      return pRetValue;
01901   else
01902   {
01903      if(m_bRecordDefaults)
01904         writeEntry(szKey, szDefault);
01905      return szDefault;
01906   }
01907 }
01908 
01909 // write (or create) value under current key
01910 // NB: only to user's part
01911 Bool RegistryConfig::writeEntry(const char *szKey, const char *szValue)
01912 {
01913   char *szFiltered = filterOut(szValue);
01914 
01915   long lRc = RegSetValueEx(m_hLocalCurKey, szKey, 0, REG_SZ, 
01916                            (unsigned char *)szFiltered, 
01917                            Strlen(szFiltered) + 1);
01918 
01919   delete [] szFiltered;
01920 
01921   return lRc == ERROR_SUCCESS;
01922 }
01923 
01924 // delete the key and the key which contains it if this is the last subkey
01925 Bool RegistryConfig::deleteEntry(const char *szKey)
01926 {
01927   Bool bDeleted = RegDeleteValue(m_hLocalCurKey, szKey) == ERROR_SUCCESS;
01928   if ( !bDeleted ) {
01929     // # don't output error message if the key is not found?
01930     LogWarning(_("can't delete entry '%s': %s"), szKey, SysError());
01931   }
01932 
01933   // even if there was an error before, still check if the key is not empty:
01934   // otherwise an attempt to delete a non existing entry would _create_ a
01935   // new subkey!
01936   while ( KeyIsEmpty(m_hLocalCurKey) ) {
01937     // delete this key
01938     const char *szPath = getCurrentPath();
01939     const char *szKeyName = strrchr(szPath, APPCONF_PATH_SEPARATOR);
01940     if ( szKeyName == NULL )
01941       szKeyName = szPath;
01942     else
01943       szKeyName++;  // to skip APPCONF_PATH_SEPARATOR
01944 
01945     // must copy it because the call to changeCurrentPath will change it!
01946     char *aszSubkey = new char[strlen(szKeyName) + 1];
01947     strcpy(aszSubkey, szKeyName);
01948 
01949     // don't delete root key
01950     if ( *szKeyName != '\0' ) {
01951       changeCurrentPath("..");
01952       if ( RegDeleteKey(m_hLocalCurKey, aszSubkey) != ERROR_SUCCESS )
01953         LogWarning(_("can't delete key '%s': %s"), aszSubkey, SysError());
01954     }
01955 
01956     delete [] aszSubkey;
01957   }
01958 
01959   return bDeleted;
01960 }
01961 
01962 // ----------------------------------------------------------------------------
01963 // Enumeration of entries/subgroups (both system and user)
01964 // ### this code is messy :-( The problem is that we try to enumerate both the
01965 //     global and local keys at once, but we don't want keys common to both
01966 //     branches appear twice, hence we check for each global key that it 
01967 //     doesn't appear locally at the end (very inefficient too).
01968 // ----------------------------------------------------------------------------
01969 
01970 BaseConfig::Enumerator *RegistryConfig::enumSubgroups() const
01971 {
01972   DWORD dwKeysLocal,   dwKeysGlobal,    // number of subkeys
01973         dwKeyLenLocal, dwKeyLenGlobal;  // length of the longest subkey
01974 
01975   long lRc = RegQueryInfoKey(m_hLocalCurKey, NULL, NULL, NULL, 
01976                              &dwKeysLocal, &dwKeyLenLocal, 
01977                              NULL, NULL, NULL, NULL, NULL, NULL);
01978 
01979   assert( lRc == ERROR_SUCCESS );  // it should never fail here
01980 
01981   if ( m_hGlobalCurKey == NULL ) {
01982     // key doesn't exist
01983     dwKeysGlobal = dwKeyLenGlobal = 0;
01984   }
01985   else {
01986     lRc = RegQueryInfoKey(m_hGlobalCurKey, NULL, NULL, NULL, &dwKeysGlobal, 
01987                           &dwKeyLenGlobal, NULL, NULL, NULL, NULL, NULL, NULL);
01988 
01989     assert( lRc == ERROR_SUCCESS );  // it should never fail here
01990   }
01991 
01992   DWORD dwKeys = dwKeysLocal + dwKeysGlobal;
01993   DWORD dwKeyLen = max(dwKeyLenLocal, dwKeyLenGlobal);
01994 
01995   BaseConfig::Enumerator *pEnum = new Enumerator((size_t)dwKeys, TRUE);
01996 
01997   char *szKey;
01998   HKEY hKey = m_hGlobalCurKey;
01999   for ( DWORD dwKey = 0; dwKeys > 0; dwKey++, dwKeys-- ) {
02000     if ( dwKeys == dwKeysLocal ) {
02001       // finished with global keys, now for local ones
02002       hKey = m_hLocalCurKey;
02003       dwKey = 0;
02004     }
02005 
02006     szKey = new char[dwKeyLen + 1];
02007     if ( RegEnumKey(hKey, dwKey, szKey, dwKeyLen + 1) != ERROR_SUCCESS ) {
02008       delete [] szKey;
02009       break;
02010     }
02011 
02012     pEnum->AddString(szKey);  // enumerator will delete it
02013   }
02014 
02015   pEnum->MakeUnique();
02016 
02017   return pEnum;
02018 }
02019 
02020 BaseConfig::Enumerator *RegistryConfig::enumEntries() const
02021 {
02022   DWORD dwEntriesLocal, dwEntriesGlobal,    // number of values
02023         dwEntryLenLocal, dwEntryLenGlobal;  // length of the longest value name
02024 
02025   long lRc = RegQueryInfoKey(m_hLocalCurKey, NULL, NULL, NULL, 
02026                              NULL, NULL, NULL, 
02027                              &dwEntriesLocal, &dwEntryLenLocal, 
02028                              NULL, NULL, NULL);
02029 
02030   assert( lRc == ERROR_SUCCESS );  // it should never fail here
02031 
02032   if ( m_hGlobalCurKey == NULL ) {
02033     // key doesn't exist
02034     dwEntriesGlobal = dwEntryLenGlobal = 0;
02035   }
02036   else {
02037     lRc = RegQueryInfoKey(m_hGlobalCurKey, 
02038                           NULL, NULL, NULL, 
02039                           NULL, NULL, NULL, 
02040                           &dwEntriesGlobal, 
02041                           &dwEntryLenGlobal, 
02042                           NULL, NULL, NULL);
02043 
02044     assert( lRc == ERROR_SUCCESS );  // it should never fail here
02045   }
02046 
02047   DWORD dwEntries = dwEntriesLocal + dwEntriesGlobal;
02048   DWORD dwEntryLen = max(dwEntryLenLocal, dwEntryLenGlobal);
02049 
02050   BaseConfig::Enumerator *pEnum = new Enumerator((size_t)dwEntries, TRUE);
02051 
02052   char *szVal;
02053   DWORD dwLen;
02054   HKEY  hKey = m_hGlobalCurKey;
02055   for ( DWORD dwEntry = 0; dwEntries > 0; dwEntry++, dwEntries-- ) {
02056     if ( dwEntries == dwEntriesLocal ) {
02057       // finished with global keys, now for local ones
02058       hKey = m_hLocalCurKey;
02059       dwEntry = 0;
02060     }
02061 
02062     dwLen = dwEntryLen + 1;
02063     szVal = new char [dwLen];
02064     lRc = RegEnumValue(hKey, dwEntry, szVal, &dwLen, 0, NULL, NULL, NULL);
02065     if ( lRc != ERROR_SUCCESS ) {
02066       delete [] szVal;
02067       break;
02068     }
02069 
02070     pEnum->AddString(szVal);    // enumerator will delete it
02071   }
02072 
02073   pEnum->MakeUnique();
02074 
02075   return pEnum;
02076 }
02077 
02078 // ----------------------------------------------------------------------------
02079 // change current key
02080 // ----------------------------------------------------------------------------
02081 
02082 // only in user's registry hive (not in global one)
02083 void RegistryConfig::changeCurrentPath(const char *szPath)
02084 {
02085   // normalize path
02086   BaseConfig::changeCurrentPath(szPath);
02087   szPath = getCurrentPath();
02088 
02089   // special case because RegCreateKey(hKey, "") fails!
02090   if ( *szPath == '\0' ) {
02091     if ( m_hLocalCurKey != m_hLocalRootKey ) {
02092       RegCloseKey(m_hLocalCurKey);
02093       m_hLocalCurKey = m_hLocalRootKey;
02094     }
02095 
02096     if ( m_hGlobalCurKey != m_hGlobalRootKey ) {
02097       RegCloseKey(m_hGlobalCurKey);
02098       m_hGlobalCurKey = m_hGlobalRootKey;
02099     }
02100 
02101     return;
02102   }
02103 
02104   char *szRegPath = new char[Strlen(szPath) + 1];
02105   strcpy(szRegPath, szPath);
02106 
02107   // replace all APPCONF_PATH_SEPARATORs with '\'
02108   char *pcSeparator = szRegPath;
02109   while ( pcSeparator != NULL ) {
02110     pcSeparator = strchr(pcSeparator, APPCONF_PATH_SEPARATOR);
02111     if ( pcSeparator != NULL )
02112       *pcSeparator = '\\';
02113   }
02114 
02115   HKEY hOldKey = m_hLocalCurKey;
02116 
02117   if ( RegCreateKey(m_hLocalRootKey, szRegPath, 
02118                     &m_hLocalCurKey) == ERROR_SUCCESS ) {
02119     // at any time, we keep open two keys
02120     // here we check that we don't close them
02121     if ( (m_hLocalCurKey != hOldKey) && (hOldKey != m_hLocalRootKey) )
02122       RegCloseKey(hOldKey);
02123   }
02124   else {
02125     LogWarning(_("can not open key '%s' (%s)."), szPath, SysError());
02126   }
02127 
02128   if ( m_hGlobalCurKey != 0 ) {
02129     hOldKey = m_hGlobalCurKey;
02130     if ( RegOpenKey(m_hGlobalRootKey, szRegPath, 
02131                     &m_hGlobalCurKey) == ERROR_SUCCESS ) {
02132       if ( (m_hGlobalCurKey != hOldKey) && (hOldKey != m_hGlobalRootKey) )
02133         RegCloseKey(hOldKey);
02134     }
02135     else {
02136       // no such key
02137       if ( m_hGlobalCurKey != m_hGlobalRootKey )
02138         RegCloseKey(m_hGlobalCurKey);
02139       m_hGlobalCurKey = NULL;
02140     }
02141   }
02142 
02143   delete [] szRegPath;
02144 }
02145 
02146 // ----------------------------------------------------------------------------
02147 // helper function: return TRUE if the key has no subkeys/values
02148 // ----------------------------------------------------------------------------
02149 Bool RegistryConfig::KeyIsEmpty(void *hKey)
02150 {
02151   DWORD dwKeys, dwValues;
02152   long lRc = RegQueryInfoKey(hKey, NULL, NULL, NULL, &dwKeys, NULL, NULL, 
02153                              &dwValues, NULL, NULL, NULL, NULL);
02154 
02155   return (lRc == ERROR_SUCCESS) && (dwValues == 0) && (dwKeys == 0);
02156 }
02157 
02158 #endif  // WIN32

Generated at Wed Apr 4 19:53:58 2001 for ClanLib by doxygen1.2.6 written by Dimitri van Heesch, © 1997-2001