Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 7232 was 7163, checked in by dafrick, 14 years ago

Merged presentation3 branch into trunk.

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