4 * System/application configuration class implementation
6 * Portable Windows Library
8 * Copyright (c) 1993-1998 Equivalence Pty. Ltd.
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
24 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
25 * All Rights Reserved.
27 * Contributor(s): ______________________________________.
30 * Revision 1.37 2006/06/21 03:28:44 csoutheren
31 * Various cleanups thanks for Frederic Heem
33 * Revision 1.36 2004/04/03 23:55:59 csoutheren
34 * Added fix for PConfig environment variables under Linux
35 * Thanks to Michal Zygmuntowicz
37 * Revision 1.35 2003/03/17 08:10:59 robertj
38 * Fixed bug with parsing lines with no equal sign
40 * Revision 1.34 2003/01/30 23:46:05 dereks
41 * Fix compile error on gcc 3.2
43 * Revision 1.33 2003/01/26 03:57:12 robertj
44 * Fixed problem with last change so can still operate if do not have write
45 * access to the directory config file is in.
46 * Improved error reporting.
48 * Revision 1.32 2003/01/24 12:12:20 robertj
49 * Changed so that a crash in some other thread can no longer cause the
50 * config file to be truncated to zero bytes.
52 * Revision 1.31 2001/09/14 07:36:27 robertj
53 * Fixed bug where fails to read last line of config file if not ended with '\n'.
55 * Revision 1.30 2001/06/30 06:59:07 yurik
56 * Jac Goudsmit from Be submit these changes 6/28. Implemented by Yuri Kiryanov
58 * Revision 1.29 2001/05/24 00:56:38 robertj
59 * Fixed problem with config file being written every time on exit. Now is
60 * only written if it the config was modified by the application.
62 * Revision 1.28 2001/03/10 04:15:29 robertj
63 * Incorrect case for .ini extension
65 * Revision 1.27 2001/03/09 06:31:22 robertj
66 * Added ability to set default PConfig file or path to find it.
68 * Revision 1.26 2000/10/19 04:17:04 craigs
69 * Changed to allow writing of config files whilst config file is open
71 * Revision 1.25 2000/10/02 20:58:06 robertj
72 * Fixed bug where subsequent config file opening uses first opened filename.
74 * Revision 1.24 2000/08/30 04:45:02 craigs
75 * Added ability to have multiple lines with the same key
77 * Revision 1.23 2000/08/16 04:21:27 robertj
78 * Fixed subtle difference between UNix and Win32 section names (ignore trailing backslash)
80 * Revision 1.22 2000/05/25 12:10:06 robertj
81 * Added PConfig::HasKey() function to determine if value actually set.
83 * Revision 1.21 2000/05/02 08:30:26 craigs
84 * Removed "memory leaks" caused by brain-dead GNU linker
86 * Revision 1.20 1998/12/16 12:40:41 robertj
87 * Fixed bug where .ini file is not written when service run as a daemon.
89 * Revision 1.19 1998/12/16 09:57:37 robertj
90 * Fixed bug in writing .ini file, not truncating file when shrinking.
92 * Revision 1.18 1998/11/30 21:51:41 robertj
93 * New directory structure.
95 * Revision 1.17 1998/11/03 02:30:38 robertj
96 * Fixed emeory leak of environment.
98 * Revision 1.16 1998/09/24 04:12:11 robertj
99 * Added open software license.
105 #pragma implementation "config.h"
108 #include <ptlib/pprocess.h>
110 #include "../common/pconfig.cxx"
113 #define SYS_CONFIG_NAME "pwlib"
115 #define APP_CONFIG_DIR ".pwlib_config/"
116 #define SYS_CONFIG_DIR "/usr/local/pwlib/"
118 #define EXTENSION ".ini"
119 #define ENVIRONMENT_CONFIG_STR "/\~~environment~~\/"
122 // a single key/value pair
124 PDECLARE_CLASS (PXConfigValue
, PCaselessString
)
126 PXConfigValue(const PString
& theKey
, const PString
& theValue
= "")
127 : PCaselessString(theKey
), value(theValue
) { }
128 PString
GetValue() const { return value
; }
129 void SetValue(const PString
& theValue
) { value
= theValue
; }
136 // a list of key/value pairs
138 PLIST (PXConfigSectionList
, PXConfigValue
);
141 // a list of key value pairs, with a section name
143 PDECLARE_CLASS(PXConfigSection
, PCaselessString
)
145 PXConfigSection(const PCaselessString
& theName
)
146 : PCaselessString(theName
) { list
.AllowDeleteObjects(); }
148 PXConfigSectionList
& GetList() { return list
; }
151 PXConfigSectionList list
;
155 // a list of sections
158 PDECLARE_LIST(PXConfig
, PXConfigSection
)
162 void Wait() { mutex
.Wait(); }
163 void Signal() { mutex
.Signal(); }
165 BOOL
ReadFromFile (const PFilePath
& filename
);
166 void ReadFromEnvironment (char **envp
);
168 BOOL
WriteToFile(const PFilePath
& filename
);
169 BOOL
Flush(const PFilePath
& filename
);
171 void SetDirty() { dirty
= TRUE
; }
174 BOOL
RemoveInstance(const PFilePath
& filename
);
176 PINDEX
GetSectionsIndex(const PString
& theSection
) const;
186 // a dictionary of configurations, keyed by filename
188 PDECLARE_DICTIONARY(PXConfigDictionary
, PFilePath
, PXConfig
)
190 PXConfigDictionary(int dummy
);
191 ~PXConfigDictionary();
192 PXConfig
* GetFileConfigInstance(const PFilePath
& key
, const PFilePath
& readKey
);
193 PXConfig
* GetEnvironmentInstance();
194 void RemoveInstance(PXConfig
* instance
);
195 void WriteChangedInstances();
199 PXConfig
* environmentInstance
;
200 PThread
* writeThread
;
201 PSyncPointAck stopConfigWriteThread
;
205 PDECLARE_CLASS(PXConfigWriteThread
, PThread
)
207 PXConfigWriteThread(PSyncPointAck
& stop
);
208 ~PXConfigWriteThread();
211 PSyncPointAck
& stop
;
215 PXConfigDictionary
* configDict
;
219 //////////////////////////////////////////////////////
221 void PProcess::CreateConfigFilesDictionary()
223 configFiles
= new PXConfigDictionary(0);
227 PXConfigWriteThread::PXConfigWriteThread(PSyncPointAck
& s
)
228 : PThread(10000, NoAutoDeleteThread
, NormalPriority
, "PXConfigWriteThread"),
234 PXConfigWriteThread::~PXConfigWriteThread()
238 void PXConfigWriteThread::Main()
240 while (!stop
.Wait(30000)) // if stop.Wait() returns TRUE, we are shutting down
241 configDict
->WriteChangedInstances(); // check dictionary for items that need writing
243 configDict
->WriteChangedInstances();
250 PXConfig::PXConfig(int)
252 // make sure content gets removed
253 AllowDeleteObjects();
255 // no instances, initially
258 // we start off clean
261 // normally save on exit (except for environment configs)
265 BOOL
PXConfig::AddInstance()
268 BOOL stat
= instanceCount
++ == 0;
274 BOOL
PXConfig::RemoveInstance(const PFilePath
& /*filename*/)
278 PAssert(instanceCount
!= 0, "PConfig instance count dec past zero");
280 BOOL stat
= --instanceCount
== 0;
287 BOOL
PXConfig::Flush(const PFilePath
& filename
)
291 BOOL stat
= instanceCount
== 0;
293 if (canSave
&& dirty
) {
294 WriteToFile(filename
);
303 BOOL
PXConfig::WriteToFile(const PFilePath
& filename
)
305 // make sure the directory that the file is to be written into exists
306 PDirectory dir
= filename
.GetDirectory();
307 if (!dir
.Exists() && !dir
.Create(
308 PFileInfo::UserExecute
|
309 PFileInfo::UserWrite
|
310 PFileInfo::UserRead
)) {
311 PProcess::PXShowSystemWarning(2000, "Cannot create PWLIB config directory");
316 if (!file
.Open(filename
+ ".new", PFile::WriteOnly
))
317 file
.Open(filename
, PFile::WriteOnly
);
319 if (!file
.IsOpen()) {
320 PProcess::PXShowSystemWarning(2001, "Cannot create PWLIB config file: " + file
.GetErrorText());
324 for (PINDEX i
= 0; i
< GetSize(); i
++) {
325 PXConfigSectionList
& section
= (*this)[i
].GetList();
326 file
<< "[" << (*this)[i
] << "]" << endl
;
327 for (PINDEX j
= 0; j
< section
.GetSize(); j
++) {
328 PXConfigValue
& value
= section
[j
];
329 PStringArray lines
= value
.GetValue().Tokenise('\n', TRUE
);
331 for (k
= 0; k
< lines
.GetSize(); k
++)
332 file
<< value
<< "=" << lines
[k
] << endl
;
338 file
.SetLength(file
.GetPosition());
341 if (file
.GetFilePath() != filename
) {
342 if (!file
.Rename(file
.GetFilePath(), filename
.GetFileName(), TRUE
)) {
343 PProcess::PXShowSystemWarning(2001, "Cannot rename config file: " + file
.GetErrorText());
348 PTRACE(4, "PWLib\tSaved config file: " << filename
);
353 BOOL
PXConfig::ReadFromFile(const PFilePath
& filename
)
357 // clear out all information
360 // attempt to open file
362 if (!file
.Open(filename
, PFile::ReadOnly
))
365 PXConfigSection
* currentSection
= NULL
;
367 // read lines in the file
368 while (file
.good()) {
372 if ((len
= line
.GetLength()) > 0) {
374 // ignore comments and blank lines
376 if ((len
> 0) && (ch
!= ';') && (ch
!= '#')) {
378 PCaselessString sectionName
= (line
.Mid(1,len
-(line
[len
-1]==']'?2:1))).Trim();
380 if ((index
= GetValuesIndex(sectionName
)) != P_MAX_INDEX
)
381 currentSection
= &(*this )[index
];
383 currentSection
= new PXConfigSection(sectionName
);
384 Append(currentSection
);
386 } else if (currentSection
!= NULL
) {
387 PINDEX equals
= line
.Find('=');
388 if (equals
> 0 && equals
!= P_MAX_INDEX
) {
389 PString keyStr
= line
.Left(equals
).Trim();
390 PString valStr
= line
.Right(len
- equals
- 1).Trim();
393 if ((index
= currentSection
->GetList().GetValuesIndex(keyStr
)) != P_MAX_INDEX
) {
394 PXConfigValue
& value
= currentSection
->GetList()[index
];
395 value
.SetValue(value
.GetValue() + '\n' + valStr
);
397 PXConfigValue
* value
= new PXConfigValue(keyStr
, valStr
);
398 currentSection
->GetList().Append(value
);
406 // close the file and return
411 void PXConfig::ReadFromEnvironment (char **envp
)
413 // clear out all information
416 PXConfigSection
* currentSection
= new PXConfigSection("Options");
417 Append(currentSection
);
419 while (*envp
!= NULL
&& **envp
!= '\0') {
421 PINDEX equals
= line
.Find('=');
423 PXConfigValue
* value
= new PXConfigValue(line
.Left(equals
), line
.Right(line
.GetLength() - equals
- 1));
424 currentSection
->GetList().Append(value
);
429 // can't save environment configs
433 PINDEX
PXConfig::GetSectionsIndex(const PString
& theSection
) const
435 PINDEX len
= theSection
.GetLength()-1;
436 if (theSection
[len
] != '\\')
437 return GetValuesIndex(theSection
);
439 return GetValuesIndex(theSection
.Left(len
));
443 static BOOL
LocateFile(const PString
& baseName
,
444 PFilePath
& readFilename
,
445 PFilePath
& filename
)
447 // check the user's home directory first
448 filename
= readFilename
= PProcess::Current().GetConfigurationFile();
449 if (PFile::Exists(filename
))
452 // otherwise check the system directory for a file to read,
454 readFilename
= SYS_CONFIG_DIR
+ baseName
+ EXTENSION
;
455 return PFile::Exists(readFilename
);
458 ///////////////////////////////////////////////////////////////////////////////
460 PString
PProcess::GetConfigurationFile()
462 if (configurationPaths
.IsEmpty()) {
463 configurationPaths
.AppendString(PXGetHomeDir() + APP_CONFIG_DIR
);
464 configurationPaths
.AppendString(SYS_CONFIG_DIR
);
467 // See if explicit filename
468 if (configurationPaths
.GetSize() == 1 && !PDirectory::Exists(configurationPaths
[0]))
469 return configurationPaths
[0];
471 PString iniFilename
= executableFile
.GetTitle() + ".ini";
473 for (PINDEX i
= 0; i
< configurationPaths
.GetSize(); i
++) {
474 PFilePath cfgFile
= PDirectory(configurationPaths
[i
]) + iniFilename
;
475 if (PFile::Exists(cfgFile
))
479 return PDirectory(configurationPaths
[0]) + iniFilename
;
483 ////////////////////////////////////////////////////////////
485 // PXConfigDictionary
488 PXConfigDictionary::PXConfigDictionary(int)
490 environmentInstance
= NULL
;
496 PXConfigDictionary::~PXConfigDictionary()
498 if (writeThread
!= NULL
) {
499 stopConfigWriteThread
.Signal();
500 writeThread
->WaitForTermination();
503 delete environmentInstance
;
507 PXConfig
* PXConfigDictionary::GetEnvironmentInstance()
510 if (environmentInstance
== NULL
) {
511 environmentInstance
= new PXConfig(0);
512 environmentInstance
->ReadFromEnvironment(PProcess::Current().PXGetEnvp());
515 return environmentInstance
;
519 PXConfig
* PXConfigDictionary::GetFileConfigInstance(const PFilePath
& key
, const PFilePath
& readKey
)
523 // start write thread, if not already started
524 if (writeThread
== NULL
)
525 writeThread
= new PXConfigWriteThread(stopConfigWriteThread
);
527 PXConfig
* config
= GetAt(key
);
529 config
->AddInstance();
531 config
= new PXConfig(0);
532 config
->ReadFromFile(readKey
);
533 config
->AddInstance();
541 void PXConfigDictionary::RemoveInstance(PXConfig
* instance
)
545 if (instance
!= environmentInstance
) {
546 PINDEX index
= GetObjectsIndex(instance
);
547 PAssert(index
!= P_MAX_INDEX
, "Cannot find PXConfig instance to remove");
549 // decrement the instance count, but don't remove it yet
550 PFilePath key
= GetKeyAt(index
);
551 instance
->RemoveInstance(key
);
557 void PXConfigDictionary::WriteChangedInstances()
562 for (i
= 0; i
< GetSize(); i
++) {
563 PFilePath key
= GetKeyAt(i
);
564 GetAt(key
)->Flush(key
);
570 ////////////////////////////////////////////////////////////
574 // Create a new configuration object
576 ////////////////////////////////////////////////////////////
578 void PConfig::Construct(Source src
,
579 const PString
& appname
,
580 const PString
& /*manuf*/)
582 // handle cnvironment configs differently
583 if (src
== PConfig::Environment
) {
584 config
= configDict
->GetEnvironmentInstance();
589 PFilePath filename
, readFilename
;
591 // look up file name to read, and write
592 if (src
== PConfig::System
)
593 LocateFile(SYS_CONFIG_NAME
, readFilename
, filename
);
595 filename
= readFilename
= PProcess::Current().GetConfigurationFile();
597 // get, or create, the configuration
598 config
= configDict
->GetFileConfigInstance(filename
, readFilename
);
601 PConfig::PConfig(int, const PString
& name
)
602 : defaultSection("Options")
604 PFilePath readFilename
, filename
;
605 LocateFile(name
, readFilename
, filename
);
606 config
= configDict
->GetFileConfigInstance(filename
, readFilename
);
609 void PConfig::Construct(const PFilePath
& theFilename
)
612 config
= configDict
->GetFileConfigInstance(theFilename
, theFilename
);
618 configDict
->RemoveInstance(config
);
621 ////////////////////////////////////////////////////////////
625 // Return a list of all the section names in the file.
627 ////////////////////////////////////////////////////////////
629 PStringList
PConfig::GetSections() const
631 PAssert(config
!= NULL
, "config instance not set");
636 for (PINDEX i
= 0; i
< (*config
).GetSize(); i
++)
637 list
.AppendString((*config
)[i
]);
645 ////////////////////////////////////////////////////////////
649 // Return a list of all the keys in the section. If the section name is
650 // not specified then use the default section.
652 ////////////////////////////////////////////////////////////
654 PStringList
PConfig::GetKeys(const PString
& theSection
) const
656 PAssert(config
!= NULL
, "config instance not set");
662 if ((index
= config
->GetSectionsIndex(theSection
)) != P_MAX_INDEX
) {
663 PXConfigSectionList
& section
= (*config
)[index
].GetList();
664 for (PINDEX i
= 0; i
< section
.GetSize(); i
++)
665 list
.AppendString(section
[i
]);
674 ////////////////////////////////////////////////////////////
678 // Delete all variables in the specified section. If the section name is
679 // not specified then use the default section.
681 ////////////////////////////////////////////////////////////
683 void PConfig::DeleteSection(const PString
& theSection
)
686 PAssert(config
!= NULL
, "config instance not set");
692 if ((index
= config
->GetSectionsIndex(theSection
)) != P_MAX_INDEX
) {
693 config
->RemoveAt(index
);
701 ////////////////////////////////////////////////////////////
705 // Delete the particular variable in the specified section.
707 ////////////////////////////////////////////////////////////
709 void PConfig::DeleteKey(const PString
& theSection
, const PString
& theKey
)
711 PAssert(config
!= NULL
, "config instance not set");
715 if ((index
= config
->GetSectionsIndex(theSection
)) != P_MAX_INDEX
) {
716 PXConfigSectionList
& section
= (*config
)[index
].GetList();
718 if ((index_2
= section
.GetValuesIndex(theKey
)) != P_MAX_INDEX
) {
719 section
.RemoveAt(index_2
);
729 ////////////////////////////////////////////////////////////
733 // Test if there is a value for the key.
735 ////////////////////////////////////////////////////////////
737 BOOL
PConfig::HasKey(const PString
& theSection
, const PString
& theKey
) const
739 PAssert(config
!= NULL
, "config instance not set");
742 BOOL present
= FALSE
;
744 if ((index
= config
->GetSectionsIndex(theSection
)) != P_MAX_INDEX
) {
745 PXConfigSectionList
& section
= (*config
)[index
].GetList();
746 present
= section
.GetValuesIndex(theKey
) != P_MAX_INDEX
;
755 ////////////////////////////////////////////////////////////
759 // Get a string variable determined by the key in the section.
761 ////////////////////////////////////////////////////////////
763 PString
PConfig::GetString(const PString
& theSection
,
764 const PString
& theKey
, const PString
& dflt
) const
766 PAssert(config
!= NULL
, "config instance not set");
769 PString value
= dflt
;
771 if ((index
= config
->GetSectionsIndex(theSection
)) != P_MAX_INDEX
) {
773 PXConfigSectionList
& section
= (*config
)[index
].GetList();
774 if ((index
= section
.GetValuesIndex(theKey
)) != P_MAX_INDEX
)
775 value
= section
[index
].GetValue();
783 ////////////////////////////////////////////////////////////
787 // Set a string variable determined by the key in the section.
789 ////////////////////////////////////////////////////////////
791 void PConfig::SetString(const PString
& theSection
,
792 const PString
& theKey
,
793 const PString
& theValue
)
795 PAssert(config
!= NULL
, "config instance not set");
799 PXConfigSection
* section
;
800 PXConfigValue
* value
;
802 if ((index
= config
->GetSectionsIndex(theSection
)) != P_MAX_INDEX
)
803 section
= &(*config
)[index
];
805 section
= new PXConfigSection(theSection
);
806 config
->Append(section
);
810 if ((index
= section
->GetList().GetValuesIndex(theKey
)) != P_MAX_INDEX
)
811 value
= &(section
->GetList()[index
]);
813 value
= new PXConfigValue(theKey
);
814 section
->GetList().Append(value
);
818 if (theValue
!= value
->GetValue()) {
819 value
->SetValue(theValue
);
827 ///////////////////////////////////////////////////////////////////////////////