Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

ConfigFileManager added backslashes to strings with special chars when saving them into the config file, but didn't remove them while loading. fixed this issue.

  • Property svn:eol-style set to native
File size: 24.5 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 = line.substr(pos1 + 1);
280                            }
281                            else
282                            {
283                                value = line.substr(pos1 + 1, commentposition - pos1 - 1);
284                                comment = removeTrailingWhitespaces(line.substr(commentposition));
285                            }
286
287                            value = removeTrailingWhitespaces(value);
288                            value = removeSlashes(value);
289
290                            if (pos2 != std::string::npos && pos3 != std::string::npos && pos3 > pos2 + 1)
291                            {
292                                // There might be an array index
293                                unsigned int index = 0;
294                                if (convertValue(&index, line.substr(pos2 + 1, pos3 - pos2 - 1)))
295                                {
296                                    // New array
297                                    std::list<ConfigFileEntry*>::iterator it = newsection->getOrCreateEntryIterator(getStripped(line.substr(0, pos2)), index, value, false);
298                                    (*it)->setValue(value);
299                                    (*it)->setComment(comment);
300                                    continue;
301                                }
302                            }
303
304                            // New value
305                            newsection->getEntries().insert(newsection->getEntries().end(), new ConfigFileEntryValue(getStripped(line.substr(0, pos1)), value, false, comment));
306                            continue;
307                        }
308                    }
309                }
310            }
311
312            file.close();
313
314            COUT(3) << "Loaded config file \"" << this->filename_ << "\"." << std::endl;
315
316            // DO NOT save the file --> we can open supposedly read only config files
317        } // end file.is_open()
318    }
319
320    void ConfigFile::save() const
321    {
322        this->saveAs(this->filename_);
323    }
324
325    void ConfigFile::saveAs(const std::string& filename) const
326    {
327        boost::filesystem::path filepath(filename);
328        if (!filepath.is_complete())
329            filepath = PathConfig::getConfigPath() / filename;
330        std::ofstream file;
331        file.open(filepath.string().c_str(), std::fstream::out);
332        file.setf(std::ios::fixed, std::ios::floatfield);
333        file.precision(6);
334
335        if (!file.is_open())
336        {
337            COUT(1) << "Error: Couldn't open config-file \"" << filename << "\"." << std::endl;
338            return;
339        }
340
341        for (std::list<ConfigFileSection*>::const_iterator it = this->sections_.begin(); it != this->sections_.end(); ++it)
342        {
343            file << (*it)->getFileEntry() << std::endl;
344
345            for (std::list<ConfigFileEntry*>::const_iterator it_entries = (*it)->getEntriesBegin(); it_entries != (*it)->getEntriesEnd(); ++it_entries)
346                file << (*it_entries)->getFileEntry() << std::endl;
347
348            file << std::endl;
349        }
350
351        file.close();
352
353        COUT(4) << "Saved config file \"" << filename << "\"." << std::endl;
354    }
355
356    void ConfigFile::clear()
357    {
358        for (std::list<ConfigFileSection*>::iterator it = this->sections_.begin(); it != this->sections_.end(); )
359            delete (*(it++));
360        this->sections_.clear();
361    }
362
363    const std::string& ConfigFile::getOrCreateValue(const std::string& section, const std::string& name, const std::string& fallback, bool bString)
364    {
365        const std::string& output = this->getOrCreateSection(section)->getOrCreateValue(name, fallback, bString);
366        this->saveIfUpdated();
367        return output;
368    }
369
370    const std::string& ConfigFile::getOrCreateValue(const std::string& section, const std::string& name, unsigned int index, const std::string& fallback, bool bString)
371    {
372        const std::string& output = this->getOrCreateSection(section)->getOrCreateValue(name, index, fallback, bString);
373        this->saveIfUpdated();
374        return output;
375    }
376
377    void ConfigFile::deleteVectorEntries(const std::string& section, const std::string& name, unsigned int startindex)
378    {
379        if (ConfigFileSection* sectionPtr = this->getSection(section))
380        {
381            sectionPtr->deleteVectorEntries(name, startindex);
382            this->save();
383        }
384    }
385
386    ConfigFileSection* ConfigFile::getSection(const std::string& section) const
387    {
388        for (std::list<ConfigFileSection*>::const_iterator it = this->sections_.begin(); it != this->sections_.end(); ++it)
389            if ((*it)->getName() == section)
390                return (*it);
391        return NULL;
392    }
393
394    ConfigFileSection* ConfigFile::getOrCreateSection(const std::string& section)
395    {
396        for (std::list<ConfigFileSection*>::iterator it = this->sections_.begin(); it != this->sections_.end(); ++it)
397            if ((*it)->getName() == section)
398                return (*it);
399
400        this->bUpdated_ = true;
401
402        return (*this->sections_.insert(this->sections_.end(), new ConfigFileSection(section)));
403    }
404
405    void ConfigFile::saveIfUpdated()
406    {
407        bool sectionsUpdated = false;
408
409        for (std::list<ConfigFileSection*>::iterator it = this->sections_.begin(); it != this->sections_.end(); ++it)
410        {
411            if ((*it)->bUpdated_)
412            {
413                sectionsUpdated = true;
414                (*it)->bUpdated_ = false;
415            }
416        }
417
418        if (this->bUpdated_ || sectionsUpdated)
419        {
420            this->bUpdated_ = false;
421            this->save();
422        }
423    }
424
425
426    ////////////////////////
427    // SettingsConfigFile //
428    ////////////////////////
429
430    static const std::string __CC_load_name = "reloadSettings";
431    static const std::string __CC_setFilename_name = "setSettingsFile";
432    static const std::string __CC_config_name = "config";
433    static const std::string __CC_tconfig_name = "tconfig";
434    static const std::string __CC_getConfig_name = "getConfig";
435
436    SetConsoleCommand(__CC_load_name,            &ConfigFile::load);
437    SetConsoleCommand(__CC_setFilename_name,     &SettingsConfigFile::setFilename);
438    SetConsoleCommand(__CC_config_name,          &SettingsConfigFile::config).argumentCompleter(0, autocompletion::settingssections()).argumentCompleter(1, autocompletion::settingsentries()).argumentCompleter(2, autocompletion::settingsvalue());
439    SetConsoleCommand(__CC_tconfig_name,         &SettingsConfigFile::tconfig).argumentCompleter(0, autocompletion::settingssections()).argumentCompleter(1, autocompletion::settingsentries()).argumentCompleter(2, autocompletion::settingsvalue());
440    SetConsoleCommand(__CC_getConfig_name,       &SettingsConfigFile::getConfig).argumentCompleter(0, autocompletion::settingssections()).argumentCompleter(1, autocompletion::settingsentries());
441
442    SettingsConfigFile* SettingsConfigFile::singletonPtr_s = 0;
443
444    SettingsConfigFile::SettingsConfigFile(const std::string& filename)
445        : ConfigFile(filename)
446    {
447        ModifyConsoleCommand(__CC_load_name).setObject(this);
448        ModifyConsoleCommand(__CC_setFilename_name).setObject(this);
449        ModifyConsoleCommand(__CC_config_name).setObject(this);
450        ModifyConsoleCommand(__CC_tconfig_name).setObject(this);
451        ModifyConsoleCommand(__CC_getConfig_name).setObject(this);
452    }
453
454    SettingsConfigFile::~SettingsConfigFile()
455    {
456        ModifyConsoleCommand(__CC_load_name).setObject(0);
457        ModifyConsoleCommand(__CC_setFilename_name).setObject(0);
458        ModifyConsoleCommand(__CC_config_name).setObject(0);
459        ModifyConsoleCommand(__CC_tconfig_name).setObject(0);
460        ModifyConsoleCommand(__CC_getConfig_name).setObject(0);
461    }
462
463    void SettingsConfigFile::load()
464    {
465        ConfigFile::load();
466        this->updateConfigValues();
467    }
468
469    void SettingsConfigFile::setFilename(const std::string& filename)
470    {
471        ConfigFileManager::getInstance().setFilename(ConfigFileType::Settings, filename);
472    }
473
474    void SettingsConfigFile::addConfigValueContainer(ConfigValueContainer* container)
475    {
476        if (container == NULL)
477            return;
478        std::pair<std::string, ConfigValueContainer*> second(getLowercase(container->getName()), container);
479        this->containers_.insert(std::make_pair(getLowercase(container->getSectionName()), second));
480        this->sectionNames_.insert(container->getSectionName());
481    }
482
483    void SettingsConfigFile::removeConfigValueContainer(ConfigValueContainer* container)
484    {
485        if (container == NULL)
486            return;
487        const std::string& sectionLC = getLowercase(container->getSectionName());
488        ContainerMap::iterator upper = this->containers_.upper_bound(sectionLC);
489        for (ContainerMap::iterator it = this->containers_.lower_bound(sectionLC); it != upper; ++it)
490        {
491            if (it->second.second == container)
492            {
493                // Remove entry from section name set this was the last container for that section
494                if (upper == this->containers_.lower_bound(sectionLC))
495                    this->sectionNames_.erase(container->getSectionName());
496                this->containers_.erase(it);
497                break;
498            }
499        }
500    }
501
502    void SettingsConfigFile::updateConfigValues()
503    {
504        for (ContainerMap::const_iterator it = this->containers_.begin(); it != this->containers_.end(); ++it)
505        {
506            it->second.second->update();
507            it->second.second->getIdentifier()->updateConfigValues();
508        }
509    }
510
511    void SettingsConfigFile::clean(bool bCleanComments)
512    {
513        for (std::list<ConfigFileSection*>::iterator itSection = this->sections_.begin(); itSection != this->sections_.end(); )
514        {
515            const std::string& sectionLC = getLowercase((*itSection)->getName());
516            ContainerMap::const_iterator lower = this->containers_.lower_bound(sectionLC);
517            ContainerMap::const_iterator upper = this->containers_.upper_bound(sectionLC);
518            if (lower != upper)
519            {
520                // The section exists, delete comment
521                if (bCleanComments)
522                    (*itSection)->setComment("");
523                for (std::list<ConfigFileEntry*>::iterator itEntry = (*itSection)->entries_.begin(); itEntry != (*itSection)->entries_.end(); )
524                {
525                    const std::string& entryLC = getLowercase((*itEntry)->getName());
526                    bool bFound = false;
527                    for (ContainerMap::const_iterator itContainer = lower; itContainer != upper; ++itContainer)
528                    {
529                        if (itContainer->second.first == entryLC)
530                        {
531                            // The config-value exists, delete comment
532                            if (bCleanComments)
533                                (*itEntry)->setComment("");
534                            ++itEntry;
535                            bFound = true;
536                            break;
537                        }
538                    }
539                    if (!bFound)
540                    {
541                        // The config-value doesn't exist
542                        delete (*itEntry);
543                        (*itSection)->entries_.erase(itEntry++);
544                    }
545                }
546                ++itSection;
547            }
548            else
549            {
550                // The section doesn't exist
551                delete (*itSection);
552                this->sections_.erase(itSection++);
553            }
554        }
555
556        // Save the file
557        this->save();
558    }
559
560    void SettingsConfigFile::config(const std::string& section, const std::string& entry, const std::string& value)
561    {
562        if (!this->configImpl(section, entry, value, &ConfigValueContainer::set))
563            COUT(1) << "Error: Config value \"" << entry << "\" in section \"" << section << "\" doesn't exist." << std::endl;
564    }
565
566    void SettingsConfigFile::tconfig(const std::string& section, const std::string& entry, const std::string& value)
567    {
568        if (!this->configImpl(section, entry, value, &ConfigValueContainer::tset))
569            COUT(1) << "Error: Config value \"" << entry << "\" in section \"" << section << "\" doesn't exist." << std::endl;
570    }
571
572    bool SettingsConfigFile::configImpl(const std::string& section, const std::string& entry, const std::string& value, bool (ConfigValueContainer::*function)(const MultiType&))
573    {
574        const std::string& sectionLC = getLowercase(section);
575        const std::string& entryLC = getLowercase(entry);
576        ContainerMap::iterator upper = this->containers_.upper_bound(sectionLC);
577        for (ContainerMap::iterator it = this->containers_.lower_bound(sectionLC); it != upper; ++it)
578        {
579            // Note: Config value vectors cannot be supported
580            if (it->second.first == entryLC && !it->second.second->isVector())
581            {
582                return (it->second.second->*function)(value);
583            }
584        }
585        return false;
586    }
587
588    std::string SettingsConfigFile::getConfig(const std::string& section, const std::string& entry)
589    {
590        const std::string& sectionLC = getLowercase(section);
591        const std::string& entryLC = getLowercase(entry);
592        ContainerMap::iterator upper = this->containers_.upper_bound(sectionLC);
593        for (ContainerMap::iterator it = this->containers_.lower_bound(sectionLC); it != upper; ++it)
594        {
595            // Note: Config value vectors cannot be supported
596            if (it->second.first == entryLC && ! it->second.second->isVector())
597            {
598                std::string value;
599                it->second.second->getValue<std::string, OrxonoxClass>(&value, NULL);
600                return value;
601            }
602        }
603        return "";
604    }
605
606
607    ///////////////////////
608    // ConfigFileManager //
609    ///////////////////////
610
611    ConfigFileManager* ConfigFileManager::singletonPtr_s = 0;
612
613    ConfigFileManager::ConfigFileManager()
614    {
615        this->configFiles_.assign(NULL);
616    }
617
618    ConfigFileManager::~ConfigFileManager()
619    {
620        for (boost::array<ConfigFile*, 3>::const_iterator it = this->configFiles_.begin(); it != this->configFiles_.end(); ++it)
621            if (*it)
622                delete (*it);
623    }
624
625    void ConfigFileManager::setFilename(ConfigFileType::Value type, const std::string& filename)
626    {
627        if (this->getConfigFile(type))
628            delete this->configFiles_[type];
629        // Create and load config file
630        switch (type)
631        {
632        case ConfigFileType::Settings:
633            this->configFiles_[type] = new SettingsConfigFile(filename);
634            break;
635        case ConfigFileType::JoyStickCalibration:
636        case ConfigFileType::CommandHistory:
637            this->configFiles_[type] = new ConfigFile(filename);
638            break;
639        }
640        this->configFiles_[type]->load();
641    }
642}
Note: See TracBrowser for help on using the repository browser.