Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/consolecommands3/src/libraries/core/ConfigFileManager.cc @ 7236

Last change on this file since 7236 was 7236, checked in by landauf, 14 years ago

replaced the temporary names of all ConsoleCommand related classes and functions by their real names

  • Property svn:eol-style set to native
File size: 24.4 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 "ConfigValueContainer.h"
37#include "PathConfig.h"
38#include "command/ConsoleCommand.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    static const std::string __CC_load_name = "reloadSettings";
428    static const std::string __CC_setFilename_name = "setSettingsFile";
429    static const std::string __CC_config_name = "config";
430    static const std::string __CC_tconfig_name = "tconfig";
431    static const std::string __CC_getConfig_name = "getConfig";
432
433    SetConsoleCommand(__CC_load_name,            &ConfigFile::load);
434    SetConsoleCommand(__CC_setFilename_name,     &SettingsConfigFile::setFilename);
435    SetConsoleCommand(__CC_config_name,          &SettingsConfigFile::config).argumentCompleter(0, autocompletion::settingssections()).argumentCompleter(1, autocompletion::settingsentries()).argumentCompleter(2, autocompletion::settingsvalue());
436    SetConsoleCommand(__CC_tconfig_name,         &SettingsConfigFile::tconfig).argumentCompleter(0, autocompletion::settingssections()).argumentCompleter(1, autocompletion::settingsentries()).argumentCompleter(2, autocompletion::settingsvalue());
437    SetConsoleCommand(__CC_getConfig_name,       &SettingsConfigFile::getConfig).argumentCompleter(0, autocompletion::settingssections()).argumentCompleter(1, autocompletion::settingsentries());
438
439    SettingsConfigFile* SettingsConfigFile::singletonPtr_s = 0;
440
441    SettingsConfigFile::SettingsConfigFile(const std::string& filename)
442        : ConfigFile(filename)
443    {
444        ModifyConsoleCommand(__CC_load_name).setObject(this);
445        ModifyConsoleCommand(__CC_setFilename_name).setObject(this);
446        ModifyConsoleCommand(__CC_config_name).setObject(this);
447        ModifyConsoleCommand(__CC_tconfig_name).setObject(this);
448        ModifyConsoleCommand(__CC_getConfig_name).setObject(this);
449    }
450
451    SettingsConfigFile::~SettingsConfigFile()
452    {
453        ModifyConsoleCommand(__CC_load_name).setObject(0);
454        ModifyConsoleCommand(__CC_setFilename_name).setObject(0);
455        ModifyConsoleCommand(__CC_config_name).setObject(0);
456        ModifyConsoleCommand(__CC_tconfig_name).setObject(0);
457        ModifyConsoleCommand(__CC_getConfig_name).setObject(0);
458    }
459
460    void SettingsConfigFile::load()
461    {
462        ConfigFile::load();
463        this->updateConfigValues();
464    }
465
466    void SettingsConfigFile::setFilename(const std::string& filename)
467    {
468        ConfigFileManager::getInstance().setFilename(ConfigFileType::Settings, filename);
469    }
470
471    void SettingsConfigFile::addConfigValueContainer(ConfigValueContainer* container)
472    {
473        if (container == NULL)
474            return;
475        std::pair<std::string, ConfigValueContainer*> second(getLowercase(container->getName()), container);
476        this->containers_.insert(std::make_pair(getLowercase(container->getSectionName()), second));
477        this->sectionNames_.insert(container->getSectionName());
478    }
479
480    void SettingsConfigFile::removeConfigValueContainer(ConfigValueContainer* container)
481    {
482        if (container == NULL)
483            return;
484        const std::string& sectionLC = getLowercase(container->getSectionName());
485        ContainerMap::iterator upper = this->containers_.upper_bound(sectionLC);
486        for (ContainerMap::iterator it = this->containers_.lower_bound(sectionLC); it != upper; ++it)
487        {
488            if (it->second.second == container)
489            {
490                // Remove entry from section name set this was the last container for that section
491                if (upper == this->containers_.lower_bound(sectionLC))
492                    this->sectionNames_.erase(container->getSectionName());
493                this->containers_.erase(it);
494                break;
495            }
496        }
497    }
498
499    void SettingsConfigFile::updateConfigValues()
500    {
501        for (ContainerMap::const_iterator it = this->containers_.begin(); it != this->containers_.end(); ++it)
502        {
503            it->second.second->update();
504            it->second.second->getIdentifier()->updateConfigValues();
505        }
506    }
507
508    void SettingsConfigFile::clean(bool bCleanComments)
509    {
510        for (std::list<ConfigFileSection*>::iterator itSection = this->sections_.begin(); itSection != this->sections_.end(); )
511        {
512            const std::string& sectionLC = getLowercase((*itSection)->getName());
513            ContainerMap::const_iterator lower = this->containers_.lower_bound(sectionLC);
514            ContainerMap::const_iterator upper = this->containers_.upper_bound(sectionLC);
515            if (lower != upper)
516            {
517                // The section exists, delete comment
518                if (bCleanComments)
519                    (*itSection)->setComment("");
520                for (std::list<ConfigFileEntry*>::iterator itEntry = (*itSection)->entries_.begin(); itEntry != (*itSection)->entries_.end(); )
521                {
522                    const std::string& entryLC = getLowercase((*itEntry)->getName());
523                    bool bFound = false;
524                    for (ContainerMap::const_iterator itContainer = lower; itContainer != upper; ++itContainer)
525                    {
526                        if (itContainer->second.first == entryLC)
527                        {
528                            // The config-value exists, delete comment
529                            if (bCleanComments)
530                                (*itEntry)->setComment("");
531                            ++itEntry;
532                            bFound = true;
533                            break;
534                        }
535                    }
536                    if (!bFound)
537                    {
538                        // The config-value doesn't exist
539                        delete (*itEntry);
540                        (*itSection)->entries_.erase(itEntry++);
541                    }
542                }
543                ++itSection;
544            }
545            else
546            {
547                // The section doesn't exist
548                delete (*itSection);
549                this->sections_.erase(itSection++);
550            }
551        }
552
553        // Save the file
554        this->save();
555    }
556
557    void SettingsConfigFile::config(const std::string& section, const std::string& entry, const std::string& value)
558    {
559        if (!this->configImpl(section, entry, value, &ConfigValueContainer::set))
560            COUT(1) << "Error: Config value \"" << entry << "\" in section \"" << section << "\" doesn't exist." << std::endl;
561    }
562
563    void SettingsConfigFile::tconfig(const std::string& section, const std::string& entry, const std::string& value)
564    {
565        if (!this->configImpl(section, entry, value, &ConfigValueContainer::tset))
566            COUT(1) << "Error: Config value \"" << entry << "\" in section \"" << section << "\" doesn't exist." << std::endl;
567    }
568
569    bool SettingsConfigFile::configImpl(const std::string& section, const std::string& entry, const std::string& value, bool (ConfigValueContainer::*function)(const MultiType&))
570    {
571        const std::string& sectionLC = getLowercase(section);
572        const std::string& entryLC = getLowercase(entry);
573        ContainerMap::iterator upper = this->containers_.upper_bound(sectionLC);
574        for (ContainerMap::iterator it = this->containers_.lower_bound(sectionLC); it != upper; ++it)
575        {
576            // Note: Config value vectors cannot be supported
577            if (it->second.first == entryLC && !it->second.second->isVector())
578            {
579                return (it->second.second->*function)(value);
580            }
581        }
582        return false;
583    }
584
585    std::string SettingsConfigFile::getConfig(const std::string& section, const std::string& entry)
586    {
587        const std::string& sectionLC = getLowercase(section);
588        const std::string& entryLC = getLowercase(entry);
589        ContainerMap::iterator upper = this->containers_.upper_bound(sectionLC);
590        for (ContainerMap::iterator it = this->containers_.lower_bound(sectionLC); it != upper; ++it)
591        {
592            // Note: Config value vectors cannot be supported
593            if (it->second.first == entryLC && ! it->second.second->isVector())
594            {
595                std::string value;
596                it->second.second->getValue<std::string, OrxonoxClass>(&value, NULL);
597                return value;
598            }
599        }
600        return "";
601    }
602
603
604    ///////////////////////
605    // ConfigFileManager //
606    ///////////////////////
607
608    ConfigFileManager* ConfigFileManager::singletonPtr_s = 0;
609
610    ConfigFileManager::ConfigFileManager()
611    {
612        this->configFiles_.assign(NULL);
613    }
614
615    ConfigFileManager::~ConfigFileManager()
616    {
617        for (boost::array<ConfigFile*, 3>::const_iterator it = this->configFiles_.begin(); it != this->configFiles_.end(); ++it)
618            if (*it)
619                delete (*it);
620    }
621
622    void ConfigFileManager::setFilename(ConfigFileType::Value type, const std::string& filename)
623    {
624        if (this->getConfigFile(type))
625            delete this->configFiles_[type];
626        // Create and load config file
627        switch (type)
628        {
629        case ConfigFileType::Settings:
630            this->configFiles_[type] = new SettingsConfigFile(filename);
631            break;
632        case ConfigFileType::JoyStickCalibration:
633        case ConfigFileType::CommandHistory:
634            this->configFiles_[type] = new ConfigFile(filename);
635            break;
636        }
637        this->configFiles_[type]->load();
638    }
639}
Note: See TracBrowser for help on using the repository browser.