- Timestamp:
- Mar 23, 2013, 9:57:41 PM (11 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
code/branches/core6/src/libraries/core/config/ConfigFileManager.cc
r9558 r9559 29 29 /** 30 30 @file 31 @brief Implementation of ConfigFileManager and its helper classes.31 @brief Implementation of ConfigFileManager. 32 32 */ 33 33 34 34 #include "ConfigFileManager.h" 35 35 36 #include <boost/filesystem.hpp> 37 38 #include "util/Convert.h" 39 #include "util/Math.h" 40 #include "util/StringUtils.h" 41 #include "core/PathConfig.h" 42 #include "core/command/ConsoleCommand.h" 43 #include "ConfigValueContainer.h" 36 #include "SettingsConfigFile.h" 44 37 45 38 namespace orxonox 46 39 { 47 //////////////////////////48 // ConfigFileEntryValue //49 //////////////////////////50 /**51 @brief Updates the string that will be stored in the file after one of it's components (name, value, comment) has changed.52 */53 void ConfigFileEntryValue::update()54 {55 // Make sure we remove the quotes when bString changes56 if (this->bString_)57 this->value_ = stripEnclosingQuotes(this->value_);58 // Assemble the entry line59 this->fileEntry_ = this->getKeyString() + " = ";60 if (this->bString_ && !this->value_.empty())61 this->fileEntry_ += '"' + addSlashes(this->value_) + '"';62 else63 this->fileEntry_ += this->value_;64 if (!this->additionalComment_.empty())65 this->fileEntry_ += ' ' + this->additionalComment_;66 }67 68 69 ////////////////////////////////70 // ConfigFileEntryVectorValue //71 ////////////////////////////////72 /**73 @brief Updates the string that will be stored in the file after one of it's components (name, value, index, comment) has changed.74 */75 void ConfigFileEntryVectorValue::update()76 {77 this->keyString_ = this->name_ + '[' + multi_cast<std::string>(this->index_) + ']';78 ConfigFileEntryValue::update();79 }80 81 82 ///////////////////////83 // ConfigFileSection //84 ///////////////////////85 /**86 @brief Destructor: Deletes all entries.87 */88 ConfigFileSection::~ConfigFileSection()89 {90 for (std::list<ConfigFileEntry*>::iterator it = this->entries_.begin(); it != this->entries_.end(); )91 delete (*(it++));92 }93 94 /**95 @brief Deletes all elements of a config vector if their index is greater or equal to @a startindex.96 97 @param name The name of the vector98 @param startindex The index of the first element that will be deleted99 */100 void ConfigFileSection::deleteVectorEntries(const std::string& name, unsigned int startindex)101 {102 for (std::list<ConfigFileEntry*>::iterator it = this->entries_.begin(); it != this->entries_.end(); )103 {104 if (((*it)->getName() == name) && ((*it)->getIndex() >= startindex))105 {106 delete (*it);107 this->entries_.erase(it++);108 }109 else110 {111 ++it;112 }113 }114 }115 116 /**117 @brief Returns the size of a config vector.118 @param name The name of the vector119 */120 unsigned int ConfigFileSection::getVectorSize(const std::string& name) const121 {122 unsigned int size = 0;123 for (std::list<ConfigFileEntry*>::const_iterator it = this->entries_.begin(); it != this->entries_.end(); ++it)124 if ((*it)->getName() == name)125 if ((*it)->getIndex() >= size)126 size = (*it)->getIndex() + 1;127 return size;128 }129 130 /**131 @brief Returns the title and comment of the section as it will be stored in the file.132 */133 std::string ConfigFileSection::getFileEntry() const134 {135 if (this->additionalComment_.empty())136 return ('[' + this->name_ + ']');137 else138 return ('[' + this->name_ + "] " + this->additionalComment_);139 }140 141 /**142 @brief Returns the entry with given name (or NULL if it doesn't exist).143 144 @param name The name of the entry145 */146 ConfigFileEntry* ConfigFileSection::getEntry(const std::string& name) const147 {148 for (std::list<ConfigFileEntry*>::const_iterator it = this->entries_.begin(); it != this->entries_.end(); ++it)149 {150 if ((*it)->getName() == name)151 return *it;152 }153 return NULL;154 }155 156 /**157 @brief Returns the entry of a vector element with given name and index (or NULL if it doesn't exist).158 159 @param name The name of the vector160 @param index The index of the element in the vector161 */162 ConfigFileEntry* ConfigFileSection::getEntry(const std::string& name, unsigned int index) const163 {164 for (std::list<ConfigFileEntry*>::const_iterator it = this->entries_.begin(); it != this->entries_.end(); ++it)165 {166 if (((*it)->getName() == name) && ((*it)->getIndex() == index))167 return *it;168 }169 return NULL;170 }171 172 /**173 @brief Returns the iterator to the entry with given name. If the entry doesn't exist, it is created using the fallback value.174 175 @param name The name of the entry176 @param fallback The value that will be used if the entry doesn't exist177 @param bString If true, the value is treated as string which means some special treatment of special characters.178 */179 std::list<ConfigFileEntry*>::iterator ConfigFileSection::getOrCreateEntryIterator(const std::string& name, const std::string& fallback, bool bString)180 {181 for (std::list<ConfigFileEntry*>::iterator it = this->entries_.begin(); it != this->entries_.end(); ++it)182 {183 if ((*it)->getName() == name)184 {185 (*it)->setString(bString);186 return it;187 }188 }189 190 this->bUpdated_ = true;191 192 return this->entries_.insert(this->entries_.end(), new ConfigFileEntryValue(name, fallback, bString));193 }194 195 /**196 @brief Returns the iterator to the entry of a vector element with given name and index. If the entry doesn't exist, it is created using the fallback value.197 198 @param name The name of the vector199 @param index The index of the element in the vector200 @param fallback The value that will be used if the entry doesn't exist201 @param bString If true, the value is treated as string which means some special treatment of special characters.202 */203 std::list<ConfigFileEntry*>::iterator ConfigFileSection::getOrCreateEntryIterator(const std::string& name, unsigned int index, const std::string& fallback, bool bString)204 {205 for (std::list<ConfigFileEntry*>::iterator it = this->entries_.begin(); it != this->entries_.end(); ++it)206 {207 if (((*it)->getName() == name) && ((*it)->getIndex() == index))208 {209 (*it)->setString(bString);210 return it;211 }212 }213 214 this->bUpdated_ = true;215 216 if (index == 0)217 return this->entries_.insert(this->entries_.end(), new ConfigFileEntryVectorValue(name, index, fallback, bString));218 else219 return this->entries_.insert(++this->getOrCreateEntryIterator(name, index - 1, "", bString), new ConfigFileEntryVectorValue(name, index, fallback, bString));220 }221 222 223 ////////////////224 // ConfigFile //225 ////////////////226 227 const char* ConfigFile::DEFAULT_CONFIG_FOLDER = "defaultConfig";228 229 /**230 @brief Constructor: Initializes the config file.231 @param filename The file-name of this config file232 @param bCopyFallbackFile If true, the default config file is copied into the config-directory before loading the file233 */234 ConfigFile::ConfigFile(const std::string& filename, bool bCopyFallbackFile)235 : filename_(filename)236 , bCopyFallbackFile_(bCopyFallbackFile)237 , bUpdated_(false)238 {239 }240 241 /**242 @brief Destructor: Deletes all sections and entries.243 */244 ConfigFile::~ConfigFile()245 {246 this->clear();247 }248 249 /**250 @brief Loads the config file from the hard-disk and reads the sections and their values.251 */252 void ConfigFile::load()253 {254 // Be sure we start from new in the memory255 this->clear();256 257 boost::filesystem::path filepath(this->filename_);258 if (!filepath.is_complete())259 {260 filepath = PathConfig::getConfigPath() / filepath;261 if (this->bCopyFallbackFile_)262 {263 // Look for default file in the data folder264 if (!boost::filesystem::exists(filepath))265 {266 boost::filesystem::path defaultFilepath(PathConfig::getDataPath() / DEFAULT_CONFIG_FOLDER / this->filename_);267 if (boost::filesystem::exists(defaultFilepath))268 {269 // Try to copy default file from the data folder270 try271 {272 boost::filesystem::copy_file(defaultFilepath, filepath);273 orxout(internal_info, context::config) << "Copied " << this->filename_ << " from the default config folder." << endl;274 }275 catch (const boost::filesystem::filesystem_error& ex)276 { orxout(user_error, context::config) << "Error in ConfigFile: " << ex.what() << endl; }277 }278 }279 }280 }281 282 // Open the file283 std::ifstream file;284 file.open(filepath.string().c_str(), std::fstream::in);285 if (file.is_open())286 {287 ConfigFileSection* newsection = 0;288 289 while (file.good() && !file.eof())290 {291 std::string line;292 std::getline(file, line);293 294 const std::string& temp = getStripped(line);295 if (!isEmpty(temp) && !isComment(temp))296 {297 size_t pos1 = temp.find('[');298 if (pos1 == 0) pos1 = line.find('['); else pos1 = std::string::npos;299 size_t pos2 = line.find(']');300 301 if (pos1 != std::string::npos && pos2 != std::string::npos && pos2 > pos1 + 1)302 {303 // New section304 const std::string& comment = line.substr(pos2 + 1);305 if (isComment(comment))306 newsection = new ConfigFileSection(line.substr(pos1 + 1, pos2 - pos1 - 1), comment);307 else308 newsection = new ConfigFileSection(line.substr(pos1 + 1, pos2 - pos1 - 1));309 this->sections_.insert(this->sections_.end(), newsection);310 continue;311 }312 }313 314 if (newsection != 0)315 {316 if (isComment(line))317 {318 // New comment319 newsection->getEntries().insert(newsection->getEntries().end(), new ConfigFileEntryComment(removeTrailingWhitespaces(line)));320 continue;321 }322 else323 {324 size_t pos1 = line.find('=');325 326 if (pos1 != std::string::npos && pos1 > 0)327 {328 // New entry329 size_t pos2 = line.find('[');330 size_t pos3 = line.find(']');331 size_t commentposition = getNextCommentPosition(line, pos1 + 1);332 while (isBetweenQuotes(line, commentposition))333 {334 commentposition = getNextCommentPosition(line, commentposition + 1);335 }336 std::string value, comment;337 if (commentposition == std::string::npos)338 {339 value = line.substr(pos1 + 1);340 }341 else342 {343 value = line.substr(pos1 + 1, commentposition - pos1 - 1);344 comment = removeTrailingWhitespaces(line.substr(commentposition));345 }346 347 value = removeTrailingWhitespaces(value);348 value = removeSlashes(value);349 350 if (pos2 != std::string::npos && pos3 != std::string::npos && pos3 > pos2 + 1)351 {352 // There might be an array index353 unsigned int index = 0;354 if (convertValue(&index, line.substr(pos2 + 1, pos3 - pos2 - 1)))355 {356 // New array357 std::list<ConfigFileEntry*>::iterator it = newsection->getOrCreateEntryIterator(getStripped(line.substr(0, pos2)), index, value, false);358 (*it)->setValue(value);359 (*it)->setComment(comment);360 continue;361 }362 }363 364 // New value365 newsection->getEntries().insert(newsection->getEntries().end(), new ConfigFileEntryValue(getStripped(line.substr(0, pos1)), value, false, comment));366 continue;367 }368 }369 }370 }371 372 file.close();373 374 orxout(internal_info, context::config) << "Loaded config file \"" << this->filename_ << "\"." << endl;375 376 // DO NOT save the file --> we can open supposedly read only config files377 } // end file.is_open()378 }379 380 /**381 @brief Writes the sections and values to the hard-disk.382 */383 void ConfigFile::save() const384 {385 this->saveAs(this->filename_);386 }387 388 /**389 @brief Writes the sections and values to a given file on the hard-disk.390 */391 void ConfigFile::saveAs(const std::string& filename) const392 {393 boost::filesystem::path filepath(filename);394 if (!filepath.is_complete())395 filepath = PathConfig::getConfigPath() / filename;396 std::ofstream file;397 file.open(filepath.string().c_str(), std::fstream::out);398 file.setf(std::ios::fixed, std::ios::floatfield);399 file.precision(6);400 401 if (!file.is_open())402 {403 orxout(user_error, context::config) << "Couldn't open config-file \"" << filename << "\"." << endl;404 return;405 }406 407 for (std::list<ConfigFileSection*>::const_iterator it = this->sections_.begin(); it != this->sections_.end(); ++it)408 {409 file << (*it)->getFileEntry() << endl;410 411 for (std::list<ConfigFileEntry*>::const_iterator it_entries = (*it)->getEntriesBegin(); it_entries != (*it)->getEntriesEnd(); ++it_entries)412 file << (*it_entries)->getFileEntry() << endl;413 414 file << endl;415 }416 417 file.close();418 419 orxout(verbose, context::config) << "Saved config file \"" << filename << "\"." << endl;420 }421 422 /**423 @brief Deletes all sections (which again delete all their values) and clears the list of sections.424 */425 void ConfigFile::clear()426 {427 for (std::list<ConfigFileSection*>::iterator it = this->sections_.begin(); it != this->sections_.end(); )428 delete (*(it++));429 this->sections_.clear();430 }431 432 /**433 @brief Deletes all elements of a config vector if their index is greater or equal to @a startindex.434 435 @param section The name of the section436 @param name The name of the vector437 @param startindex The index of the first element that will be deleted438 */439 void ConfigFile::deleteVectorEntries(const std::string& section, const std::string& name, unsigned int startindex)440 {441 if (ConfigFileSection* sectionPtr = this->getSection(section))442 {443 sectionPtr->deleteVectorEntries(name, startindex);444 this->save();445 }446 }447 448 /**449 @brief Returns a pointer to the section with given name (or NULL if the section doesn't exist).450 */451 ConfigFileSection* ConfigFile::getSection(const std::string& section) const452 {453 for (std::list<ConfigFileSection*>::const_iterator it = this->sections_.begin(); it != this->sections_.end(); ++it)454 if ((*it)->getName() == section)455 return (*it);456 return NULL;457 }458 459 /**460 @brief Returns a pointer to the section with given name. If it doesn't exist, the section is created.461 */462 ConfigFileSection* ConfigFile::getOrCreateSection(const std::string& section)463 {464 for (std::list<ConfigFileSection*>::iterator it = this->sections_.begin(); it != this->sections_.end(); ++it)465 if ((*it)->getName() == section)466 return (*it);467 468 this->bUpdated_ = true;469 470 return (*this->sections_.insert(this->sections_.end(), new ConfigFileSection(section)));471 }472 473 /**474 @brief Saves the config file if it was updated (or if any of its sections were updated).475 */476 void ConfigFile::saveIfUpdated()477 {478 bool sectionsUpdated = false;479 480 for (std::list<ConfigFileSection*>::iterator it = this->sections_.begin(); it != this->sections_.end(); ++it)481 {482 if ((*it)->bUpdated_)483 {484 sectionsUpdated = true;485 (*it)->bUpdated_ = false;486 }487 }488 489 if (this->bUpdated_ || sectionsUpdated)490 {491 this->bUpdated_ = false;492 this->save();493 }494 }495 496 497 ////////////////////////498 // SettingsConfigFile //499 ////////////////////////500 501 static const std::string __CC_load_name = "reloadSettings";502 static const std::string __CC_setFilename_name = "setSettingsFile";503 static const std::string __CC_config_name = "config";504 static const std::string __CC_tconfig_name = "tconfig";505 static const std::string __CC_getConfig_name = "getConfig";506 507 SetConsoleCommand(__CC_load_name, &ConfigFile::load);508 SetConsoleCommand(__CC_setFilename_name, &SettingsConfigFile::setFilename);509 SetConsoleCommand(__CC_config_name, &SettingsConfigFile::config).argumentCompleter(0, autocompletion::settingssections()).argumentCompleter(1, autocompletion::settingsentries()).argumentCompleter(2, autocompletion::settingsvalue());510 SetConsoleCommand(__CC_tconfig_name, &SettingsConfigFile::tconfig).argumentCompleter(0, autocompletion::settingssections()).argumentCompleter(1, autocompletion::settingsentries()).argumentCompleter(2, autocompletion::settingsvalue());511 SetConsoleCommand(__CC_getConfig_name, &SettingsConfigFile::getConfig).argumentCompleter(0, autocompletion::settingssections()).argumentCompleter(1, autocompletion::settingsentries());512 513 SettingsConfigFile* SettingsConfigFile::singletonPtr_s = 0;514 515 /**516 @brief Constructor: Activates the console commands.517 */518 SettingsConfigFile::SettingsConfigFile(const std::string& filename)519 : ConfigFile(filename)520 {521 ModifyConsoleCommand(__CC_load_name).setObject(this);522 ModifyConsoleCommand(__CC_setFilename_name).setObject(this);523 ModifyConsoleCommand(__CC_config_name).setObject(this);524 ModifyConsoleCommand(__CC_tconfig_name).setObject(this);525 ModifyConsoleCommand(__CC_getConfig_name).setObject(this);526 }527 528 /**529 @brief Destructor: Deactivates the console commands.530 */531 SettingsConfigFile::~SettingsConfigFile()532 {533 ModifyConsoleCommand(__CC_load_name).setObject(0);534 ModifyConsoleCommand(__CC_setFilename_name).setObject(0);535 ModifyConsoleCommand(__CC_config_name).setObject(0);536 ModifyConsoleCommand(__CC_tconfig_name).setObject(0);537 ModifyConsoleCommand(__CC_getConfig_name).setObject(0);538 }539 540 /**541 @brief Loads the config file and updates the @ref ConfigValueContainer "config value containers".542 */543 void SettingsConfigFile::load()544 {545 ConfigFile::load();546 this->updateConfigValues();547 }548 549 /**550 @brief Changes the file-name.551 */552 void SettingsConfigFile::setFilename(const std::string& filename)553 {554 ConfigFileManager::getInstance().setFilename(ConfigFileType::Settings, filename);555 }556 557 /**558 @brief Registers a new @ref ConfigValueContainer "config value container".559 */560 void SettingsConfigFile::addConfigValueContainer(ConfigValueContainer* container)561 {562 if (container == NULL)563 return;564 std::pair<std::string, ConfigValueContainer*> second(getLowercase(container->getName()), container);565 this->containers_.insert(std::make_pair(getLowercase(container->getSectionName()), second));566 this->sectionNames_.insert(container->getSectionName());567 }568 569 /**570 @brief Unregisters a @ref ConfigValueContainer "config value container".571 */572 void SettingsConfigFile::removeConfigValueContainer(ConfigValueContainer* container)573 {574 if (container == NULL)575 return;576 const std::string& sectionLC = getLowercase(container->getSectionName());577 ContainerMap::iterator upper = this->containers_.upper_bound(sectionLC);578 for (ContainerMap::iterator it = this->containers_.lower_bound(sectionLC); it != upper; ++it)579 {580 if (it->second.second == container)581 {582 // Remove entry from section name set this was the last container for that section583 if (upper == this->containers_.lower_bound(sectionLC))584 this->sectionNames_.erase(container->getSectionName());585 this->containers_.erase(it);586 break;587 }588 }589 }590 591 /**592 @brief Updates all @ref ConfigValueContainer "config value containers".593 */594 void SettingsConfigFile::updateConfigValues()595 {596 // todo: can this be done more efficiently? looks like some identifiers will be updated multiple times.597 598 for (ContainerMap::const_iterator it = this->containers_.begin(); it != this->containers_.end(); ++it)599 {600 it->second.second->update();601 it->second.second->getIdentifier()->updateConfigValues();602 }603 }604 605 /**606 @brief Removes entries and sections from the file that don't exist anymore (i.e. if there's no corresponding @ref ConfigValueContainer "config value container").607 @param bCleanComments If true, comments are also removed from the file608 */609 void SettingsConfigFile::clean(bool bCleanComments)610 {611 for (std::list<ConfigFileSection*>::iterator itSection = this->sections_.begin(); itSection != this->sections_.end(); )612 {613 const std::string& sectionLC = getLowercase((*itSection)->getName());614 ContainerMap::const_iterator lower = this->containers_.lower_bound(sectionLC);615 ContainerMap::const_iterator upper = this->containers_.upper_bound(sectionLC);616 if (lower != upper)617 {618 // The section exists, delete comment619 if (bCleanComments)620 (*itSection)->setComment("");621 for (std::list<ConfigFileEntry*>::iterator itEntry = (*itSection)->entries_.begin(); itEntry != (*itSection)->entries_.end(); )622 {623 const std::string& entryLC = getLowercase((*itEntry)->getName());624 bool bFound = false;625 for (ContainerMap::const_iterator itContainer = lower; itContainer != upper; ++itContainer)626 {627 if (itContainer->second.first == entryLC)628 {629 // The config-value exists, delete comment630 if (bCleanComments)631 (*itEntry)->setComment("");632 ++itEntry;633 bFound = true;634 break;635 }636 }637 if (!bFound)638 {639 // The config-value doesn't exist640 delete (*itEntry);641 (*itSection)->entries_.erase(itEntry++);642 }643 }644 ++itSection;645 }646 else647 {648 // The section doesn't exist649 delete (*itSection);650 this->sections_.erase(itSection++);651 }652 }653 654 // Save the file655 this->save();656 }657 658 /**659 @brief Console-command: Changes the value of an entry and stores it the file.660 661 @param section The section of the config value662 @param entry The name of the config value663 @param value The new value664 */665 void SettingsConfigFile::config(const std::string& section, const std::string& entry, const std::string& value)666 {667 if (!this->configImpl(section, entry, value, &ConfigValueContainer::set))668 orxout(user_error, context::config) << "Config value \"" << entry << "\" in section \"" << section << "\" doesn't exist." << endl;669 }670 671 /**672 @brief Console-command: Changes the value of an entry, but doesn't store it in the file (it's only a temporary change).673 674 @param section The section of the config value675 @param entry The name of the config value676 @param value The new value677 */678 void SettingsConfigFile::tconfig(const std::string& section, const std::string& entry, const std::string& value)679 {680 if (!this->configImpl(section, entry, value, &ConfigValueContainer::tset))681 orxout(user_error, context::config) << "Config value \"" << entry << "\" in section \"" << section << "\" doesn't exist." << endl;682 }683 684 /**685 @brief Changes the value of an entry, depending on @a function, either by using "set" or "tset"686 687 @param section The section of the config value688 @param entry The name of the config value689 @param value The new value690 @param function The function ("set" or "tset") that will be used to change the value.691 */692 bool SettingsConfigFile::configImpl(const std::string& section, const std::string& entry, const std::string& value, bool (ConfigValueContainer::*function)(const MultiType&))693 {694 const std::string& sectionLC = getLowercase(section);695 const std::string& entryLC = getLowercase(entry);696 ContainerMap::iterator upper = this->containers_.upper_bound(sectionLC);697 for (ContainerMap::iterator it = this->containers_.lower_bound(sectionLC); it != upper; ++it)698 {699 // Note: Config value vectors cannot be supported700 if (it->second.first == entryLC && !it->second.second->isVector())701 {702 return (it->second.second->*function)(value);703 }704 }705 return false;706 }707 708 /**709 @brief Console-command: Returns the value of a given entry.710 711 @param section The section of the config value712 @param entry The name of the config value713 */714 std::string SettingsConfigFile::getConfig(const std::string& section, const std::string& entry)715 {716 const std::string& sectionLC = getLowercase(section);717 const std::string& entryLC = getLowercase(entry);718 ContainerMap::iterator upper = this->containers_.upper_bound(sectionLC);719 for (ContainerMap::iterator it = this->containers_.lower_bound(sectionLC); it != upper; ++it)720 {721 // Note: Config value vectors cannot be supported722 if (it->second.first == entryLC && ! it->second.second->isVector())723 {724 std::string value;725 it->second.second->getValue<std::string, OrxonoxClass>(&value, NULL);726 return value;727 }728 }729 return "";730 }731 732 733 40 /////////////////////// 734 41 // ConfigFileManager //
Note: See TracChangeset
for help on using the changeset viewer.