Uncommented beaudio code
[pwlib.git] / src / ptlib / unix / config.cxx
blob9acaa313db972a1248a9934633fd4f2187a17f0c
1 /*
2 * config.cxx
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
18 * under the License.
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): ______________________________________.
29 * $Log$
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.
96 #define _CONFIG_CXX
98 #pragma implementation "config.h"
100 #include <ptlib.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)
118 public:
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; }
124 protected:
125 PString value;
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)
137 public:
138 PXConfigSection(const PCaselessString & theName)
139 : PCaselessString(theName) { list.AllowDeleteObjects(); }
141 PXConfigSectionList & GetList() { return list; }
143 protected:
144 PXConfigSectionList list;
148 // a list of sections
151 PDECLARE_LIST(PXConfig, PXConfigSection)
152 public:
153 PXConfig(int i = 0);
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; }
166 BOOL AddInstance();
167 BOOL RemoveInstance(const PFilePath & filename);
169 PINDEX GetSectionsIndex(const PString & theSection) const;
171 protected:
172 int instanceCount;
173 PMutex mutex;
174 BOOL dirty;
175 BOOL canSave;
179 // a dictionary of configurations, keyed by filename
181 PDECLARE_DICTIONARY(PXConfigDictionary, PFilePath, PXConfig)
182 public:
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();
190 protected:
191 PMutex mutex;
192 PXConfig * environmentInstance;
193 PThread * writeThread;
194 PSyncPointAck stopConfigWriteThread;
198 PDECLARE_CLASS(PXConfigWriteThread, PThread)
199 public:
200 PXConfigWriteThread(PSyncPointAck & stop);
201 ~PXConfigWriteThread();
202 void Main();
203 private:
204 PSyncPointAck & stop;
208 PXConfigDictionary * configDict;
210 #define new PNEW
212 //////////////////////////////////////////////////////
214 void PProcess::CreateConfigFilesDictionary()
216 configFiles = new PXConfigDictionary(0);
220 PXConfigWriteThread::PXConfigWriteThread(PSyncPointAck & s)
221 : PThread(10000, NoAutoDeleteThread, NormalPriority, "PXConfigWriteThread"),
222 stop(s)
224 Resume();
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();
238 stop.Acknowledge();
243 PXConfig::PXConfig(int)
245 // make sure content gets removed
246 AllowDeleteObjects();
248 // no instances, initially
249 instanceCount = 0;
251 // we start off clean
252 dirty = FALSE;
254 // normally save on exit (except for environment configs)
255 canSave = TRUE;
258 BOOL PXConfig::AddInstance()
260 mutex.Wait();
261 BOOL stat = instanceCount++ == 0;
262 mutex.Signal();
264 return stat;
267 BOOL PXConfig::RemoveInstance(const PFilePath & /*filename*/)
269 mutex.Wait();
271 PAssert(instanceCount != 0, "PConfig instance count dec past zero");
273 BOOL stat = --instanceCount == 0;
275 mutex.Signal();
277 return stat;
280 BOOL PXConfig::Flush(const PFilePath & filename)
282 mutex.Wait();
284 BOOL stat = instanceCount == 0;
286 if (canSave && dirty) {
287 WriteToFile(filename);
288 dirty = FALSE;
291 mutex.Signal();
293 return stat;
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");
305 return FALSE;
308 PTextFile file;
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());
314 return FALSE;
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);
323 PINDEX k;
324 for (k = 0; k < lines.GetSize(); k++)
325 file << value << "=" << lines[k] << endl;
327 file << endl;
330 file.flush();
331 file.SetLength(file.GetPosition());
332 file.Close();
334 if (file.GetFilePath() != filename) {
335 if (!file.Rename(file.GetFilePath(), filename.GetFileName(), TRUE)) {
336 PProcess::PXShowSystemWarning(2001, "Cannot rename config file: " + file.GetErrorText());
337 return FALSE;
341 PTRACE(4, "PWLib\tSaved config file: " << filename);
342 return TRUE;
346 BOOL PXConfig::ReadFromFile(const PFilePath & filename)
348 PINDEX len;
350 // clear out all information
351 RemoveAll();
353 // attempt to open file
354 PTextFile file;
355 if (!file.Open(filename, PFile::ReadOnly))
356 return FALSE;
358 PXConfigSection * currentSection = NULL;
360 // read lines in the file
361 while (file.good()) {
362 PString line;
363 file >> line;
364 line = line.Trim();
365 if ((len = line.GetLength()) > 0) {
367 // ignore comments and blank lines
368 char ch = line[0];
369 if ((len > 0) && (ch != ';') && (ch != '#')) {
370 if (ch == '[') {
371 PCaselessString sectionName = (line.Mid(1,len-(line[len-1]==']'?2:1))).Trim();
372 PINDEX index;
373 if ((index = GetValuesIndex(sectionName)) != P_MAX_INDEX)
374 currentSection = &(*this )[index];
375 else {
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();
385 PINDEX index;
386 if ((index = currentSection->GetList().GetValuesIndex(keyStr)) != P_MAX_INDEX) {
387 PXConfigValue & value = currentSection->GetList()[index];
388 value.SetValue(value.GetValue() + '\n' + valStr);
389 } else {
390 PXConfigValue * value = new PXConfigValue(keyStr, valStr);
391 currentSection->GetList().Append(value);
399 // close the file and return
400 file.Close();
401 return TRUE;
404 void PXConfig::ReadFromEnvironment (char **envp)
406 // clear out all information
407 RemoveAll();
409 PXConfigSection * currentSection = new PXConfigSection("Options");
410 Append(currentSection);
412 while (*envp != NULL && **envp != '\0') {
413 PString line(*envp);
414 PINDEX equals = line.Find('=');
415 if (equals > 0) {
416 PXConfigValue * value = new PXConfigValue(line.Left(equals), line.Right(line.GetLength() - equals - 1));
417 currentSection->GetList().Append(value);
419 envp++;
422 // can't save environment configs
423 canSave = FALSE;
426 PINDEX PXConfig::GetSectionsIndex(const PString & theSection) const
428 PINDEX len = theSection.GetLength()-1;
429 if (theSection[len] != '\\')
430 return GetValuesIndex(theSection);
431 else
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))
443 return TRUE;
445 // otherwise check the system directory for a file to read,
446 // and then create
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))
469 return cfgFile;
472 return PDirectory(configurationPaths[0]) + iniFilename;
476 ////////////////////////////////////////////////////////////
478 // PXConfigDictionary
481 PXConfigDictionary::PXConfigDictionary(int)
483 environmentInstance = NULL;
484 writeThread = NULL;
485 configDict = this;
489 PXConfigDictionary::~PXConfigDictionary()
491 if (writeThread != NULL) {
492 stopConfigWriteThread.Signal();
493 writeThread->WaitForTermination();
494 delete writeThread;
496 delete environmentInstance;
500 PXConfig * PXConfigDictionary::GetEnvironmentInstance()
502 mutex.Wait();
503 if (environmentInstance == NULL) {
504 environmentInstance = new PXConfig(0);
505 environmentInstance->ReadFromEnvironment(PProcess::Current().PXGetEnvp());
507 mutex.Signal();
508 return environmentInstance;
512 PXConfig * PXConfigDictionary::GetFileConfigInstance(const PFilePath & key, const PFilePath & readKey)
514 mutex.Wait();
516 // start write thread, if not already started
517 if (writeThread == NULL)
518 writeThread = new PXConfigWriteThread(stopConfigWriteThread);
520 PXConfig * config = GetAt(key);
521 if (config != NULL)
522 config->AddInstance();
523 else {
524 config = new PXConfig(0);
525 config->ReadFromFile(readKey);
526 config->AddInstance();
527 SetAt(key, config);
530 mutex.Signal();
531 return config;
534 void PXConfigDictionary::RemoveInstance(PXConfig * instance)
536 mutex.Wait();
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);
547 mutex.Signal();
550 void PXConfigDictionary::WriteChangedInstances()
552 mutex.Wait();
554 PINDEX i;
555 for (i = 0; i < GetSize(); i++) {
556 PFilePath key = GetKeyAt(i);
557 GetAt(key)->Flush(key);
560 mutex.Signal();
563 ////////////////////////////////////////////////////////////
565 // PConfig::
567 // Create a new configuration object
569 ////////////////////////////////////////////////////////////
571 void PConfig::Construct(Source src,
572 const PString & appname,
573 const PString & /*manuf*/)
575 PString name;
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);
585 else
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);
606 PConfig::~PConfig()
609 configDict->RemoveInstance(config);
612 ////////////////////////////////////////////////////////////
614 // PConfig::
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");
623 config->Wait();
625 PStringList list;
627 for (PINDEX i = 0; i < (*config).GetSize(); i++)
628 list.AppendString((*config)[i]);
630 config->Signal();
632 return list;
636 ////////////////////////////////////////////////////////////
638 // PConfig::
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");
648 config->Wait();
650 PINDEX index;
651 PStringList list;
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]);
659 config->Signal();
660 return list;
665 ////////////////////////////////////////////////////////////
667 // PConfig::
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");
678 config->Wait();
680 PStringList list;
682 PINDEX index;
683 if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX) {
684 config->RemoveAt(index);
685 config->SetDirty();
688 config->Signal();
692 ////////////////////////////////////////////////////////////
694 // PConfig::
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");
703 config->Wait();
705 PINDEX index;
706 if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX) {
707 PXConfigSectionList & section = (*config)[index].GetList();
708 PINDEX index_2;
709 if ((index_2 = section.GetValuesIndex(theKey)) != P_MAX_INDEX) {
710 section.RemoveAt(index_2);
711 config->SetDirty();
715 config->Signal();
720 ////////////////////////////////////////////////////////////
722 // PConfig::
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");
731 config->Wait();
733 BOOL present = FALSE;
734 PINDEX index;
735 if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX) {
736 PXConfigSectionList & section = (*config)[index].GetList();
737 present = section.GetValuesIndex(theKey) != P_MAX_INDEX;
740 config->Signal();
741 return present;
746 ////////////////////////////////////////////////////////////
748 // PConfig::
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");
758 config->Wait();
760 PString value = dflt;
761 PINDEX index;
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();
769 config->Signal();
770 return value;
774 ////////////////////////////////////////////////////////////
776 // PConfig::
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");
787 config->Wait();
789 PINDEX index;
790 PXConfigSection * section;
791 PXConfigValue * value;
793 if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX)
794 section = &(*config)[index];
795 else {
796 section = new PXConfigSection(theSection);
797 config->Append(section);
798 config->SetDirty();
801 if ((index = section->GetList().GetValuesIndex(theKey)) != P_MAX_INDEX)
802 value = &(section->GetList()[index]);
803 else {
804 value = new PXConfigValue(theKey);
805 section->GetList().Append(value);
806 config->SetDirty();
809 if (theValue != value->GetValue()) {
810 value->SetValue(theValue);
811 config->SetDirty();
814 config->Signal();
818 ///////////////////////////////////////////////////////////////////////////////