00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 #include "common/config-manager.h"
00024 #include "common/debug.h"
00025 #include "common/file.h"
00026 #include "common/fs.h"
00027 #include "common/system.h"
00028 #include "common/textconsole.h"
00029
00030 static bool isValidDomainName(const Common::String &domName) {
00031 const char *p = domName.c_str();
00032 while (*p && (Common::isAlnum(*p) || *p == '-' || *p == '_'))
00033 p++;
00034 return *p == 0;
00035 }
00036
00037 namespace Common {
00038
00039 DECLARE_SINGLETON(ConfigManager);
00040
00041 char const *const ConfigManager::kApplicationDomain = "residualvm";
00042 char const *const ConfigManager::kTransientDomain = "__TRANSIENT";
00043
00044 #ifdef ENABLE_KEYMAPPER
00045 char const *const ConfigManager::kKeymapperDomain = "keymapper";
00046 #endif
00047
00048 #ifdef USE_CLOUD
00049 char const *const ConfigManager::kCloudDomain = "cloud";
00050 #endif
00051
00052 #pragma mark -
00053
00054
00055 ConfigManager::ConfigManager() : _activeDomain(nullptr) {
00056 }
00057
00058 void ConfigManager::defragment() {
00059 ConfigManager *newInstance = new ConfigManager();
00060 newInstance->copyFrom(*_singleton);
00061 delete _singleton;
00062 _singleton = newInstance;
00063 }
00064
00065 void ConfigManager::copyFrom(ConfigManager &source) {
00066 _transientDomain = source._transientDomain;
00067 _gameDomains = source._gameDomains;
00068 _miscDomains = source._miscDomains;
00069 _appDomain = source._appDomain;
00070 _defaultsDomain = source._defaultsDomain;
00071 #ifdef ENABLE_KEYMAPPER
00072 _keymapperDomain = source._keymapperDomain;
00073 #endif
00074 #ifdef USE_CLOUD
00075 _cloudDomain = source._cloudDomain;
00076 #endif
00077 _domainSaveOrder = source._domainSaveOrder;
00078 _activeDomainName = source._activeDomainName;
00079 _activeDomain = &_gameDomains[_activeDomainName];
00080 _filename = source._filename;
00081 }
00082
00083
00084 void ConfigManager::loadDefaultConfigFile() {
00085
00086 assert(g_system);
00087 SeekableReadStream *stream = g_system->createConfigReadStream();
00088 _filename.clear();
00089
00090
00091 if (stream) {
00092 loadFromStream(*stream);
00093
00094
00095 delete stream;
00096
00097 } else {
00098
00099 debug("Default configuration file missing, creating a new one");
00100
00101 flushToDisk();
00102 }
00103 }
00104
00105 void ConfigManager::loadConfigFile(const String &filename) {
00106 _filename = filename;
00107
00108 FSNode node(filename);
00109 File cfg_file;
00110 if (!cfg_file.open(node)) {
00111 debug("Creating configuration file: %s", filename.c_str());
00112 } else {
00113 debug("Using configuration file: %s", _filename.c_str());
00114 loadFromStream(cfg_file);
00115 }
00116 }
00117
00122 void ConfigManager::addDomain(const String &domainName, const ConfigManager::Domain &domain) {
00123 if (domainName.empty())
00124 return;
00125 if (domainName == kApplicationDomain) {
00126 _appDomain = domain;
00127 #ifdef ENABLE_KEYMAPPER
00128 } else if (domainName == kKeymapperDomain) {
00129 _keymapperDomain = domain;
00130 #endif
00131 #ifdef USE_CLOUD
00132 } else if (domainName == kCloudDomain) {
00133 _cloudDomain = domain;
00134 #endif
00135 } else if (domain.contains("gameid")) {
00136
00137 if (_gameDomains.contains(domainName))
00138 warning("Game domain %s already exists in ConfigManager", domainName.c_str());
00139
00140 _gameDomains[domainName] = domain;
00141
00142 _domainSaveOrder.push_back(domainName);
00143
00144
00145
00146
00147 if (_miscDomains.contains(domainName))
00148 _miscDomains.erase(domainName);
00149 } else {
00150
00151 if (_miscDomains.contains(domainName))
00152 warning("Misc domain %s already exists in ConfigManager", domainName.c_str());
00153
00154 _miscDomains[domainName] = domain;
00155 }
00156 }
00157
00158
00159 void ConfigManager::loadFromStream(SeekableReadStream &stream) {
00160 String domainName;
00161 String comment;
00162 Domain domain;
00163 int lineno = 0;
00164
00165 _appDomain.clear();
00166 _gameDomains.clear();
00167 _miscDomains.clear();
00168 _transientDomain.clear();
00169 _domainSaveOrder.clear();
00170
00171 #ifdef ENABLE_KEYMAPPER
00172 _keymapperDomain.clear();
00173 #endif
00174 #ifdef USE_CLOUD
00175 _cloudDomain.clear();
00176 #endif
00177
00178
00179
00180
00181 while (!stream.eos() && !stream.err()) {
00182 lineno++;
00183
00184
00185 String line = stream.readLine();
00186
00187 if (line.size() == 0) {
00188
00189 } else if (line[0] == '#') {
00190
00191
00192
00193 comment += line;
00194 comment += "\n";
00195 } else if (line[0] == '[') {
00196
00197
00198 addDomain(domainName, domain);
00199 domain.clear();
00200 const char *p = line.c_str() + 1;
00201
00202
00203
00204 while (*p && (isAlnum(*p) || *p == '-' || *p == '_'))
00205 p++;
00206
00207 if (*p == '\0')
00208 error("Config file buggy: missing ] in line %d", lineno);
00209 else if (*p != ']')
00210 error("Config file buggy: Invalid character '%c' occurred in section name in line %d", *p, lineno);
00211
00212 domainName = String(line.c_str() + 1, p);
00213
00214 domain.setDomainComment(comment);
00215 comment.clear();
00216
00217 } else {
00218
00219
00220
00221 const char *t = line.c_str();
00222 while (isSpace(*t))
00223 t++;
00224
00225
00226 if (*t == 0)
00227 continue;
00228
00229
00230 if (domainName.empty()) {
00231 error("Config file buggy: Key/value pair found outside a domain in line %d", lineno);
00232 }
00233
00234
00235 const char *p = strchr(t, '=');
00236 if (!p)
00237 error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
00238
00239
00240 String key(t, p);
00241 String value(p + 1);
00242
00243
00244 key.trim();
00245 value.trim();
00246
00247
00248 domain[key] = value;
00249
00250
00251 domain.setKVComment(key, comment);
00252 comment.clear();
00253 }
00254 }
00255
00256 addDomain(domainName, domain);
00257 }
00258
00259 void ConfigManager::flushToDisk() {
00260 #ifndef __DC__
00261 WriteStream *stream;
00262
00263 if (_filename.empty()) {
00264
00265 assert(g_system);
00266 stream = g_system->createConfigWriteStream();
00267 if (!stream)
00268 return;
00269 } else {
00270 DumpFile *dump = new DumpFile();
00271 assert(dump);
00272
00273 if (!dump->open(_filename)) {
00274 warning("Unable to write configuration file: %s", _filename.c_str());
00275 delete dump;
00276 return;
00277 }
00278
00279 stream = dump;
00280 }
00281
00282
00283 writeDomain(*stream, kApplicationDomain, _appDomain);
00284
00285 #ifdef ENABLE_KEYMAPPER
00286
00287 writeDomain(*stream, kKeymapperDomain, _keymapperDomain);
00288 #endif
00289 #ifdef USE_CLOUD
00290
00291 writeDomain(*stream, kCloudDomain, _cloudDomain);
00292 #endif
00293
00294 DomainMap::const_iterator d;
00295
00296
00297 for (d = _miscDomains.begin(); d != _miscDomains.end(); ++d) {
00298 writeDomain(*stream, d->_key, d->_value);
00299 }
00300
00301
00302
00303
00304 Array<String>::const_iterator i;
00305 for (i = _domainSaveOrder.begin(); i != _domainSaveOrder.end(); ++i) {
00306 if (_gameDomains.contains(*i)) {
00307 writeDomain(*stream, *i, _gameDomains[*i]);
00308 }
00309 }
00310
00311
00312 for (d = _gameDomains.begin(); d != _gameDomains.end(); ++d) {
00313 if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), d->_key) == _domainSaveOrder.end())
00314 writeDomain(*stream, d->_key, d->_value);
00315 }
00316
00317 delete stream;
00318
00319 #endif // !__DC__
00320 }
00321
00322 void ConfigManager::writeDomain(WriteStream &stream, const String &name, const Domain &domain) {
00323 if (domain.empty())
00324 return;
00325
00326
00327
00328
00329 if (domain.contains("id_came_from_command_line"))
00330 return;
00331
00332 String comment;
00333
00334
00335 comment = domain.getDomainComment();
00336 if (!comment.empty())
00337 stream.writeString(comment);
00338
00339
00340 stream.writeByte('[');
00341 stream.writeString(name);
00342 stream.writeByte(']');
00343 stream.writeByte('\n');
00344
00345
00346 Domain::const_iterator x;
00347 for (x = domain.begin(); x != domain.end(); ++x) {
00348 if (!x->_value.empty()) {
00349
00350 if (domain.hasKVComment(x->_key)) {
00351 comment = domain.getKVComment(x->_key);
00352 stream.writeString(comment);
00353 }
00354
00355 stream.writeString(x->_key);
00356 stream.writeByte('=');
00357 stream.writeString(x->_value);
00358 stream.writeByte('\n');
00359 }
00360 }
00361 stream.writeByte('\n');
00362 }
00363
00364
00365 #pragma mark -
00366
00367
00368 const ConfigManager::Domain *ConfigManager::getDomain(const String &domName) const {
00369 assert(!domName.empty());
00370 assert(isValidDomainName(domName));
00371
00372 if (domName == kTransientDomain)
00373 return &_transientDomain;
00374 if (domName == kApplicationDomain)
00375 return &_appDomain;
00376 #ifdef ENABLE_KEYMAPPER
00377 if (domName == kKeymapperDomain)
00378 return &_keymapperDomain;
00379 #endif
00380 #ifdef USE_CLOUD
00381 if (domName == kCloudDomain)
00382 return &_cloudDomain;
00383 #endif
00384 if (_gameDomains.contains(domName))
00385 return &_gameDomains[domName];
00386 if (_miscDomains.contains(domName))
00387 return &_miscDomains[domName];
00388
00389 return nullptr;
00390 }
00391
00392 ConfigManager::Domain *ConfigManager::getDomain(const String &domName) {
00393 assert(!domName.empty());
00394 assert(isValidDomainName(domName));
00395
00396 if (domName == kTransientDomain)
00397 return &_transientDomain;
00398 if (domName == kApplicationDomain)
00399 return &_appDomain;
00400 #ifdef ENABLE_KEYMAPPER
00401 if (domName == kKeymapperDomain)
00402 return &_keymapperDomain;
00403 #endif
00404 #ifdef USE_CLOUD
00405 if (domName == kCloudDomain)
00406 return &_cloudDomain;
00407 #endif
00408 if (_gameDomains.contains(domName))
00409 return &_gameDomains[domName];
00410 if (_miscDomains.contains(domName))
00411 return &_miscDomains[domName];
00412
00413 return nullptr;
00414 }
00415
00416
00417 #pragma mark -
00418
00419
00420 bool ConfigManager::hasKey(const String &key) const {
00421
00422
00423
00424
00425
00426
00427 if (_transientDomain.contains(key))
00428 return true;
00429
00430 if (_activeDomain && _activeDomain->contains(key))
00431 return true;
00432
00433 if (_appDomain.contains(key))
00434 return true;
00435
00436 return false;
00437 }
00438
00439 bool ConfigManager::hasKey(const String &key, const String &domName) const {
00440
00441
00442
00443 if (domName.empty())
00444 return hasKey(key);
00445
00446 const Domain *domain = getDomain(domName);
00447
00448 if (!domain)
00449 return false;
00450 return domain->contains(key);
00451 }
00452
00453 void ConfigManager::removeKey(const String &key, const String &domName) {
00454 Domain *domain = getDomain(domName);
00455
00456 if (!domain)
00457 error("ConfigManager::removeKey(%s, %s) called on non-existent domain",
00458 key.c_str(), domName.c_str());
00459
00460 domain->erase(key);
00461 }
00462
00463
00464 #pragma mark -
00465
00466
00467 const String &ConfigManager::get(const String &key) const {
00468 if (_transientDomain.contains(key))
00469 return _transientDomain[key];
00470 else if (_activeDomain && _activeDomain->contains(key))
00471 return (*_activeDomain)[key];
00472 else if (_appDomain.contains(key))
00473 return _appDomain[key];
00474
00475 return _defaultsDomain.getVal(key);
00476 }
00477
00478 const String &ConfigManager::get(const String &key, const String &domName) const {
00479
00480
00481
00482 if (domName.empty())
00483 return get(key);
00484
00485 const Domain *domain = getDomain(domName);
00486
00487 if (!domain)
00488 error("ConfigManager::get(%s,%s) called on non-existent domain",
00489 key.c_str(), domName.c_str());
00490
00491 if (domain->contains(key))
00492 return (*domain)[key];
00493
00494 return _defaultsDomain.getVal(key);
00495 }
00496
00497 int ConfigManager::getInt(const String &key, const String &domName) const {
00498 String value(get(key, domName));
00499 char *errpos;
00500
00501
00502
00503
00504 if (value.empty())
00505 return 0;
00506
00507
00508
00509
00510 int ivalue = (int)strtol(value.c_str(), &errpos, 0);
00511 if (value.c_str() == errpos)
00512 error("ConfigManager::getInt(%s,%s): '%s' is not a valid integer",
00513 key.c_str(), domName.c_str(), errpos);
00514
00515 return ivalue;
00516 }
00517
00518 bool ConfigManager::getBool(const String &key, const String &domName) const {
00519 String value(get(key, domName));
00520 bool val;
00521 if (parseBool(value, val))
00522 return val;
00523
00524 error("ConfigManager::getBool(%s,%s): '%s' is not a valid bool",
00525 key.c_str(), domName.c_str(), value.c_str());
00526 }
00527
00528
00529 #pragma mark -
00530
00531
00532 void ConfigManager::set(const String &key, const String &value) {
00533
00534 _transientDomain.erase(key);
00535
00536
00537
00538 if (_activeDomain)
00539 (*_activeDomain)[key] = value;
00540 else
00541 _appDomain[key] = value;
00542 }
00543
00544 void ConfigManager::set(const String &key, const String &value, const String &domName) {
00545
00546
00547
00548 if (domName.empty()) {
00549 set(key, value);
00550 return;
00551 }
00552
00553 Domain *domain = getDomain(domName);
00554
00555 if (!domain)
00556 error("ConfigManager::set(%s,%s,%s) called on non-existent domain",
00557 key.c_str(), value.c_str(), domName.c_str());
00558
00559 (*domain)[key] = value;
00560
00561
00562
00563
00564
00565
00566
00567
00568
00569
00570
00571
00572 #if 0
00573 if (domName == kTransientDomain)
00574 _transientDomain[key] = value;
00575 else {
00576 if (domName == kApplicationDomain) {
00577 _appDomain[key] = value;
00578 if (_activeDomainName.empty() || !_gameDomains[_activeDomainName].contains(key))
00579 _transientDomain.erase(key);
00580 } else {
00581 _gameDomains[domName][key] = value;
00582 if (domName == _activeDomainName)
00583 _transientDomain.erase(key);
00584 }
00585 }
00586 #endif
00587 }
00588
00589 void ConfigManager::setInt(const String &key, int value, const String &domName) {
00590 set(key, String::format("%i", value), domName);
00591 }
00592
00593 void ConfigManager::setBool(const String &key, bool value, const String &domName) {
00594 set(key, String(value ? "true" : "false"), domName);
00595 }
00596
00597
00598 #pragma mark -
00599
00600
00601 void ConfigManager::registerDefault(const String &key, const String &value) {
00602 _defaultsDomain[key] = value;
00603 }
00604
00605 void ConfigManager::registerDefault(const String &key, const char *value) {
00606 registerDefault(key, String(value));
00607 }
00608
00609 void ConfigManager::registerDefault(const String &key, int value) {
00610 registerDefault(key, String::format("%i", value));
00611 }
00612
00613 void ConfigManager::registerDefault(const String &key, bool value) {
00614 registerDefault(key, value ? "true" : "false");
00615 }
00616
00617
00618 #pragma mark -
00619
00620
00621 void ConfigManager::setActiveDomain(const String &domName) {
00622 if (domName.empty()) {
00623 _activeDomain = nullptr;
00624 } else {
00625 assert(isValidDomainName(domName));
00626 _activeDomain = &_gameDomains[domName];
00627 }
00628 _activeDomainName = domName;
00629 }
00630
00631 void ConfigManager::addGameDomain(const String &domName) {
00632 assert(!domName.empty());
00633 assert(isValidDomainName(domName));
00634
00635
00636
00637
00638 _gameDomains[domName];
00639
00640
00641 if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), domName) == _domainSaveOrder.end())
00642 _domainSaveOrder.push_back(domName);
00643 }
00644
00645 void ConfigManager::addMiscDomain(const String &domName) {
00646 assert(!domName.empty());
00647 assert(isValidDomainName(domName));
00648
00649 _miscDomains[domName];
00650 }
00651
00652 void ConfigManager::removeGameDomain(const String &domName) {
00653 assert(!domName.empty());
00654 assert(isValidDomainName(domName));
00655 if (domName == _activeDomainName) {
00656 _activeDomainName.clear();
00657 _activeDomain = nullptr;
00658 }
00659 _gameDomains.erase(domName);
00660 }
00661
00662 void ConfigManager::removeMiscDomain(const String &domName) {
00663 assert(!domName.empty());
00664 assert(isValidDomainName(domName));
00665 _miscDomains.erase(domName);
00666 }
00667
00668
00669 void ConfigManager::renameGameDomain(const String &oldName, const String &newName) {
00670 renameDomain(oldName, newName, _gameDomains);
00671 if (_activeDomainName == oldName) {
00672 _activeDomainName = newName;
00673 _activeDomain = &_gameDomains[newName];
00674 }
00675 }
00676
00677 void ConfigManager::renameMiscDomain(const String &oldName, const String &newName) {
00678 renameDomain(oldName, newName, _miscDomains);
00679 }
00680
00684 void ConfigManager::renameDomain(const String &oldName, const String &newName, DomainMap &map) {
00685 if (oldName == newName)
00686 return;
00687
00688 assert(!oldName.empty());
00689 assert(!newName.empty());
00690 assert(isValidDomainName(oldName));
00691 assert(isValidDomainName(newName));
00692
00693
00694 Domain &oldDom = map[oldName];
00695 Domain &newDom = map[newName];
00696 Domain::const_iterator iter;
00697 for (iter = oldDom.begin(); iter != oldDom.end(); ++iter)
00698 newDom[iter->_key] = iter->_value;
00699
00700 map.erase(oldName);
00701 }
00702
00703 bool ConfigManager::hasGameDomain(const String &domName) const {
00704 assert(!domName.empty());
00705 return isValidDomainName(domName) && _gameDomains.contains(domName);
00706 }
00707
00708 bool ConfigManager::hasMiscDomain(const String &domName) const {
00709 assert(!domName.empty());
00710 return isValidDomainName(domName) && _miscDomains.contains(domName);
00711 }
00712
00713 #pragma mark -
00714
00715 void ConfigManager::Domain::setDomainComment(const String &comment) {
00716 _domainComment = comment;
00717 }
00718 const String &ConfigManager::Domain::getDomainComment() const {
00719 return _domainComment;
00720 }
00721
00722 void ConfigManager::Domain::setKVComment(const String &key, const String &comment) {
00723 _keyValueComments[key] = comment;
00724 }
00725 const String &ConfigManager::Domain::getKVComment(const String &key) const {
00726 return _keyValueComments[key];
00727 }
00728 bool ConfigManager::Domain::hasKVComment(const String &key) const {
00729 return _keyValueComments.contains(key);
00730 }
00731
00732 }