/* * ORXONOX - the hottest 3D action shooter ever to exist * > www.orxonox.net < * * * License notice: * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: * Fabian 'x3n' Landau * Co-authors: * ... * */ #include "ConfigFileManager.h" #include #include #include "util/Convert.h" #include "util/String.h" #include "ConsoleCommand.h" #include "ConfigValueContainer.h" #include "Core.h" namespace orxonox { const int CONFIG_FILE_MAX_LINELENGHT = 1024; const char* const DEFAULT_CONFIG_FILE = "default.ini"; ConfigFileManager* ConfigFileManager::singletonRef_s = 0; SetConsoleCommandShortcutExtern(config).argumentCompleter(0, autocompletion::configvalueclasses()).argumentCompleter(1, autocompletion::configvalues()).argumentCompleter(2, autocompletion::configvalue()); SetConsoleCommandShortcutExtern(tconfig).argumentCompleter(0, autocompletion::configvalueclasses()).argumentCompleter(1, autocompletion::configvalues()).argumentCompleter(2, autocompletion::configvalue()); SetConsoleCommandShortcutExtern(reloadConfig); SetConsoleCommandShortcutExtern(cleanConfig); SetConsoleCommandShortcutExtern(loadSettings).argumentCompleter(0, autocompletion::files()); bool config(const std::string& classname, const std::string& varname, const std::string& value) { std::map::const_iterator identifier = Identifier::getLowercaseIdentifierMap().find(getLowercase(classname)); if (identifier != Identifier::getLowercaseIdentifierMapEnd()) { std::map::const_iterator variable = (*identifier).second->getLowercaseConfigValueMap().find(getLowercase(varname)); if (variable != (*identifier).second->getLowercaseConfigValueMapEnd()) return (*variable).second->set(value); } return false; } bool tconfig(const std::string& classname, const std::string& varname, const std::string& value) { std::map::const_iterator identifier = Identifier::getLowercaseIdentifierMap().find(getLowercase(classname)); if (identifier != Identifier::getLowercaseIdentifierMapEnd()) { std::map::const_iterator variable = (*identifier).second->getLowercaseConfigValueMap().find(getLowercase(varname)); if (variable != (*identifier).second->getLowercaseConfigValueMapEnd()) return (*variable).second->tset(value); } return false; } void reloadConfig() { ConfigFileManager::getInstance().load(); } void cleanConfig() { ConfigFileManager::getInstance().clean(false); } void loadSettings(const std::string& filename) { ConfigFileManager::getInstance().setFilename(ConfigFileType::Settings, filename); } ////////////////////////// // ConfigFileEntryValue // ////////////////////////// void ConfigFileEntryValue::setValue(const std::string& value) { if (!this->bString_) this->value_ = value; else this->value_ = "\"" + addSlashes(stripEnclosingQuotes(value)) + "\""; } std::string ConfigFileEntryValue::getValue() const { if (!this->bString_) return this->value_; else return removeSlashes(stripEnclosingQuotes(this->value_)); } std::string ConfigFileEntryValue::getFileEntry() const { if (this->additionalComment_ == "" || this->additionalComment_.size() == 0) return (this->name_ + "=" + this->value_); else return (this->name_ + "=" + this->value_ + " " + this->additionalComment_); } //////////////////////////////// // ConfigFileEntryVectorValue // //////////////////////////////// std::string ConfigFileEntryVectorValue::getFileEntry() const { if (this->additionalComment_ == "" || this->additionalComment_.size() == 0) return (this->name_ + "[" + getConvertedValue(this->index_, "0") + "]" + "=" + this->value_); else return (this->name_ + "[" + getConvertedValue(this->index_, "0") + "]=" + this->value_ + " " + this->additionalComment_); } /////////////////////// // ConfigFileSection // /////////////////////// ConfigFileSection::~ConfigFileSection() { for (std::list::iterator it = this->entries_.begin(); it != this->entries_.end(); ) delete (*(it++)); } void ConfigFileSection::deleteVectorEntries(const std::string& name, unsigned int startindex) { for (std::list::iterator it = this->entries_.begin(); it != this->entries_.end(); ) { if (((*it)->getName() == name) && ((*it)->getIndex() >= startindex)) { delete (*it); this->entries_.erase(it++); } else { ++it; } } } unsigned int ConfigFileSection::getVectorSize(const std::string& name) { unsigned int size = 0; for (std::list::const_iterator it = this->entries_.begin(); it != this->entries_.end(); ++it) if ((*it)->getName() == name) if ((*it)->getIndex() > size) size = (*it)->getIndex(); if (size == 0) return 0; else return (size + 1); } std::string ConfigFileSection::getFileEntry() const { if (this->additionalComment_ == "" || this->additionalComment_.size() == 0) return ("[" + this->name_ + "]"); else return ("[" + this->name_ + "] " + this->additionalComment_); } std::list::iterator ConfigFileSection::getEntryIterator(const std::string& name, const std::string& fallback, bool bString) { for (std::list::iterator it = this->entries_.begin(); it != this->entries_.end(); ++it) { if ((*it)->getName() == name) { (*it)->setString(bString); return it; } } this->bUpdated_ = true; return this->entries_.insert(this->entries_.end(), (ConfigFileEntry*)(new ConfigFileEntryValue(name, fallback, bString))); } std::list::iterator ConfigFileSection::getEntryIterator(const std::string& name, unsigned int index, const std::string& fallback, bool bString) { for (std::list::iterator it = this->entries_.begin(); it != this->entries_.end(); ++it) { if (((*it)->getName() == name) && ((*it)->getIndex() == index)) { (*it)->setString(bString); return it; } } this->bUpdated_ = true; if (index == 0) return this->entries_.insert(this->entries_.end(), (ConfigFileEntry*)(new ConfigFileEntryVectorValue(name, index, fallback, bString))); else return this->entries_.insert(++this->getEntryIterator(name, index - 1, "", bString), (ConfigFileEntry*)(new ConfigFileEntryVectorValue(name, index, fallback, bString))); } //////////////// // ConfigFile // //////////////// ConfigFile::~ConfigFile() { this->clear(); } void ConfigFile::load(bool bCreateIfNotExisting) { // Be sure we start from new in the memory this->clear(); // Get default file if necessary and available boost::filesystem::path filepath(Core::getConfigPath() / this->filename_); if (!boost::filesystem::exists(filepath)) { // Try to get default one from the media folder boost::filesystem::path defaultFilepath(Core::getMediaPath() / "defaultConfig" / this->filename_); if (boost::filesystem::exists(defaultFilepath)) { boost::filesystem::copy_file(defaultFilepath, filepath); } } // Open the file std::ifstream file; file.open(filepath.file_string().c_str(), std::fstream::in); if (file.is_open()) { char linearray[CONFIG_FILE_MAX_LINELENGHT]; ConfigFileSection* newsection = 0; while (file.good() && !file.eof()) { file.getline(linearray, CONFIG_FILE_MAX_LINELENGHT); std::string line = std::string(linearray); std::string temp = getStripped(line); if (!isEmpty(temp) && !isComment(temp)) { size_t pos1 = temp.find('['); if (pos1 == 0) pos1 = line.find('['); else pos1 = std::string::npos; size_t pos2 = line.find(']'); if (pos1 != std::string::npos && pos2 != std::string::npos && pos2 > pos1 + 1) { // New section std::string comment = line.substr(pos2 + 1); if (isComment(comment)) newsection = new ConfigFileSection(line.substr(pos1 + 1, pos2 - pos1 - 1), comment); else newsection = new ConfigFileSection(line.substr(pos1 + 1, pos2 - pos1 - 1)); this->sections_.insert(this->sections_.end(), newsection); continue; } } if (newsection != 0) { if (isComment(line)) { // New comment newsection->getEntries().insert(newsection->getEntries().end(), new ConfigFileEntryComment(removeTrailingWhitespaces(line))); continue; } else { size_t pos1 = line.find('='); if (pos1 != std::string::npos && pos1 > 0) { // New entry size_t pos2 = line.find('['); size_t pos3 = line.find(']'); size_t commentposition = getNextCommentPosition(line, pos1 + 1); while (isBetweenQuotes(line, commentposition)) { commentposition = getNextCommentPosition(line, commentposition + 1); } std::string value = "", comment = ""; if (commentposition == std::string::npos) { value = removeTrailingWhitespaces(line.substr(pos1 + 1)); } else { value = removeTrailingWhitespaces(line.substr(pos1 + 1, commentposition - pos1 - 1)); comment = removeTrailingWhitespaces(line.substr(commentposition)); } if (pos2 != std::string::npos && pos3 != std::string::npos && pos3 > pos2 + 1) { // There might be an array index unsigned int index = 0; if (ConvertValue(&index, line.substr(pos2 + 1, pos3 - pos2 - 1))) { // New array std::list::iterator it = newsection->getEntryIterator(getStripped(line.substr(0, pos2)), index, value, false); (*it)->setValue(value); (*it)->setComment(comment); continue; } } // New value newsection->getEntries().insert(newsection->getEntries().end(), new ConfigFileEntryValue(getStripped(line.substr(0, pos1)), value, false, comment)); continue; } } } } file.close(); COUT(3) << "Loaded config file \"" << this->filename_ << "\"." << std::endl; // Save the file in case something changed (like stripped whitespaces) this->save(); // Update all ConfigValueContainers this->updateConfigValues(); } // end file.is_open() } void ConfigFile::save() const { boost::filesystem::path filepath(Core::getConfigPath() / this->filename_); std::ofstream file; file.open(filepath.file_string().c_str(), std::fstream::out); file.setf(std::ios::fixed, std::ios::floatfield); file.precision(6); if (!file.is_open()) { COUT(1) << "An error occurred in ConfigFileManager.cc:" << std::endl; COUT(1) << "Error: Couldn't open config-file \"" << this->filename_ << "\"." << std::endl; return; } for (std::list::const_iterator it = this->sections_.begin(); it != this->sections_.end(); ++it) { file << (*it)->getFileEntry() << std::endl; for (std::list::const_iterator it_entries = (*it)->getEntriesBegin(); it_entries != (*it)->getEntriesEnd(); ++it_entries) { file << (*it_entries)->getFileEntry() << std::endl; } file << std::endl; } file.close(); COUT(4) << "Saved config file \"" << this->filename_ << "\"." << std::endl; } void ConfigFile::saveAs(const std::string& filename) { std::string temp = this->filename_; this->filename_ = filename; this->save(); this->filename_ = temp; } void ConfigFile::clean(bool bCleanComments) { for (std::list::iterator it1 = this->sections_.begin(); it1 != this->sections_.end(); ) { std::map::const_iterator it2 = Identifier::getIdentifierMap().find((*it1)->getName()); if (it2 != Identifier::getIdentifierMapEnd() && (*it2).second->hasConfigValues()) { // The section exists, delete comment if (bCleanComments) (*it1)->setComment(""); for (std::list::iterator it3 = (*it1)->entries_.begin(); it3 != (*it1)->entries_.end(); ) { std::map::const_iterator it4 = (*it2).second->getConfigValueMap().find((*it3)->getName()); if (it4 != (*it2).second->getConfigValueMapEnd()) { // The config-value exists, delete comment if (bCleanComments) (*it3)->setComment(""); ++it3; } else { // The config-value doesn't exist delete (*it3); (*it1)->entries_.erase(it3++); } } ++it1; } else { // The section doesn't exist delete (*it1); this->sections_.erase(it1++); } } // Save the file this->save(); } void ConfigFile::clear() { for (std::list::iterator it = this->sections_.begin(); it != this->sections_.end(); ) delete (*(it++)); this->sections_.clear(); } ConfigFileSection* ConfigFile::getSection(const std::string& section) { for (std::list::iterator it = this->sections_.begin(); it != this->sections_.end(); ++it) if ((*it)->getName() == section) return (*it); this->bUpdated_ = true; return (*this->sections_.insert(this->sections_.end(), new ConfigFileSection(section))); } void ConfigFile::saveIfUpdated() { bool sectionsUpdated = false; for (std::list::iterator it = this->sections_.begin(); it != this->sections_.end(); ++it) { if ((*it)->bUpdated_) { sectionsUpdated = true; (*it)->bUpdated_ = false; } } if (this->bUpdated_ || sectionsUpdated) { this->bUpdated_ = false; this->save(); } } void ConfigFile::updateConfigValues() { if (this->type_ == ConfigFileType::Settings) { for (std::map::const_iterator it = Identifier::getIdentifierMapBegin(); it != Identifier::getIdentifierMapEnd(); ++it) { if (it->second->hasConfigValues()) { for (std::map::const_iterator it2 = (*it).second->getConfigValueMapBegin(); it2 != (*it).second->getConfigValueMapEnd(); ++it2) it2->second->update(); it->second->updateConfigValues(); } } } } /////////////////////// // ConfigFileManager // /////////////////////// ConfigFileManager::ConfigFileManager() : mininmalFreeType_(ConfigFileType::numberOfReservedTypes) { assert(singletonRef_s == 0); singletonRef_s = this; } ConfigFileManager::~ConfigFileManager() { for(std::map::const_iterator it = this->configFiles_.begin(); it != this->configFiles_.end(); ) delete (it++)->second; assert(singletonRef_s != 0); singletonRef_s = 0; } void ConfigFileManager::setFilename(ConfigFileType type, const std::string& filename) { std::map::const_iterator it = this->configFiles_.find(type); if (it != this->configFiles_.end()) { assert(it->second); delete it->second; } this->configFiles_[type] = new ConfigFile(filename, type); this->load(type); } void ConfigFileManager::load() { for(std::map::const_iterator it = this->configFiles_.begin(); it != this->configFiles_.end(); ++it) it->second->load(); } void ConfigFileManager::save() { for(std::map::const_iterator it = this->configFiles_.begin(); it != this->configFiles_.end(); ++it) it->second->save(); } void ConfigFileManager::clean(bool bCleanComments) { for(std::map::const_iterator it = this->configFiles_.begin(); it != this->configFiles_.end(); ++it) this->clean(it->first, bCleanComments); } void ConfigFileManager::load(ConfigFileType type) { this->getFile(type)->load(); } void ConfigFileManager::save(ConfigFileType type) { this->getFile(type)->save(); } void ConfigFileManager::saveAs(ConfigFileType type, const std::string& saveFilename) { this->getFile(type)->saveAs(saveFilename); } void ConfigFileManager::clean(ConfigFileType type, bool bCleanComments) { this->getFile(type)->clean(bCleanComments); } void ConfigFileManager::updateConfigValues() { for(std::map::const_iterator it = this->configFiles_.begin(); it != this->configFiles_.end(); ++it) it->second->updateConfigValues(); } void ConfigFileManager::updateConfigValues(ConfigFileType type) { this->getFile(type)->updateConfigValues(); } const std::string& ConfigFileManager::getFilename(ConfigFileType type) { std::map::const_iterator it = this->configFiles_.find(type); if (it != this->configFiles_.end()) return it->second->getFilename(); else return BLANKSTRING; } ConfigFile* ConfigFileManager::getFile(ConfigFileType type) { std::map::const_iterator it = this->configFiles_.find(type); if (it != this->configFiles_.end()) return it->second; else { COUT(1) << "ConfigFileManager: Can't find a config file for type with ID " << (int)type << std::endl; COUT(1) << "Using " << DEFAULT_CONFIG_FILE << " file." << std::endl; this->setFilename(type, DEFAULT_CONFIG_FILE); return getFile(type); } } }