/*
 * 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