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.35 2003/03/17 08:10:59 robertj
31 * Fixed bug with parsing lines with no equal sign
33 * Revision 1.34 2003/01/30 23:46:05 dereks
34 * Fix compile error on gcc 3.2
36 * Revision 1.33 2003/01/26 03:57:12 robertj
37 * Fixed problem with last change so can still operate if do not have write
38 * access to the directory config file is in.
39 * Improved error reporting.
41 * Revision 1.32 2003/01/24 12:12:20 robertj
42 * Changed so that a crash in some other thread can no longer cause the
43 * config file to be truncated to zero bytes.
45 * Revision 1.31 2001/09/14 07:36:27 robertj
46 * Fixed bug where fails to read last line of config file if not ended with '\n'.
48 * Revision 1.30 2001/06/30 06:59:07 yurik
49 * Jac Goudsmit from Be submit these changes 6/28. Implemented by Yuri Kiryanov
51 * Revision 1.29 2001/05/24 00:56:38 robertj
52 * Fixed problem with config file being written every time on exit. Now is
53 * only written if it the config was modified by the application.
55 * Revision 1.28 2001/03/10 04:15:29 robertj
56 * Incorrect case for .ini extension
58 * Revision 1.27 2001/03/09 06:31:22 robertj
59 * Added ability to set default PConfig file or path to find it.
61 * Revision 1.26 2000/10/19 04:17:04 craigs
62 * Changed to allow writing of config files whilst config file is open
64 * Revision 1.25 2000/10/02 20:58:06 robertj
65 * Fixed bug where subsequent config file opening uses first opened filename.
67 * Revision 1.24 2000/08/30 04:45:02 craigs
68 * Added ability to have multiple lines with the same key
70 * Revision 1.23 2000/08/16 04:21:27 robertj
71 * Fixed subtle difference between UNix and Win32 section names (ignore trailing backslash)
73 * Revision 1.22 2000/05/25 12:10:06 robertj
74 * Added PConfig::HasKey() function to determine if value actually set.
76 * Revision 1.21 2000/05/02 08:30:26 craigs
77 * Removed "memory leaks" caused by brain-dead GNU linker
79 * Revision 1.20 1998/12/16 12:40:41 robertj
80 * Fixed bug where .ini file is not written when service run as a daemon.
82 * Revision 1.19 1998/12/16 09:57:37 robertj
83 * Fixed bug in writing .ini file, not truncating file when shrinking.
85 * Revision 1.18 1998/11/30 21:51:41 robertj
86 * New directory structure.
88 * Revision 1.17 1998/11/03 02:30:38 robertj
89 * Fixed emeory leak of environment.
91 * Revision 1.16 1998/09/24 04:12:11 robertj
92 * Added open software license.
98 #pragma implementation "config.h"
103 #include "../common/pconfig.cxx"
106 #define SYS_CONFIG_NAME "pwlib"
108 #define APP_CONFIG_DIR ".pwlib_config/"
109 #define SYS_CONFIG_DIR "/usr/local/pwlib/"
111 #define EXTENSION ".ini"
112 #define ENVIRONMENT_CONFIG_STR "/\~~environment~~\/"
115 // a single key/value pair
117 PDECLARE_CLASS (PXConfigValue
, PCaselessString
)
119 PXConfigValue(const PString
& theKey
, const PString
& theValue
= "")
120 : PCaselessString(theKey
), value(theValue
) { }
121 PString
GetValue() const { return value
; }
122 void SetValue(const PString
& theValue
) { value
= theValue
; }
129 // a list of key/value pairs
131 PLIST (PXConfigSectionList
, PXConfigValue
);
134 // a list of key value pairs, with a section name
136 PDECLARE_CLASS(PXConfigSection
, PCaselessString
)
138 PXConfigSection(const PCaselessString
& theName
)
139 : PCaselessString(theName
) { list
.AllowDeleteObjects(); }
141 PXConfigSectionList
& GetList() { return list
; }
144 PXConfigSectionList list
;
148 // a list of sections
151 PDECLARE_LIST(PXConfig
, PXConfigSection
)
155 void Wait() { mutex
.Wait(); }
156 void Signal() { mutex
.Signal(); }
158 BOOL
ReadFromFile (const PFilePath
& filename
);
159 void ReadFromEnvironment (char **envp
);
161 BOOL
WriteToFile(const PFilePath
& filename
);
162 BOOL
Flush(const PFilePath
& filename
);
164 void SetDirty() { dirty
= TRUE
; }
167 BOOL
RemoveInstance(const PFilePath
& filename
);
169 PINDEX
GetSectionsIndex(const PString
& theSection
) const;
179 // a dictionary of configurations, keyed by filename
181 PDECLARE_DICTIONARY(PXConfigDictionary
, PFilePath
, PXConfig
)
183 PXConfigDictionary(int dummy
);
184 ~PXConfigDictionary();
185 PXConfig
* GetFileConfigInstance(const PFilePath
& key
, const PFilePath
& readKey
);
186 PXConfig
* GetEnvironmentInstance();
187 void RemoveInstance(PXConfig
* instance
);
188 void WriteChangedInstances();
192 PXConfig
* environmentInstance
;
193 PThread
* writeThread
;
194 PSyncPointAck stopConfigWriteThread
;
198 PDECLARE_CLASS(PXConfigWriteThread
, PThread
)
200 PXConfigWriteThread(PSyncPointAck
& stop
);
201 ~PXConfigWriteThread();
204 PSyncPointAck
& stop
;
208 PXConfigDictionary
* configDict
;
212 //////////////////////////////////////////////////////
214 void PProcess::CreateConfigFilesDictionary()
216 configFiles
= new PXConfigDictionary(0);
220 PXConfigWriteThread::PXConfigWriteThread(PSyncPointAck
& s
)
221 : PThread(10000, NoAutoDeleteThread
, NormalPriority
, "PXConfigWriteThread"),
227 PXConfigWriteThread::~PXConfigWriteThread()
231 void PXConfigWriteThread::Main()
233 while (!stop
.Wait(30000)) // if stop.Wait() returns TRUE, we are shutting down
234 configDict
->WriteChangedInstances(); // check dictionary for items that need writing
236 configDict
->WriteChangedInstances();
243 PXConfig::PXConfig(int)
245 // make sure content gets removed
246 AllowDeleteObjects();
248 // no instances, initially
251 // we start off clean
254 // normally save on exit (except for environment configs)
258 BOOL
PXConfig::AddInstance()
261 BOOL stat
= instanceCount
++ == 0;
267 BOOL
PXConfig::RemoveInstance(const PFilePath
& /*filename*/)
271 PAssert(instanceCount
!= 0, "PConfig instance count dec past zero");
273 BOOL stat
= --instanceCount
== 0;
280 BOOL
PXConfig::Flush(const PFilePath
& filename
)
284 BOOL stat
= instanceCount
== 0;
286 if (canSave
&& dirty
) {
287 WriteToFile(filename
);
296 BOOL
PXConfig::WriteToFile(const PFilePath
& filename
)
298 // make sure the directory that the file is to be written into exists
299 PDirectory dir
= filename
.GetDirectory();
300 if (!dir
.Exists() && !dir
.Create(
301 PFileInfo::UserExecute
|
302 PFileInfo::UserWrite
|
303 PFileInfo::UserRead
)) {
304 PProcess::PXShowSystemWarning(2000, "Cannot create PWLIB config directory");
309 if (!file
.Open(filename
+ ".new", PFile::WriteOnly
))
310 file
.Open(filename
, PFile::WriteOnly
);
312 if (!file
.IsOpen()) {
313 PProcess::PXShowSystemWarning(2001, "Cannot create PWLIB config file: " + file
.GetErrorText());
317 for (PINDEX i
= 0; i
< GetSize(); i
++) {
318 PXConfigSectionList
& section
= (*this)[i
].GetList();
319 file
<< "[" << (*this)[i
] << "]" << endl
;
320 for (PINDEX j
= 0; j
< section
.GetSize(); j
++) {
321 PXConfigValue
& value
= section
[j
];
322 PStringArray lines
= value
.GetValue().Tokenise('\n', TRUE
);
324 for (k
= 0; k
< lines
.GetSize(); k
++)
325 file
<< value
<< "=" << lines
[k
] << endl
;
331 file
.SetLength(file
.GetPosition());
334 if (file
.GetFilePath() != filename
) {
335 if (!file
.Rename(file
.GetFilePath(), filename
.GetFileName(), TRUE
)) {
336 PProcess::PXShowSystemWarning(2001, "Cannot rename config file: " + file
.GetErrorText());
341 PTRACE(4, "PWLib\tSaved config file: " << filename
);
346 BOOL
PXConfig::ReadFromFile(const PFilePath
& filename
)
350 // clear out all information
353 // attempt to open file
355 if (!file
.Open(filename
, PFile::ReadOnly
))
358 PXConfigSection
* currentSection
= NULL
;
360 // read lines in the file
361 while (file
.good()) {
365 if ((len
= line
.GetLength()) > 0) {
367 // ignore comments and blank lines
369 if ((len
> 0) && (ch
!= ';') && (ch
!= '#')) {
371 PCaselessString sectionName
= (line
.Mid(1,len
-(line
[len
-1]==']'?2:1))).Trim();
373 if ((index
= GetValuesIndex(sectionName
)) != P_MAX_INDEX
)
374 currentSection
= &(*this )[index
];
376 currentSection
= new PXConfigSection(sectionName
);
377 Append(currentSection
);
379 } else if (currentSection
!= NULL
) {
380 PINDEX equals
= line
.Find('=');
381 if (equals
> 0 && equals
!= P_MAX_INDEX
) {
382 PString keyStr
= line
.Left(equals
).Trim();
383 PString valStr
= line
.Right(len
- equals
- 1).Trim();
386 if ((index
= currentSection
->GetList().GetValuesIndex(keyStr
)) != P_MAX_INDEX
) {
387 PXConfigValue
& value
= currentSection
->GetList()[index
];
388 value
.SetValue(value
.GetValue() + '\n' + valStr
);
390 PXConfigValue
* value
= new PXConfigValue(keyStr
, valStr
);
391 currentSection
->GetList().Append(value
);
399 // close the file and return
404 void PXConfig::ReadFromEnvironment (char **envp
)
406 // clear out all information
409 PXConfigSection
* currentSection
= new PXConfigSection("Options");
410 Append(currentSection
);
412 while (*envp
!= NULL
&& **envp
!= '\0') {
414 PINDEX equals
= line
.Find('=');
416 PXConfigValue
* value
= new PXConfigValue(line
.Left(equals
), line
.Right(line
.GetLength() - equals
- 1));
417 currentSection
->GetList().Append(value
);
422 // can't save environment configs
426 PINDEX
PXConfig::GetSectionsIndex(const PString
& theSection
) const
428 PINDEX len
= theSection
.GetLength()-1;
429 if (theSection
[len
] != '\\')
430 return GetValuesIndex(theSection
);
432 return GetValuesIndex(theSection
.Left(len
));
436 static BOOL
LocateFile(const PString
& baseName
,
437 PFilePath
& readFilename
,
438 PFilePath
& filename
)
440 // check the user's home directory first
441 filename
= readFilename
= PProcess::Current().GetConfigurationFile();
442 if (PFile::Exists(filename
))
445 // otherwise check the system directory for a file to read,
447 readFilename
= SYS_CONFIG_DIR
+ baseName
+ EXTENSION
;
448 return PFile::Exists(readFilename
);
451 ///////////////////////////////////////////////////////////////////////////////
453 PString
PProcess::GetConfigurationFile()
455 if (configurationPaths
.IsEmpty()) {
456 configurationPaths
.AppendString(PXGetHomeDir() + APP_CONFIG_DIR
);
457 configurationPaths
.AppendString(SYS_CONFIG_DIR
);
460 // See if explicit filename
461 if (configurationPaths
.GetSize() == 1 && !PDirectory::Exists(configurationPaths
[0]))
462 return configurationPaths
[0];
464 PString iniFilename
= executableFile
.GetTitle() + ".ini";
466 for (PINDEX i
= 0; i
< configurationPaths
.GetSize(); i
++) {
467 PFilePath cfgFile
= PDirectory(configurationPaths
[i
]) + iniFilename
;
468 if (PFile::Exists(cfgFile
))
472 return PDirectory(configurationPaths
[0]) + iniFilename
;
476 ////////////////////////////////////////////////////////////
478 // PXConfigDictionary
481 PXConfigDictionary::PXConfigDictionary(int)
483 environmentInstance
= NULL
;
489 PXConfigDictionary::~PXConfigDictionary()
491 if (writeThread
!= NULL
) {
492 stopConfigWriteThread
.Signal();
493 writeThread
->WaitForTermination();
496 delete environmentInstance
;
500 PXConfig
* PXConfigDictionary::GetEnvironmentInstance()
503 if (environmentInstance
== NULL
) {
504 environmentInstance
= new PXConfig(0);
505 environmentInstance
->ReadFromEnvironment(PProcess::Current().PXGetEnvp());
508 return environmentInstance
;
512 PXConfig
* PXConfigDictionary::GetFileConfigInstance(const PFilePath
& key
, const PFilePath
& readKey
)
516 // start write thread, if not already started
517 if (writeThread
== NULL
)
518 writeThread
= new PXConfigWriteThread(stopConfigWriteThread
);
520 PXConfig
* config
= GetAt(key
);
522 config
->AddInstance();
524 config
= new PXConfig(0);
525 config
->ReadFromFile(readKey
);
526 config
->AddInstance();
534 void PXConfigDictionary::RemoveInstance(PXConfig
* instance
)
538 if (instance
!= environmentInstance
) {
539 PINDEX index
= GetObjectsIndex(instance
);
540 PAssert(index
!= P_MAX_INDEX
, "Cannot find PXConfig instance to remove");
542 // decrement the instance count, but don't remove it yet
543 PFilePath key
= GetKeyAt(index
);
544 instance
->RemoveInstance(key
);
550 void PXConfigDictionary::WriteChangedInstances()
555 for (i
= 0; i
< GetSize(); i
++) {
556 PFilePath key
= GetKeyAt(i
);
557 GetAt(key
)->Flush(key
);
563 ////////////////////////////////////////////////////////////
567 // Create a new configuration object
569 ////////////////////////////////////////////////////////////
571 void PConfig::Construct(Source src
,
572 const PString
& appname
,
573 const PString
& /*manuf*/)
576 PFilePath filename
, readFilename
;
578 // handle cnvironment configs differently
579 if (src
== PConfig::Environment
)
580 config
= configDict
->GetEnvironmentInstance();
582 // look up file name to read, and write
583 if (src
== PConfig::System
)
584 LocateFile(SYS_CONFIG_NAME
, readFilename
, filename
);
586 filename
= readFilename
= PProcess::Current().GetConfigurationFile();
588 // get, or create, the configuration
589 config
= configDict
->GetFileConfigInstance(filename
, readFilename
);
592 PConfig::PConfig(int, const PString
& name
)
593 : defaultSection("Options")
595 PFilePath readFilename
, filename
;
596 LocateFile(name
, readFilename
, filename
);
597 config
= configDict
->GetFileConfigInstance(filename
, readFilename
);
600 void PConfig::Construct(const PFilePath
& theFilename
)
603 config
= configDict
->GetFileConfigInstance(theFilename
, theFilename
);
609 configDict
->RemoveInstance(config
);
612 ////////////////////////////////////////////////////////////
616 // Return a list of all the section names in the file.
618 ////////////////////////////////////////////////////////////
620 PStringList
PConfig::GetSections() const
622 PAssert(config
!= NULL
, "config instance not set");
627 for (PINDEX i
= 0; i
< (*config
).GetSize(); i
++)
628 list
.AppendString((*config
)[i
]);
636 ////////////////////////////////////////////////////////////
640 // Return a list of all the keys in the section. If the section name is
641 // not specified then use the default section.
643 ////////////////////////////////////////////////////////////
645 PStringList
PConfig::GetKeys(const PString
& theSection
) const
647 PAssert(config
!= NULL
, "config instance not set");
653 if ((index
= config
->GetSectionsIndex(theSection
)) != P_MAX_INDEX
) {
654 PXConfigSectionList
& section
= (*config
)[index
].GetList();
655 for (PINDEX i
= 0; i
< section
.GetSize(); i
++)
656 list
.AppendString(section
[i
]);
665 ////////////////////////////////////////////////////////////
669 // Delete all variables in the specified section. If the section name is
670 // not specified then use the default section.
672 ////////////////////////////////////////////////////////////
674 void PConfig::DeleteSection(const PString
& theSection
)
677 PAssert(config
!= NULL
, "config instance not set");
683 if ((index
= config
->GetSectionsIndex(theSection
)) != P_MAX_INDEX
) {
684 config
->RemoveAt(index
);
692 ////////////////////////////////////////////////////////////
696 // Delete the particular variable in the specified section.
698 ////////////////////////////////////////////////////////////
700 void PConfig::DeleteKey(const PString
& theSection
, const PString
& theKey
)
702 PAssert(config
!= NULL
, "config instance not set");
706 if ((index
= config
->GetSectionsIndex(theSection
)) != P_MAX_INDEX
) {
707 PXConfigSectionList
& section
= (*config
)[index
].GetList();
709 if ((index_2
= section
.GetValuesIndex(theKey
)) != P_MAX_INDEX
) {
710 section
.RemoveAt(index_2
);
720 ////////////////////////////////////////////////////////////
724 // Test if there is a value for the key.
726 ////////////////////////////////////////////////////////////
728 BOOL
PConfig::HasKey(const PString
& theSection
, const PString
& theKey
) const
730 PAssert(config
!= NULL
, "config instance not set");
733 BOOL present
= FALSE
;
735 if ((index
= config
->GetSectionsIndex(theSection
)) != P_MAX_INDEX
) {
736 PXConfigSectionList
& section
= (*config
)[index
].GetList();
737 present
= section
.GetValuesIndex(theKey
) != P_MAX_INDEX
;
746 ////////////////////////////////////////////////////////////
750 // Get a string variable determined by the key in the section.
752 ////////////////////////////////////////////////////////////
754 PString
PConfig::GetString(const PString
& theSection
,
755 const PString
& theKey
, const PString
& dflt
) const
757 PAssert(config
!= NULL
, "config instance not set");
760 PString value
= dflt
;
762 if ((index
= config
->GetSectionsIndex(theSection
)) != P_MAX_INDEX
) {
764 PXConfigSectionList
& section
= (*config
)[index
].GetList();
765 if ((index
= section
.GetValuesIndex(theKey
)) != P_MAX_INDEX
)
766 value
= section
[index
].GetValue();
774 ////////////////////////////////////////////////////////////
778 // Set a string variable determined by the key in the section.
780 ////////////////////////////////////////////////////////////
782 void PConfig::SetString(const PString
& theSection
,
783 const PString
& theKey
,
784 const PString
& theValue
)
786 PAssert(config
!= NULL
, "config instance not set");
790 PXConfigSection
* section
;
791 PXConfigValue
* value
;
793 if ((index
= config
->GetSectionsIndex(theSection
)) != P_MAX_INDEX
)
794 section
= &(*config
)[index
];
796 section
= new PXConfigSection(theSection
);
797 config
->Append(section
);
801 if ((index
= section
->GetList().GetValuesIndex(theKey
)) != P_MAX_INDEX
)
802 value
= &(section
->GetList()[index
]);
804 value
= new PXConfigValue(theKey
);
805 section
->GetList().Append(value
);
809 if (theValue
!= value
->GetValue()) {
810 value
->SetValue(theValue
);
818 ///////////////////////////////////////////////////////////////////////////////