openspades/Sources/Core/Settings.cpp
2014-04-01 15:52:22 +09:00

397 lines
9.6 KiB
C++

/*
Copyright (c) 2013 yvt
This file is part of OpenSpades.
OpenSpades is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenSpades is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenSpades. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include "Settings.h"
#include <Core/FileManager.h>
#include <Core/IStream.h>
#include "../Core/Debug.h"
#include <stdlib.h>
#include <memory>
#include <Core/Math.h>
#include "FltkPreferenceImporter.h"
namespace spades {
#define CONFIGFILE "SPConfig.cfg"
static Settings *instance = NULL;
Settings *Settings::GetInstance() {
if(!instance)
instance = new Settings();
return instance;
}
Settings::Settings() {
SPADES_MARK_FUNCTION();
loaded = false;
}
void Settings::Load() {
SPADES_MARK_FUNCTION();
// import Fltk preferences
bool importedPref = false;
{
auto prefs = ImportFltkPreference();
for(const auto& item: prefs) {
auto *it = GetItem(item.first);
it->Set(item.second);
}
if(prefs.size() > 0)
importedPref = true;
// FIXME: remove legacy preference?
}
SPLog("Loading preferences from " CONFIGFILE);
loaded = false;
try {
if(FileManager::FileExists(CONFIGFILE)) {
SPLog(CONFIGFILE " found.");
std::string text = FileManager::ReadAllBytes(CONFIGFILE);
auto lines = SplitIntoLines(text);
std::size_t line = 0;
while(line < lines.size()) {
auto& l = lines[line];
{
// remove comments
auto pos = l.find('#');
if(pos != std::string::npos) {
l.resize(pos);
}
}
std::size_t startPos = l.find_first_not_of(' ');
if(startPos == std::string::npos) {
// no contents in this line
line++; continue;
}
auto lineBuf = l;
std::size_t linePos = 0;
auto tryDecodeHexDigit = [](char c, int& digit) -> bool {
if(c >= '0' && c <= '9') {
digit = c - '0'; return true;
}else if(c >= 'a' && c <= 'f') {
digit = c - 'a' + 10; return true;
}else if(c >= 'A' && c <= 'F') {
digit = c - 'A' + 10; return true;
}else{
return false;
}
};
auto readString = [&](bool stopAtColon) {
std::string buffer;
int digit1, digit2;
while(linePos < lineBuf.size() &&
lineBuf[linePos] == ' ') {
linePos++;
}
while(linePos < lineBuf.size()) {
if(lineBuf[linePos] == '\\' &&
linePos + 1 == lineBuf.size() &&
line < lines.size() - 1) {
// line continuation
line++;
lineBuf = lines[line];
linePos = 0;
}else if(lineBuf[linePos] == '\\' &&
linePos + 3 < lineBuf.size() &&
lineBuf[linePos + 1] == 'x' &&
tryDecodeHexDigit(lineBuf[linePos + 2], digit1) &&
tryDecodeHexDigit(lineBuf[linePos + 3], digit2)) {
// hex
char c = (digit1 << 4) | digit2;
buffer += c;
linePos += 3;
}else if(lineBuf[linePos] == '\\' &&
linePos + 1 < lineBuf.size()) {
// escape
switch(lineBuf[linePos + 1]) {
case 'n': buffer += '\n'; break;
case 'r': buffer += '\r'; break;
case 't': buffer += '\t'; break;
default: buffer += lineBuf[linePos + 1]; break;
}
linePos += 2;
}else if(lineBuf[linePos] == ':' && stopAtColon) {
break;
}else{
// normal chars
buffer += lineBuf[linePos];
linePos++;
}
}
return buffer;
};
std::string key = readString(true);
if(linePos >= lineBuf.size()) {
SPLog("Warning: no value provided for \"%s\"", key.c_str());
}
linePos++;
std::string val = readString(false);
auto *item = GetItem(key);
item->Set(val);
line++;
}
}else{
SPLog(CONFIGFILE " doesn't exist.");
}
if(importedPref) {
SPLog("Legacy preference was imported. Removing the legacy pref file.");
DeleteFltkPreference();
Save();
}
loaded = true;
}catch(const std::exception& ex) {
SPLog("Failed to load preference: %s", ex.what());
SPLog("Disabling saving preference.");
}
}
void Settings::Save() {
SPLog("Saving preferences to " CONFIGFILE);
try {
std::string buffer;
buffer = "# OpenSpades config file\n"
"#\n"
"\n";
int column = 0;
auto emitContinuation = [&] {
buffer += "\\\n";
column = 0;
};
auto emitString = [&](const std::string& val, bool escapeColon) {
std::size_t i = 0;
while(i < val.size() && val[i] == ' ') {
if(column > 78) {
emitContinuation();
}
buffer += "\\ ";
column += 2;
i++;
}
while(i < val.size()) {
if(column > 78) {
emitContinuation();
}
unsigned char uc = static_cast<unsigned char>(val[i]);
switch(val[i]) {
case '\n':
buffer += "\\n"; column += 2; i++;
break;
case '\r':
buffer += "\\r"; column += 2; i++;
break;
case '\t':
buffer += "\\t"; column += 2; i++;
break;
default:
std::size_t utf8charsize;
GetCodePointFromUTF8String(val, i, &utf8charsize);
if(val[i] == '#' || // comment marker
(escapeColon && val[i] == ':') || // key/value split
uc < 0x20 || // control char
(uc >= 0x80 && utf8charsize == 0) || // invalid UTF8
utf8charsize >= 5) { // valid UTF-8 but codepoint beyond BMP/SMP range
static const char *s = "0123456789abcdef";
buffer += "\\x";
buffer += s[uc>>4];
buffer += s[uc&15];
column += 3; i++;
}else{
buffer.append(val, i, utf8charsize); column += utf8charsize;
i += utf8charsize;
}
break;
}
}
};
for(const auto& item: items) {
Item *itm = item.second;
emitString(itm->name, true);
buffer += ": "; column += 2;
emitString(itm->string, false);
buffer += "\n";
column = 0;
}
std::unique_ptr<IStream> s(FileManager::OpenForWriting(CONFIGFILE));
s->Write(buffer);
} catch (const std::exception& ex) {
SPLog("Failed to save preference: %s", ex.what());
}
}
void Settings::Flush() {
if(loaded) {
SPLog("Saving preference to " CONFIGFILE);
Save();
}else{
SPLog("Not saving preferences because loading preferences has failed.");
}
}
std::vector<std::string> Settings::GetAllItemNames() {
SPADES_MARK_FUNCTION();
std::vector<std::string> names;
std::map<std::string, Item *>::iterator it;
for(it = items.begin(); it != items.end(); it++){
names.push_back(it->second->name);
}
return names;
}
Settings::Item *Settings::GetItem(const std::string &name,
const std::string& def,
const std::string& desc){
SPADES_MARK_FUNCTION();
std::map<std::string, Item *>::iterator it;
it = items.find(name);
if(it == items.end()){
Item *item = new Item();
item->name = name;
item->desc = desc;
item->defaultValue = def;
item->value = static_cast<float>(atof(def.c_str()));
item->intValue = atoi(def.c_str());
item->string = def;
item->defaults = true;
items[name] = item;
return item;
}
return it->second;
}
void Settings::Item::Load() {
// no longer need to Load
}
void Settings::Item::Set(const std::string &str) {
string = str;
value = static_cast<float>(atof(str.c_str()));
intValue = atoi(str.c_str());
defaults = false;
}
void Settings::Item::Set(int v) {
SPADES_MARK_FUNCTION_DEBUG();
char buf[256];
sprintf(buf, "%d", v);
string = buf;
intValue = v;
value = (float)v;
defaults = false;
}
void Settings::Item::Set(float v){
SPADES_MARK_FUNCTION_DEBUG();
char buf[256];
sprintf(buf, "%f", v);
string = buf;
intValue = (int)v;
value = v;
defaults = false;
}
Settings::ItemHandle::ItemHandle(const std::string& name,
const std::string& def,
const std::string& desc){
SPADES_MARK_FUNCTION();
item = Settings::GetInstance()->GetItem(name, def, desc);
if( !def.empty() && item->defaultValue.empty() ){
item->defaultValue = def;
if(item->defaults) {
item->Set(def);
item->defaults = true;
}
}
if( !desc.empty() && item->desc.empty() ){
item->desc = desc;
}
if( item->defaultValue != def && !def.empty() ){
fprintf(stderr, "WARNING: setting '%s' has multiple default values ('%s', '%s')\n",
name.c_str(), def.c_str(), item->defaultValue.c_str());
}
}
void Settings::ItemHandle::operator =(const std::string& value){
item->Set(value);
}
void Settings::ItemHandle::operator =(int value){
item->Set(value);
}
void Settings::ItemHandle::operator =(float value){
item->Set(value);
}
Settings::ItemHandle::operator std::string() {
item->Load();
return item->string;
}
Settings::ItemHandle::operator int() {
item->Load();
return item->intValue;
}
Settings::ItemHandle::operator float() {
item->Load();
return item->value;
}
Settings::ItemHandle::operator bool() {
item->Load();
return item->intValue != 0;
}
const char *Settings::ItemHandle::CString() {
item->Load();
return item->string.c_str();
}
std::string Settings::ItemHandle::GetDescription() {
return item->desc;
}
}