Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/gamestate/src/libraries/core/ConfigFileManager.cc @ 6698

Last change on this file since 6698 was 6437, checked in by rgrieder, 15 years ago

Added support for keybindings.ini merging:
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.