/*
* config.cxx
*
* System/application configuration class implementation
*
* Portable Windows Library
*
* Copyright (c) 1993-1998 Equivalence Pty. Ltd.
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is Portable Windows Library.
*
* The Initial Developer of the Original Code is Equivalence Pty. Ltd.
*
* Portions are Copyright (C) 1993 Free Software Foundation, Inc.
* All Rights Reserved.
*
* Contributor(s): ______________________________________.
*
* $Log: config.cxx,v $
* Revision 1.36 2004/04/03 23:55:59 csoutheren
* Added fix for PConfig environment variables under Linux
* Thanks to Michal Zygmuntowicz
*
* Revision 1.35 2003/03/17 08:10:59 robertj
* Fixed bug with parsing lines with no equal sign
*
* Revision 1.34 2003/01/30 23:46:05 dereks
* Fix compile error on gcc 3.2
*
* Revision 1.33 2003/01/26 03:57:12 robertj
* Fixed problem with last change so can still operate if do not have write
* access to the directory config file is in.
* Improved error reporting.
*
* Revision 1.32 2003/01/24 12:12:20 robertj
* Changed so that a crash in some other thread can no longer cause the
* config file to be truncated to zero bytes.
*
* Revision 1.31 2001/09/14 07:36:27 robertj
* Fixed bug where fails to read last line of config file if not ended with '\n'.
*
* Revision 1.30 2001/06/30 06:59:07 yurik
* Jac Goudsmit from Be submit these changes 6/28. Implemented by Yuri Kiryanov
*
* Revision 1.29 2001/05/24 00:56:38 robertj
* Fixed problem with config file being written every time on exit. Now is
* only written if it the config was modified by the application.
*
* Revision 1.28 2001/03/10 04:15:29 robertj
* Incorrect case for .ini extension
*
* Revision 1.27 2001/03/09 06:31:22 robertj
* Added ability to set default PConfig file or path to find it.
*
* Revision 1.26 2000/10/19 04:17:04 craigs
* Changed to allow writing of config files whilst config file is open
*
* Revision 1.25 2000/10/02 20:58:06 robertj
* Fixed bug where subsequent config file opening uses first opened filename.
*
* Revision 1.24 2000/08/30 04:45:02 craigs
* Added ability to have multiple lines with the same key
*
* Revision 1.23 2000/08/16 04:21:27 robertj
* Fixed subtle difference between UNix and Win32 section names (ignore trailing backslash)
*
* Revision 1.22 2000/05/25 12:10:06 robertj
* Added PConfig::HasKey() function to determine if value actually set.
*
* Revision 1.21 2000/05/02 08:30:26 craigs
* Removed "memory leaks" caused by brain-dead GNU linker
*
* Revision 1.20 1998/12/16 12:40:41 robertj
* Fixed bug where .ini file is not written when service run as a daemon.
*
* Revision 1.19 1998/12/16 09:57:37 robertj
* Fixed bug in writing .ini file, not truncating file when shrinking.
*
* Revision 1.18 1998/11/30 21:51:41 robertj
* New directory structure.
*
* Revision 1.17 1998/11/03 02:30:38 robertj
* Fixed emeory leak of environment.
*
* Revision 1.16 1998/09/24 04:12:11 robertj
* Added open software license.
*
*/
#define _CONFIG_CXX
#pragma implementation "config.h"
#include <ptlib.h>
#include "../common/pconfig.cxx"
#define SYS_CONFIG_NAME "pwlib"
#define APP_CONFIG_DIR ".pwlib_config/"
#define SYS_CONFIG_DIR "/usr/local/pwlib/"
#define EXTENSION ".ini"
#define ENVIRONMENT_CONFIG_STR "/\~~environment~~\/"
//
// a single key/value pair
//
PDECLARE_CLASS (PXConfigValue, PCaselessString)
public:
PXConfigValue(const PString & theKey, const PString & theValue = "")
: PCaselessString(theKey), value(theValue) { }
PString GetValue() const { return value; }
void SetValue(const PString & theValue) { value = theValue; }
protected:
PString value;
};
//
// a list of key/value pairs
//
PLIST (PXConfigSectionList, PXConfigValue);
//
// a list of key value pairs, with a section name
//
PDECLARE_CLASS(PXConfigSection, PCaselessString)
public:
PXConfigSection(const PCaselessString & theName)
: PCaselessString(theName) { list.AllowDeleteObjects(); }
PXConfigSectionList & GetList() { return list; }
protected:
PXConfigSectionList list;
};
//
// a list of sections
//
PDECLARE_LIST(PXConfig, PXConfigSection)
public:
PXConfig(int i = 0);
void Wait() { mutex.Wait(); }
void Signal() { mutex.Signal(); }
BOOL ReadFromFile (const PFilePath & filename);
void ReadFromEnvironment (char **envp);
BOOL WriteToFile(const PFilePath & filename);
BOOL Flush(const PFilePath & filename);
void SetDirty() { dirty = TRUE; }
BOOL AddInstance();
BOOL RemoveInstance(const PFilePath & filename);
PINDEX GetSectionsIndex(const PString & theSection) const;
protected:
int instanceCount;
PMutex mutex;
BOOL dirty;
BOOL canSave;
};
//
// a dictionary of configurations, keyed by filename
//
PDECLARE_DICTIONARY(PXConfigDictionary, PFilePath, PXConfig)
public:
PXConfigDictionary(int dummy);
~PXConfigDictionary();
PXConfig * GetFileConfigInstance(const PFilePath & key, const PFilePath & readKey);
PXConfig * GetEnvironmentInstance();
void RemoveInstance(PXConfig * instance);
void WriteChangedInstances();
protected:
PMutex mutex;
PXConfig * environmentInstance;
PThread * writeThread;
PSyncPointAck stopConfigWriteThread;
};
PDECLARE_CLASS(PXConfigWriteThread, PThread)
public:
PXConfigWriteThread(PSyncPointAck & stop);
~PXConfigWriteThread();
void Main();
private:
PSyncPointAck & stop;
};
PXConfigDictionary * configDict;
#define new PNEW
//////////////////////////////////////////////////////
void PProcess::CreateConfigFilesDictionary()
{
configFiles = new PXConfigDictionary(0);
}
PXConfigWriteThread::PXConfigWriteThread(PSyncPointAck & s)
: PThread(10000, NoAutoDeleteThread, NormalPriority, "PXConfigWriteThread"),
stop(s)
{
Resume();
}
PXConfigWriteThread::~PXConfigWriteThread()
{
}
void PXConfigWriteThread::Main()
{
while (!stop.Wait(30000)) // if stop.Wait() returns TRUE, we are shutting down
configDict->WriteChangedInstances(); // check dictionary for items that need writing
configDict->WriteChangedInstances();
stop.Acknowledge();
}
PXConfig::PXConfig(int)
{
// make sure content gets removed
AllowDeleteObjects();
// no instances, initially
instanceCount = 0;
// we start off clean
dirty = FALSE;
// normally save on exit (except for environment configs)
canSave = TRUE;
}
BOOL PXConfig::AddInstance()
{
mutex.Wait();
BOOL stat = instanceCount++ == 0;
mutex.Signal();
return stat;
}
BOOL PXConfig::RemoveInstance(const PFilePath & /*filename*/)
{
mutex.Wait();
PAssert(instanceCount != 0, "PConfig instance count dec past zero");
BOOL stat = --instanceCount == 0;
mutex.Signal();
return stat;
}
BOOL PXConfig::Flush(const PFilePath & filename)
{
mutex.Wait();
BOOL stat = instanceCount == 0;
if (canSave && dirty) {
WriteToFile(filename);
dirty = FALSE;
}
mutex.Signal();
return stat;
}
BOOL PXConfig::WriteToFile(const PFilePath & filename)
{
// make sure the directory that the file is to be written into exists
PDirectory dir = filename.GetDirectory();
if (!dir.Exists() && !dir.Create(
PFileInfo::UserExecute |
PFileInfo::UserWrite |
PFileInfo::UserRead)) {
PProcess::PXShowSystemWarning(2000, "Cannot create PWLIB config directory");
return FALSE;
}
PTextFile file;
if (!file.Open(filename + ".new", PFile::WriteOnly))
file.Open(filename, PFile::WriteOnly);
if (!file.IsOpen()) {
PProcess::PXShowSystemWarning(2001, "Cannot create PWLIB config file: " + file.GetErrorText());
return FALSE;
}
for (PINDEX i = 0; i < GetSize(); i++) {
PXConfigSectionList & section = (*this)[i].GetList();
file << "[" << (*this)[i] << "]" << endl;
for (PINDEX j = 0; j < section.GetSize(); j++) {
PXConfigValue & value = section[j];
PStringArray lines = value.GetValue().Tokenise('\n', TRUE);
PINDEX k;
for (k = 0; k < lines.GetSize(); k++)
file << value << "=" << lines[k] << endl;
}
file << endl;
}
file.flush();
file.SetLength(file.GetPosition());
file.Close();
if (file.GetFilePath() != filename) {
if (!file.Rename(file.GetFilePath(), filename.GetFileName(), TRUE)) {
PProcess::PXShowSystemWarning(2001, "Cannot rename config file: " + file.GetErrorText());
return FALSE;
}
}
PTRACE(4, "PWLib\tSaved config file: " << filename);
return TRUE;
}
BOOL PXConfig::ReadFromFile(const PFilePath & filename)
{
PINDEX len;
// clear out all information
RemoveAll();
// attempt to open file
PTextFile file;
if (!file.Open(filename, PFile::ReadOnly))
return FALSE;
PXConfigSection * currentSection = NULL;
// read lines in the file
while (file.good()) {
PString line;
file >> line;
line = line.Trim();
if ((len = line.GetLength()) > 0) {
// ignore comments and blank lines
char ch = line[0];
if ((len > 0) && (ch != ';') && (ch != '#')) {
if (ch == '[') {
PCaselessString sectionName = (line.Mid(1,len-(line[len-1]==']'?2:1))).Trim();
PINDEX index;
if ((index = GetValuesIndex(sectionName)) != P_MAX_INDEX)
currentSection = &(*this )[index];
else {
currentSection = new PXConfigSection(sectionName);
Append(currentSection);
}
} else if (currentSection != NULL) {
PINDEX equals = line.Find('=');
if (equals > 0 && equals != P_MAX_INDEX) {
PString keyStr = line.Left(equals).Trim();
PString valStr = line.Right(len - equals - 1).Trim();
PINDEX index;
if ((index = currentSection->GetList().GetValuesIndex(keyStr)) != P_MAX_INDEX) {
PXConfigValue & value = currentSection->GetList()[index];
value.SetValue(value.GetValue() + '\n' + valStr);
} else {
PXConfigValue * value = new PXConfigValue(keyStr, valStr);
currentSection->GetList().Append(value);
}
}
}
}
}
}
// close the file and return
file.Close();
return TRUE;
}
void PXConfig::ReadFromEnvironment (char **envp)
{
// clear out all information
RemoveAll();
PXConfigSection * currentSection = new PXConfigSection("Options");
Append(currentSection);
while (*envp != NULL && **envp != '\0') {
PString line(*envp);
PINDEX equals = line.Find('=');
if (equals > 0) {
PXConfigValue * value = new PXConfigValue(line.Left(equals), line.Right(line.GetLength() - equals - 1));
currentSection->GetList().Append(value);
}
envp++;
}
// can't save environment configs
canSave = FALSE;
}
PINDEX PXConfig::GetSectionsIndex(const PString & theSection) const
{
PINDEX len = theSection.GetLength()-1;
if (theSection[len] != '\\')
return GetValuesIndex(theSection);
else
return GetValuesIndex(theSection.Left(len));
}
static BOOL LocateFile(const PString & baseName,
PFilePath & readFilename,
PFilePath & filename)
{
// check the user's home directory first
filename = readFilename = PProcess::Current().GetConfigurationFile();
if (PFile::Exists(filename))
return TRUE;
// otherwise check the system directory for a file to read,
// and then create
readFilename = SYS_CONFIG_DIR + baseName + EXTENSION;
return PFile::Exists(readFilename);
}
///////////////////////////////////////////////////////////////////////////////
PString PProcess::GetConfigurationFile()
{
if (configurationPaths.IsEmpty()) {
configurationPaths.AppendString(PXGetHomeDir() + APP_CONFIG_DIR);
configurationPaths.AppendString(SYS_CONFIG_DIR);
}
// See if explicit filename
if (configurationPaths.GetSize() == 1 && !PDirectory::Exists(configurationPaths[0]))
return configurationPaths[0];
PString iniFilename = executableFile.GetTitle() + ".ini";
for (PINDEX i = 0; i < configurationPaths.GetSize(); i++) {
PFilePath cfgFile = PDirectory(configurationPaths[i]) + iniFilename;
if (PFile::Exists(cfgFile))
return cfgFile;
}
return PDirectory(configurationPaths[0]) + iniFilename;
}
////////////////////////////////////////////////////////////
//
// PXConfigDictionary
//
PXConfigDictionary::PXConfigDictionary(int)
{
environmentInstance = NULL;
writeThread = NULL;
configDict = this;
}
PXConfigDictionary::~PXConfigDictionary()
{
if (writeThread != NULL) {
stopConfigWriteThread.Signal();
writeThread->WaitForTermination();
delete writeThread;
}
delete environmentInstance;
}
PXConfig * PXConfigDictionary::GetEnvironmentInstance()
{
mutex.Wait();
if (environmentInstance == NULL) {
environmentInstance = new PXConfig(0);
environmentInstance->ReadFromEnvironment(PProcess::Current().PXGetEnvp());
}
mutex.Signal();
return environmentInstance;
}
PXConfig * PXConfigDictionary::GetFileConfigInstance(const PFilePath & key, const PFilePath & readKey)
{
mutex.Wait();
// start write thread, if not already started
if (writeThread == NULL)
writeThread = new PXConfigWriteThread(stopConfigWriteThread);
PXConfig * config = GetAt(key);
if (config != NULL)
config->AddInstance();
else {
config = new PXConfig(0);
config->ReadFromFile(readKey);
config->AddInstance();
SetAt(key, config);
}
mutex.Signal();
return config;
}
void PXConfigDictionary::RemoveInstance(PXConfig * instance)
{
mutex.Wait();
if (instance != environmentInstance) {
PINDEX index = GetObjectsIndex(instance);
PAssert(index != P_MAX_INDEX, "Cannot find PXConfig instance to remove");
// decrement the instance count, but don't remove it yet
PFilePath key = GetKeyAt(index);
instance->RemoveInstance(key);
}
mutex.Signal();
}
void PXConfigDictionary::WriteChangedInstances()
{
mutex.Wait();
PINDEX i;
for (i = 0; i < GetSize(); i++) {
PFilePath key = GetKeyAt(i);
GetAt(key)->Flush(key);
}
mutex.Signal();
}
////////////////////////////////////////////////////////////
//
// PConfig::
//
// Create a new configuration object
//
////////////////////////////////////////////////////////////
void PConfig::Construct(Source src,
const PString & appname,
const PString & /*manuf*/)
{
// handle cnvironment configs differently
if (src == PConfig::Environment) {
config = configDict->GetEnvironmentInstance();
return;
}
PString name;
PFilePath filename, readFilename;
// look up file name to read, and write
if (src == PConfig::System)
LocateFile(SYS_CONFIG_NAME, readFilename, filename);
else
filename = readFilename = PProcess::Current().GetConfigurationFile();
// get, or create, the configuration
config = configDict->GetFileConfigInstance(filename, readFilename);
}
PConfig::PConfig(int, const PString & name)
: defaultSection("Options")
{
PFilePath readFilename, filename;
LocateFile(name, readFilename, filename);
config = configDict->GetFileConfigInstance(filename, readFilename);
}
void PConfig::Construct(const PFilePath & theFilename)
{
config = configDict->GetFileConfigInstance(theFilename, theFilename);
}
PConfig::~PConfig()
{
configDict->RemoveInstance(config);
}
////////////////////////////////////////////////////////////
//
// PConfig::
//
// Return a list of all the section names in the file.
//
////////////////////////////////////////////////////////////
PStringList PConfig::GetSections() const
{
PAssert(config != NULL, "config instance not set");
config->Wait();
PStringList list;
for (PINDEX i = 0; i < (*config).GetSize(); i++)
list.AppendString((*config)[i]);
config->Signal();
return list;
}
////////////////////////////////////////////////////////////
//
// PConfig::
//
// Return a list of all the keys in the section. If the section name is
// not specified then use the default section.
//
////////////////////////////////////////////////////////////
PStringList PConfig::GetKeys(const PString & theSection) const
{
PAssert(config != NULL, "config instance not set");
config->Wait();
PINDEX index;
PStringList list;
if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX) {
PXConfigSectionList & section = (*config)[index].GetList();
for (PINDEX i = 0; i < section.GetSize(); i++)
list.AppendString(section[i]);
}
config->Signal();
return list;
}
////////////////////////////////////////////////////////////
//
// PConfig::
//
// Delete all variables in the specified section. If the section name is
// not specified then use the default section.
//
////////////////////////////////////////////////////////////
void PConfig::DeleteSection(const PString & theSection)
{
PAssert(config != NULL, "config instance not set");
config->Wait();
PStringList list;
PINDEX index;
if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX) {
config->RemoveAt(index);
config->SetDirty();
}
config->Signal();
}
////////////////////////////////////////////////////////////
//
// PConfig::
//
// Delete the particular variable in the specified section.
//
////////////////////////////////////////////////////////////
void PConfig::DeleteKey(const PString & theSection, const PString & theKey)
{
PAssert(config != NULL, "config instance not set");
config->Wait();
PINDEX index;
if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX) {
PXConfigSectionList & section = (*config)[index].GetList();
PINDEX index_2;
if ((index_2 = section.GetValuesIndex(theKey)) != P_MAX_INDEX) {
section.RemoveAt(index_2);
config->SetDirty();
}
}
config->Signal();
}
////////////////////////////////////////////////////////////
//
// PConfig::
//
// Test if there is a value for the key.
//
////////////////////////////////////////////////////////////
BOOL PConfig::HasKey(const PString & theSection, const PString & theKey) const
{
PAssert(config != NULL, "config instance not set");
config->Wait();
BOOL present = FALSE;
PINDEX index;
if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX) {
PXConfigSectionList & section = (*config)[index].GetList();
present = section.GetValuesIndex(theKey) != P_MAX_INDEX;
}
config->Signal();
return present;
}
////////////////////////////////////////////////////////////
//
// PConfig::
//
// Get a string variable determined by the key in the section.
//
////////////////////////////////////////////////////////////
PString PConfig::GetString(const PString & theSection,
const PString & theKey, const PString & dflt) const
{
PAssert(config != NULL, "config instance not set");
config->Wait();
PString value = dflt;
PINDEX index;
if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX) {
PXConfigSectionList & section = (*config)[index].GetList();
if ((index = section.GetValuesIndex(theKey)) != P_MAX_INDEX)
value = section[index].GetValue();
}
config->Signal();
return value;
}
////////////////////////////////////////////////////////////
//
// PConfig::
//
// Set a string variable determined by the key in the section.
//
////////////////////////////////////////////////////////////
void PConfig::SetString(const PString & theSection,
const PString & theKey,
const PString & theValue)
{
PAssert(config != NULL, "config instance not set");
config->Wait();
PINDEX index;
PXConfigSection * section;
PXConfigValue * value;
if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX)
section = &(*config)[index];
else {
section = new PXConfigSection(theSection);
config->Append(section);
config->SetDirty();
}
if ((index = section->GetList().GetValuesIndex(theKey)) != P_MAX_INDEX)
value = &(section->GetList()[index]);
else {
value = new PXConfigValue(theKey);
section->GetList().Append(value);
config->SetDirty();
}
if (theValue != value->GetValue()) {
value->SetValue(theValue);
config->SetDirty();
}
config->Signal();
}
///////////////////////////////////////////////////////////////////////////////
syntax highlighted by Code2HTML, v. 0.9.1