/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /*************************************************************************** * configfile.cc * * Thu May 14 14:51:39 CEST 2015 * Copyright 2015 Bent Bisballe Nyeng * deva@aasimon.org ****************************************************************************/ /* * This file is part of DrumGizmo. * * DrumGizmo is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * DrumGizmo 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with DrumGizmo; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include "configfile.h" #include #include "directory.h" #include "platform.h" #if DG_PLATFORM == DG_PLATFORM_WINDOWS #include #include #include #include #else #endif #include namespace { #if DG_PLATFORM == DG_PLATFORM_WINDOWS const std::string sep = "\\"; #else const std::string sep = "/"; #endif #if DG_PLATFORM == DG_PLATFORM_WINDOWS const std::string config_dir_name = "DrumGizmo"; #else const std::string config_dir_name = ".drumgizmo"; #endif // Return the path containing the config files. std::string getConfigPath() { // TODO: Move this utility function as a static function into directory.cc/h at some point? std::string configpath; #if DG_PLATFORM == DG_PLATFORM_WINDOWS TCHAR szPath[256]; if(SHGetFolderPath(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, szPath) == S_OK) { configpath = szPath; } #else configpath = getenv("HOME"); #endif configpath += sep; configpath += config_dir_name; return configpath; } // Calling this makes sure that the config path exists bool createConfigPath() { const std::string configpath = getConfigPath(); if(!Directory::isDir(configpath)) { DEBUG(configfile, "No configuration exists, creating directory '%s'\n", configpath.c_str()); #if DG_PLATFORM == DG_PLATFORM_WINDOWS if(mkdir(configpath.c_str()) < 0) #else if(mkdir(configpath.c_str(), 0755) < 0) #endif { DEBUG(configfile, "Could not create config directory\n"); } return false; } return true; } } // end anonymous namespace ConfigFile::ConfigFile(const std::string& filename) : filename(filename) { } ConfigFile::~ConfigFile() { if (current_file.is_open()) { DEBUG(configfile, "File has not been closed by the client...\n"); current_file.close(); } } // TODO: Make this return a homemade error variant when we have this project-wide. bool ConfigFile::load() { DEBUG(configfile, "Loading config file...\n"); if(!open(std::ios_base::in)) { return false; } values.clear(); std::string line; while(std::getline(current_file, line)) { if(!parseLine(line)) { return false; } } current_file.close(); return true; } bool ConfigFile::save() { DEBUG(configfile, "Saving configuration...\n"); if(!createConfigPath()) { return false; } if(!open(std::ios_base::out)) { return false; } for(const auto& value: values) { current_file << value.first << " = " << "\"" << value.second << "\"" << std::endl; } current_file.close(); return true; } std::string ConfigFile::getValue(const std::string& key) const { auto i = values.find(key); if (i != values.end()) { return i->second; } return ""; } void ConfigFile::setValue(const std::string& key, const std::string& value) { values[key] = value; } bool ConfigFile::open(std::ios_base::openmode mode) { if(current_file.is_open()) { current_file.close(); } std::string const full_filename = getConfigPath() + sep + filename; DEBUG(configfile, "Opening config file '%s'\n", full_filename.c_str()); current_file.open(full_filename, mode); return current_file.is_open(); } bool ConfigFile::parseLine(const std::string& line) { enum class State { before_key, in_key, after_key, before_value, in_value, in_value_single_quoted, in_value_double_quoted, after_value, }; // For empty lines, we parse them fine but don't do anything. if(line == "") { return true; } std::string key; std::string value; State state = State::before_key; for(std::size_t pos = 0; pos < line.size(); ++pos) { auto c = line[pos]; switch(state) { case State::before_key: if(c == '#') { // Comment: Ignore line. pos = line.size(); continue; } if(std::isspace(c)) { continue; } key += c; state = State::in_key; break; case State::in_key: if(std::isspace(c)) { state = State::after_key; continue; } if(c == ':' || c == '=') { state = State::before_value; continue; } key += c; break; case State::after_key: if(std::isspace(c)) { continue; } if(c == ':' || c == '=') { state = State::before_value; continue; } ERR(configfile, "Bad symbol." " Expecting only whitespace or key/value seperator: '%s'", line.c_str()); return false; case State::before_value: if(std::isspace(c)) { continue; } if(c == '\'') { state = State::in_value_single_quoted; continue; } if(c == '"') { state = State::in_value_double_quoted; continue; } value += c; state = State::in_value; break; case State::in_value: if(std::isspace(c)) { state = State::after_value; continue; } if(c == '#') { // Comment: Ignore the rest of the line. pos = line.size(); state = State::after_value; continue; } value += c; break; case State::in_value_single_quoted: if(c == '\'') { state = State::after_value; continue; } value += c; break; case State::in_value_double_quoted: if(c == '"') { state = State::after_value; continue; } value += c; break; case State::after_value: if(std::isspace(c)) { continue; } if(c == '#') { // Comment: Ignore the rest of the line. pos = line.size(); continue; } ERR(configfile, "Bad symbol." " Expecting only whitespace or key/value seperator: '%s'", line.c_str()); return false; } } if(state == State::before_key) { // Line did not contain any data (empty or comment) return true; } // If state == in_value_XXX_quoted here, the string was not terminated. // If state == before_value it means that the value is empty. if(state != State::after_value && state != State::in_value && state != State::before_value) { ERR(configfile, "Malformed line: '%s'", line.c_str()); return false; } DEBUG(configfile, "key['%s'] value['%s']\n", key.c_str(), value.c_str()); if(key != "") { values[key] = value; } return true; }