Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/ConfigFileManager.cc @ 6998

Last change on this file since 6998 was 6536, checked in by rgrieder, 15 years ago

Merged revisions 6430-6440 from the gamestate branch to the trunk.
This adds keybindings merging functionality.

(from log of r6437)
When running development builds, the keybinder will merge the local file and the one from the data folder.
Catch: if you want to remove a binding, you'll have to write "NoBinding" (not case sensitive) to override the default command

The keybind command already does that for you though.

  • Property svn:eol-style set to native
File size: 23.6 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Fabian 'x3n' Landau
24 *   Co-authors:
25 *      ...
26 *
27 */
28
29#include "ConfigFileManager.h"
30
31#include <boost/filesystem.hpp>
32
33#include "util/Convert.h"
34#include "util/Math.h"
35#include "ConsoleCommand.h"
36#include "ConfigValueContainer.h"
37#include "PathConfig.h"
38
39namespace orxonox
40{
41    //////////////////////////
42    // ConfigFileEntryValue //
43    //////////////////////////
44
45    void ConfigFileEntryValue::update()
46    {
47        // Make sure we remove the quotes when bString changes
48        if (this->bString_)
49            this->value_ = stripEnclosingQuotes(this->value_);
50        // Assemble the entry line
51        this->fileEntry_ = this->getKeyString() + " = ";
52        if (this->bString_ && !this->value_.empty())
53            this->fileEntry_ += '"' + addSlashes(this->value_) + '"';
54        else
55            this->fileEntry_ += this->value_;
56        if (!this->additionalComment_.empty())
57            this->fileEntry_ += ' ' + this->additionalComment_;
58    }
59
60
61    ////////////////////////////////
62    // ConfigFileEntryVectorValue //
63    ////////////////////////////////
64    void ConfigFileEntryVectorValue::update()
65    {
66        this->keyString_ = this->name_ + '[' + multi_cast<std::string>(this->index_) + ']';
67        ConfigFileEntryValue::update();
68    }
69
70
71    ///////////////////////
72    // ConfigFileSection //
73    ///////////////////////
74    ConfigFileSection::~ConfigFileSection()
75    {
76        for (std::list<ConfigFileEntry*>::iterator it = this->entries_.begin(); it != this->entries_.end(); )
77            delete (*(it++));
78    }
79
80    void ConfigFileSection::deleteVectorEntries(const std::string& name, unsigned int startindex)
81    {
82        for (std::list<ConfigFileEntry*>::iterator it = this->entries_.begin(); it != this->entries_.end(); )
83        {
84            if (((*it)->getName() == name) && ((*it)->getIndex() >= startindex))
85            {
86                delete (*it);
87                this->entries_.erase(it++);
88            }
89            else
90            {
91                ++it;
92            }
93        }
94    }
95
96    unsigned int ConfigFileSection::getVectorSize(const std::string& name) const
97    {
98        unsigned int size = 0;
99        for (std::list<ConfigFileEntry*>::const_iterator it = this->entries_.begin(); it != this->entries_.end(); ++it)
100            if ((*it)->getName() == name)
101                if ((*it)->getIndex() > size)
102                    size = (*it)->getIndex();
103        if (size == 0)
104            return 0;
105        else
106            return (size + 1);
107    }
108
109    std::string ConfigFileSection::getFileEntry() const
110    {
111        if (this->additionalComment_.empty())
112            return ('[' + this->name_ + ']');
113        else
114            return ('[' + this->name_ + "] " + this->additionalComment_);
115    }
116
117    ConfigFileEntry* ConfigFileSection::getEntry(const std::string& name) const
118    {
119        for (std::list<ConfigFileEntry*>::const_iterator it = this->entries_.begin(); it != this->entries_.end(); ++it)
120        {
121            if ((*it)->getName() == name)
122                return *it;
123        }
124        return NULL;
125    }
126
127    ConfigFileEntry* ConfigFileSection::getEntry(const std::string& name, unsigned int index) const
128    {
129        for (std::list<ConfigFileEntry*>::const_iterator it = this->entries_.begin(); it != this->entries_.end(); ++it)
130        {
131            if (((*it)->getName() == name) && ((*it)->getIndex() == index))
132                return *it;
133        }
134        return NULL;
135    }
136
137    std::list<ConfigFileEntry*>::iterator ConfigFileSection::getOrCreateEntryIterator(const std::string& name, const std::string& fallback, bool bString)
138    {
139        for (std::list<ConfigFileEntry*>::iterator it = this->entries_.begin(); it != this->entries_.end(); ++it)
140        {
141            if ((*it)->getName() == name)
142            {
143                (*it)->setString(bString);
144                return it;
145            }
146        }
147
148        this->bUpdated_ = true;
149
150        return this->entries_.insert(this->entries_.end(), new ConfigFileEntryValue(name, fallback, bString));
151    }
152
153    std::list<ConfigFileEntry*>::iterator ConfigFileSection::getOrCreateEntryIterator(const std::string& name, unsigned int index, const std::string& fallback, bool bString)
154    {
155        for (std::list<ConfigFileEntry*>::iterator it = this->entries_.begin(); it != this->entries_.end(); ++it)
156        {
157            if (((*it)->getName() == name) && ((*it)->getIndex() == index))
158            {
159                (*it)->setString(bString);
160                return it;
161            }
162        }
163
164        this->bUpdated_ = true;
165
166        if (index == 0)
167            return this->entries_.insert(this->entries_.end(), new ConfigFileEntryVectorValue(name, index, fallback, bString));
168        else
169            return this->entries_.insert(++this->getOrCreateEntryIterator(name, index - 1, "", bString), new ConfigFileEntryVectorValue(name, index, fallback, bString));
170    }
171
172
173    ////////////////
174    // ConfigFile //
175    ////////////////
176
177    const char* ConfigFile::DEFAULT_CONFIG_FOLDER = "defaultConfig";
178
179    ConfigFile::ConfigFile(const std::string& filename, bool bCopyFallbackFile)
180        : filename_(filename)
181        , bCopyFallbackFile_(bCopyFallbackFile)
182        , bUpdated_(false)
183    {
184    }
185
186    ConfigFile::~ConfigFile()
187    {
188        this->clear();
189    }
190
191    void ConfigFile::load()
192    {
193        // Be sure we start from new in the memory
194        this->clear();
195
196        boost::filesystem::path filepath(this->filename_);
197        if (!filepath.is_complete())
198        {
199            filepath = PathConfig::getConfigPath() / filepath;
200            if (this->bCopyFallbackFile_)
201            {
202                // Look for default file in the data folder
203                if (!boost::filesystem::exists(filepath))
204                {
205                    boost::filesystem::path defaultFilepath(PathConfig::getDataPath() / DEFAULT_CONFIG_FOLDER / this->filename_);
206                    if (boost::filesystem::exists(defaultFilepath))
207                    {
208                        // Try to copy default file from the data folder
209                        try
210                        {
211                            boost::filesystem::copy_file(defaultFilepath, filepath);
212                            COUT(3) << "Copied " << this->filename_ << " from the default config folder." << std::endl;
213                        }
214                        catch (const boost::filesystem::filesystem_error& ex)
215                        { COUT(1) << "Error in ConfigFile: " << ex.what() << std::endl; }
216                    }
217                }
218            }
219        }
220
221        // Open the file
222        std::ifstream file;
223        file.open(filepath.string().c_str(), std::fstream::in);
224        if (file.is_open())
225        {
226            ConfigFileSection* newsection = 0;
227
228            while (file.good() && !file.eof())
229            {
230                std::string line;
231                std::getline(file, line);
232
233                const std::string& temp = getStripped(line);
234                if (!isEmpty(temp) && !isComment(temp))
235                {
236                    size_t   pos1 = temp.find('[');
237                    if (pos1 == 0) pos1 = line.find('['); else pos1 = std::string::npos;
238                    size_t   pos2 = line.find(']');
239
240                    if (pos1 != std::string::npos && pos2 != std::string::npos && pos2 > pos1 + 1)
241                    {
242                        // New section
243                        const std::string& comment = line.substr(pos2 + 1);
244                        if (isComment(comment))
245                            newsection = new ConfigFileSection(line.substr(pos1 + 1, pos2 - pos1 - 1), comment);
246                        else
247                            newsection = new ConfigFileSection(line.substr(pos1 + 1, pos2 - pos1 - 1));
248                        this->sections_.insert(this->sections_.end(), newsection);
249                        continue;
250                    }
251                }
252
253                if (newsection != 0)
254                {
255                    if (isComment(line))
256                    {
257                        // New comment
258                        newsection->getEntries().insert(newsection->getEntries().end(), new ConfigFileEntryComment(removeTrailingWhitespaces(line)));
259                        continue;
260                    }
261                    else
262                    {
263                        size_t pos1 = line.find('=');
264
265                        if (pos1 != std::string::npos && pos1 > 0)
266                        {
267                            // New entry
268                            size_t pos2 = line.find('[');
269                            size_t pos3 = line.find(']');
270                            size_t commentposition = getNextCommentPosition(line, pos1 + 1);
271                            while (isBetweenQuotes(line, commentposition))
272                            {
273                                commentposition = getNextCommentPosition(line, commentposition + 1);
274                            }
275                            std::string value, comment;
276                            if (commentposition == std::string::npos)
277                            {
278                                value = removeTrailingWhitespaces(line.substr(pos1 + 1));
279                            }
280                            else
281                            {
282                                value = removeTrailingWhitespaces(line.substr(pos1 + 1, commentposition - pos1 - 1));
283                                comment = removeTrailingWhitespaces(line.substr(commentposition));
284                            }
285
286                            if (pos2 != std::string::npos && pos3 != std::string::npos && pos3 > pos2 + 1)
287                            {
288                                // There might be an array index
289                                unsigned int index = 0;
290                                if (convertValue(&index, line.substr(pos2 + 1, pos3 - pos2 - 1)))
291                                {
292                                    // New array
293                                    std::list<ConfigFileEntry*>::iterator it = newsection->getOrCreateEntryIterator(getStripped(line.substr(0, pos2)), index, value, false);
294                                    (*it)->setValue(value);
295                                    (*it)->setComment(comment);
296                                    continue;
297                                }
298                            }
299
300                            // New value
301                            newsection->getEntries().insert(newsection->getEntries().end(), new ConfigFileEntryValue(getStripped(line.substr(0, pos1)), value, false, comment));
302                            continue;
303                        }
304                    }
305                }
306            }
307
308            file.close();
309
310            COUT(3) << "Loaded config file \"" << this->filename_ << "\"." << std::endl;
311
312            // DO NOT save the file --> we can open supposedly read only config files
313        } // end file.is_open()
314    }
315
316    void ConfigFile::save() const
317    {
318        this->saveAs(this->filename_);
319    }
320
321    void ConfigFile::saveAs(const std::string& filename) const
322    {
323        boost::filesystem::path filepath(filename);
324        if (!filepath.is_complete())
325            filepath = PathConfig::getConfigPath() / filename;
326        std::ofstream file;
327        file.open(filepath.string().c_str(), std::fstream::out);
328        file.setf(std::ios::fixed, std::ios::floatfield);
329        file.precision(6);
330
331        if (!file.is_open())
332        {
333            COUT(1) << "Error: Couldn't open config-file \"" << filename << "\"." << std::endl;
334            return;
335        }
336
337        for (std::list<ConfigFileSection*>::const_iterator it = this->sections_.begin(); it != this->sections_.end(); ++it)
338        {
339            file << (*it)->getFileEntry() << std::endl;
340
341            for (std::list<ConfigFileEntry*>::const_iterator it_entries = (*it)->getEntriesBegin(); it_entries != (*it)->getEntriesEnd(); ++it_entries)
342                file << (*it_entries)->getFileEntry() << std::endl;
343
344            file << std::endl;
345        }
346
347        file.close();
348
349        COUT(4) << "Saved config file \"" << filename << "\"." << std::endl;
350    }
351
352    void ConfigFile::clear()
353    {
354        for (std::list<ConfigFileSection*>::iterator it = this->sections_.begin(); it != this->sections_.end(); )
355            delete (*(it++));
356        this->sections_.clear();
357    }
358
359    const std::string& ConfigFile::getOrCreateValue(const std::string& section, const std::string& name, const std::string& fallback, bool bString)
360    {
361        const std::string& output = this->getOrCreateSection(section)->getOrCreateValue(name, fallback, bString);
362        this->saveIfUpdated();
363        return output;
364    }
365
366    const std::string& ConfigFile::getOrCreateValue(const std::string& section, const std::string& name, unsigned int index, const std::string& fallback, bool bString)
367    {
368        const std::string& output = this->getOrCreateSection(section)->getOrCreateValue(name, index, fallback, bString);
369        this->saveIfUpdated();
370        return output;
371    }
372
373    void ConfigFile::deleteVectorEntries(const std::string& section, const std::string& name, unsigned int startindex)
374    {
375        if (ConfigFileSection* sectionPtr = this->getSection(section))
376        {
377            sectionPtr->deleteVectorEntries(name, startindex);
378            this->save();
379        }
380    }
381
382    ConfigFileSection* ConfigFile::getSection(const std::string& section) const
383    {
384        for (std::list<ConfigFileSection*>::const_iterator it = this->sections_.begin(); it != this->sections_.end(); ++it)
385            if ((*it)->getName() == section)
386                return (*it);
387        return NULL;
388    }
389
390    ConfigFileSection* ConfigFile::getOrCreateSection(const std::string& section)
391    {
392        for (std::list<ConfigFileSection*>::iterator it = this->sections_.begin(); it != this->sections_.end(); ++it)
393            if ((*it)->getName() == section)
394                return (*it);
395
396        this->bUpdated_ = true;
397
398        return (*this->sections_.insert(this->sections_.end(), new ConfigFileSection(section)));
399    }
400
401    void ConfigFile::saveIfUpdated()
402    {
403        bool sectionsUpdated = false;
404
405        for (std::list<ConfigFileSection*>::iterator it = this->sections_.begin(); it != this->sections_.end(); ++it)
406        {
407            if ((*it)->bUpdated_)
408            {
409                sectionsUpdated = true;
410                (*it)->bUpdated_ = false;
411            }
412        }
413
414        if (this->bUpdated_ || sectionsUpdated)
415        {
416            this->bUpdated_ = false;
417            this->save();
418        }
419    }
420
421
422    ////////////////////////
423    // SettingsConfigFile //
424    ////////////////////////
425
426    SettingsConfigFile* SettingsConfigFile::singletonPtr_s = 0;
427
428    SettingsConfigFile::SettingsConfigFile(const std::string& filename)
429        : ConfigFile(filename)
430    {
431        ConsoleCommand* command = createConsoleCommand(createFunctor(&ConfigFile::load, this), "reloadSettings");
432        CommandExecutor::addConsoleCommandShortcut(command);
433        command = createConsoleCommand(createFunctor(&SettingsConfigFile::setFilename, this), "setSettingsFile");
434        CommandExecutor::addConsoleCommandShortcut(command);
435        command = createConsoleCommand(createFunctor(&SettingsConfigFile::config, this), "config");
436        CommandExecutor::addConsoleCommandShortcut(command).argumentCompleter(0, autocompletion::settingssections()).argumentCompleter(1, autocompletion::settingsentries()).argumentCompleter(2, autocompletion::settingsvalue());
437        command = createConsoleCommand(createFunctor(&SettingsConfigFile::tconfig, this), "tconfig");
438        CommandExecutor::addConsoleCommandShortcut(command).argumentCompleter(0, autocompletion::settingssections()).argumentCompleter(1, autocompletion::settingsentries()).argumentCompleter(2, autocompletion::settingsvalue());
439        command = createConsoleCommand(createFunctor(&SettingsConfigFile::getConfig, this), "getConfig");
440        CommandExecutor::addConsoleCommandShortcut(command).argumentCompleter(0, autocompletion::settingssections()).argumentCompleter(1, autocompletion::settingsentries());
441    }
442
443    SettingsConfigFile::~SettingsConfigFile()
444    {
445    }
446
447    void SettingsConfigFile::load()
448    {
449        ConfigFile::load();
450        this->updateConfigValues();
451    }
452
453    void SettingsConfigFile::setFilename(const std::string& filename)
454    {
455        ConfigFileManager::getInstance().setFilename(ConfigFileType::Settings, filename);
456    }
457
458    void SettingsConfigFile::addConfigValueContainer(ConfigValueContainer* container)
459    {
460        if (container == NULL)
461            return;
462        std::pair<std::string, ConfigValueContainer*> second(getLowercase(container->getName()), container);
463        this->containers_.insert(std::make_pair(getLowercase(container->getSectionName()), second));
464        this->sectionNames_.insert(container->getSectionName());
465    }
466
467    void SettingsConfigFile::removeConfigValueContainer(ConfigValueContainer* container)
468    {
469        if (container == NULL)
470            return;
471        const std::string& sectionLC = getLowercase(container->getSectionName());
472        ContainerMap::iterator upper = this->containers_.upper_bound(sectionLC);
473        for (ContainerMap::iterator it = this->containers_.lower_bound(sectionLC); it != upper; ++it)
474        {
475            if (it->second.second == container)
476            {
477                // Remove entry from section name set this was the last container for that section
478                if (upper == this->containers_.lower_bound(sectionLC))
479                    this->sectionNames_.erase(container->getSectionName());
480                this->containers_.erase(it);
481                break;
482            }
483        }
484    }
485
486    void SettingsConfigFile::updateConfigValues()
487    {
488        for (ContainerMap::const_iterator it = this->containers_.begin(); it != this->containers_.end(); ++it)
489        {
490            it->second.second->update();
491            it->second.second->getIdentifier()->updateConfigValues();
492        }
493    }
494
495    void SettingsConfigFile::clean(bool bCleanComments)
496    {
497        for (std::list<ConfigFileSection*>::iterator itSection = this->sections_.begin(); itSection != this->sections_.end(); )
498        {
499            const std::string& sectionLC = getLowercase((*itSection)->getName());
500            ContainerMap::const_iterator lower = this->containers_.lower_bound(sectionLC);
501            ContainerMap::const_iterator upper = this->containers_.upper_bound(sectionLC);
502            if (lower != upper)
503            {
504                // The section exists, delete comment
505                if (bCleanComments)
506                    (*itSection)->setComment("");
507                for (std::list<ConfigFileEntry*>::iterator itEntry = (*itSection)->entries_.begin(); itEntry != (*itSection)->entries_.end(); )
508                {
509                    const std::string& entryLC = getLowercase((*itEntry)->getName());
510                    bool bFound = false;
511                    for (ContainerMap::const_iterator itContainer = lower; itContainer != upper; ++itContainer)
512                    {
513                        if (itContainer->second.first == entryLC)
514                        {
515                            // The config-value exists, delete comment
516                            if (bCleanComments)
517                                (*itEntry)->setComment("");
518                            ++itEntry;
519                            bFound = true;
520                            break;
521                        }
522                    }
523                    if (!bFound)
524                    {
525                        // The config-value doesn't exist
526                        delete (*itEntry);
527                        (*itSection)->entries_.erase(itEntry++);
528                    }
529                }
530                ++itSection;
531            }
532            else
533            {
534                // The section doesn't exist
535                delete (*itSection);
536                this->sections_.erase(itSection++);
537            }
538        }
539
540        // Save the file
541        this->save();
542    }
543
544    bool SettingsConfigFile::config(const std::string& section, const std::string& entry, const std::string& value)
545    {
546        return this->configImpl(section, entry, value, &ConfigValueContainer::set);
547    }
548
549    bool SettingsConfigFile::tconfig(const std::string& section, const std::string& entry, const std::string& value)
550    {
551        return this->configImpl(section, entry, value, &ConfigValueContainer::tset);
552    }
553
554    bool SettingsConfigFile::configImpl(const std::string& section, const std::string& entry, const std::string& value, bool (ConfigValueContainer::*function)(const MultiType&))
555    {
556        const std::string& sectionLC = getLowercase(section);
557        const std::string& entryLC = getLowercase(entry);
558        ContainerMap::iterator upper = this->containers_.upper_bound(sectionLC);
559        for (ContainerMap::iterator it = this->containers_.lower_bound(sectionLC); it != upper; ++it)
560        {
561            // Note: Config value vectors cannot be supported
562            if (it->second.first == entryLC && !it->second.second->isVector())
563            {
564                return (it->second.second->*function)(value);
565            }
566        }
567        return false;
568    }
569
570    std::string SettingsConfigFile::getConfig(const std::string& section, const std::string& entry)
571    {
572        const std::string& sectionLC = getLowercase(section);
573        const std::string& entryLC = getLowercase(entry);
574        ContainerMap::iterator upper = this->containers_.upper_bound(sectionLC);
575        for (ContainerMap::iterator it = this->containers_.lower_bound(sectionLC); it != upper; ++it)
576        {
577            // Note: Config value vectors cannot be supported
578            if (it->second.first == entryLC && ! it->second.second->isVector())
579            {
580                std::string value;
581                it->second.second->getValue<std::string, OrxonoxClass>(&value, NULL);
582                return value;
583            }
584        }
585        return "";
586    }
587
588
589    ///////////////////////
590    // ConfigFileManager //
591    ///////////////////////
592
593    ConfigFileManager* ConfigFileManager::singletonPtr_s = 0;
594
595    ConfigFileManager::ConfigFileManager()
596    {
597        this->configFiles_.assign(NULL);
598    }
599
600    ConfigFileManager::~ConfigFileManager()
601    {
602        for (boost::array<ConfigFile*, 3>::const_iterator it = this->configFiles_.begin(); it != this->configFiles_.end(); ++it)
603            if (*it)
604                delete (*it);
605    }
606
607    void ConfigFileManager::setFilename(ConfigFileType::Value type, const std::string& filename)
608    {
609        if (this->getConfigFile(type))
610            delete this->configFiles_[type];
611        // Create and load config file
612        switch (type)
613        {
614        case ConfigFileType::Settings:
615            this->configFiles_[type] = new SettingsConfigFile(filename);
616            break;
617        case ConfigFileType::JoyStickCalibration:
618        case ConfigFileType::CommandHistory:
619            this->configFiles_[type] = new ConfigFile(filename);
620            break;
621        }
622        this->configFiles_[type]->load();
623    }
624}
Note: See TracBrowser for help on using the repository browser.