/* SWR INI Tool - Switchroot INI Configuration Editor Copyright (C) 2026 Switchroot This program 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. */ #include "ini_handler.h" #include #include #include #include static std::string trim(const std::string& str) { size_t first = str.find_first_not_of(" \t\r\n"); if (first == std::string::npos) return ""; size_t last = str.find_last_not_of(" \t\r\n"); return str.substr(first, last - first + 1); } static std::string toLower(const std::string& str) { std::string result = str; std::transform(result.begin(), result.end(), result.begin(), ::tolower); return result; } IniHandler::IniHandler() : loaded(false) { } bool IniHandler::load(const std::string& path) { this->filePath = path; this->lines.clear(); this->loaded = false; std::ifstream file(path); if (!file.is_open()) return false; std::string currentSection; std::string rawLine; while (std::getline(file, rawLine)) { Line line; line.raw = rawLine; std::string trimmed = trim(rawLine); if (trimmed.empty()) { line.type = Line::BLANK; } else if (trimmed[0] == '#' || trimmed[0] == ';') { line.type = Line::COMMENT; } else if (trimmed[0] == '[' && trimmed.back() == ']') { line.type = Line::SECTION; line.section = trim(trimmed.substr(1, trimmed.size() - 2)); currentSection = line.section; } else { size_t eqPos = trimmed.find('='); if (eqPos != std::string::npos) { line.type = Line::KEY_VALUE; line.section = currentSection; line.key = trim(trimmed.substr(0, eqPos)); line.value = trim(trimmed.substr(eqPos + 1)); } else { line.type = Line::COMMENT; // treat unparseable lines as comments } } this->lines.push_back(line); } file.close(); this->loaded = true; return true; } bool IniHandler::save() { return save(this->filePath); } bool IniHandler::save(const std::string& path) { std::ofstream file(path); if (!file.is_open()) return false; for (size_t i = 0; i < this->lines.size(); i++) { const Line& line = this->lines[i]; if (line.type == Line::KEY_VALUE) { file << line.key << "=" << line.value; } else { file << line.raw; } if (i < this->lines.size() - 1) file << "\n"; } file.close(); return true; } std::string IniHandler::get(const std::string& section, const std::string& key, const std::string& defaultValue) const { int idx = findLine(section, key); if (idx >= 0) return this->lines[idx].value; return defaultValue; } int IniHandler::getInt(const std::string& section, const std::string& key, int defaultValue) const { std::string val = get(section, key, ""); if (val.empty()) return defaultValue; try { return std::stoi(val); } catch (...) { return defaultValue; } } bool IniHandler::getBool(const std::string& section, const std::string& key, bool defaultValue) const { return getInt(section, key, defaultValue ? 1 : 0) != 0; } void IniHandler::set(const std::string& section, const std::string& key, const std::string& value) { int idx = findLine(section, key); if (idx >= 0) { this->lines[idx].value = value; } else { // Key doesn't exist - add it at the end of the section int sectionEnd = findSectionEnd(section); if (sectionEnd < 0) { // Section doesn't exist - create it Line sectionLine; sectionLine.type = Line::SECTION; sectionLine.section = section; sectionLine.raw = "[" + section + "]"; Line blankLine; blankLine.type = Line::BLANK; blankLine.raw = ""; Line kvLine; kvLine.type = Line::KEY_VALUE; kvLine.section = section; kvLine.key = key; kvLine.value = value; this->lines.push_back(blankLine); this->lines.push_back(sectionLine); this->lines.push_back(kvLine); } else { Line kvLine; kvLine.type = Line::KEY_VALUE; kvLine.section = section; kvLine.key = key; kvLine.value = value; this->lines.insert(this->lines.begin() + sectionEnd, kvLine); } } } void IniHandler::setInt(const std::string& section, const std::string& key, int value) { set(section, key, std::to_string(value)); } void IniHandler::setBool(const std::string& section, const std::string& key, bool value) { setInt(section, key, value ? 1 : 0); } bool IniHandler::hasSection(const std::string& section) const { for (const Line& line : this->lines) { if (line.type == Line::SECTION && toLower(line.section) == toLower(section)) return true; } return false; } bool IniHandler::hasKey(const std::string& section, const std::string& key) const { return findLine(section, key) >= 0; } std::vector IniHandler::getSections() const { std::vector sections; for (const Line& line : this->lines) { if (line.type == Line::SECTION) sections.push_back(line.section); } return sections; } std::vector> IniHandler::getKeys(const std::string& section) const { std::vector> keys; for (const Line& line : this->lines) { if (line.type == Line::KEY_VALUE && toLower(line.section) == toLower(section)) keys.push_back({line.key, line.value}); } return keys; } std::string IniHandler::findOsSection() const { for (const Line& line : this->lines) { if (line.type == Line::SECTION && toLower(line.section) != "config") return line.section; } return ""; } int IniHandler::findLine(const std::string& section, const std::string& key) const { std::string sectionLower = toLower(section); std::string keyLower = toLower(key); for (size_t i = 0; i < this->lines.size(); i++) { const Line& line = this->lines[i]; if (line.type == Line::KEY_VALUE && toLower(line.section) == sectionLower && toLower(line.key) == keyLower) { return (int)i; } } return -1; } int IniHandler::findSectionEnd(const std::string& section) const { std::string sectionLower = toLower(section); bool inSection = false; int lastLine = -1; for (size_t i = 0; i < this->lines.size(); i++) { const Line& line = this->lines[i]; if (line.type == Line::SECTION) { if (toLower(line.section) == sectionLower) { inSection = true; lastLine = (int)i + 1; } else if (inSection) { return lastLine; } } else if (inSection && line.type != Line::BLANK) { lastLine = (int)i + 1; } } if (inSection) return lastLine; return -1; }