Serenity Operating System
at portability 238 lines 7.0 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <AK/StringBuilder.h> 28#include <LibCore/ConfigFile.h> 29#include <LibCore/File.h> 30#include <LibCore/UserInfo.h> 31#include <pwd.h> 32#include <stdio.h> 33#include <unistd.h> 34 35namespace Core { 36 37NonnullRefPtr<ConfigFile> ConfigFile::get_for_app(const String& app_name) 38{ 39 String home_path = get_current_user_home_path(); 40 if (home_path == "/") 41 home_path = String::format("/tmp"); 42 auto path = String::format("%s/%s.ini", home_path.characters(), app_name.characters()); 43 return adopt(*new ConfigFile(path)); 44} 45 46NonnullRefPtr<ConfigFile> ConfigFile::get_for_system(const String& app_name) 47{ 48 auto path = String::format("/etc/%s.ini", app_name.characters()); 49 return adopt(*new ConfigFile(path)); 50} 51 52NonnullRefPtr<ConfigFile> ConfigFile::open(const String& path) 53{ 54 return adopt(*new ConfigFile(path)); 55} 56 57ConfigFile::ConfigFile(const String& file_name) 58 : m_file_name(file_name) 59{ 60 reparse(); 61} 62 63ConfigFile::~ConfigFile() 64{ 65 sync(); 66} 67 68void ConfigFile::reparse() 69{ 70 m_groups.clear(); 71 72 auto file = File::construct(m_file_name); 73 if (!file->open(IODevice::OpenMode::ReadOnly)) 74 return; 75 76 HashMap<String, String>* current_group = nullptr; 77 78 while (file->can_read_line()) { 79 auto line = file->read_line(BUFSIZ); 80 auto* cp = (const char*)line.data(); 81 82 while (*cp && (*cp == ' ' || *cp == '\t' || *cp == '\n')) 83 ++cp; 84 85 switch (*cp) { 86 case '\0': // EOL... 87 case '#': // Comment, skip entire line. 88 case ';': // -||- 89 continue; 90 case '[': { // Start of new group. 91 StringBuilder builder; 92 ++cp; // Skip the '[' 93 while (*cp && (*cp != ']')) 94 builder.append(*(cp++)); 95 current_group = &m_groups.ensure(builder.to_string()); 96 break; 97 } 98 default: { // Start of key{ 99 StringBuilder key_builder; 100 StringBuilder value_builder; 101 while (*cp && (*cp != '=')) 102 key_builder.append(*(cp++)); 103 ++cp; // Skip the '=' 104 while (*cp && (*cp != '\n')) 105 value_builder.append(*(cp++)); 106 if (!current_group) { 107 // We're not in a group yet, create one with the name ""... 108 current_group = &m_groups.ensure(""); 109 } 110 current_group->set(key_builder.to_string(), value_builder.to_string()); 111 } 112 } 113 } 114} 115 116String ConfigFile::read_entry(const String& group, const String& key, const String& default_value) const 117{ 118 if (!has_key(group, key)) { 119 const_cast<ConfigFile&>(*this).write_entry(group, key, default_value); 120 return default_value; 121 } 122 auto it = m_groups.find(group); 123 auto jt = it->value.find(key); 124 return jt->value; 125} 126 127int ConfigFile::read_num_entry(const String& group, const String& key, int default_value) const 128{ 129 if (!has_key(group, key)) { 130 const_cast<ConfigFile&>(*this).write_num_entry(group, key, default_value); 131 return default_value; 132 } 133 134 bool ok; 135 int value = read_entry(group, key).to_uint(ok); 136 if (!ok) 137 return default_value; 138 return value; 139} 140 141bool ConfigFile::read_bool_entry(const String& group, const String& key, bool default_value) const 142{ 143 return read_entry(group, key, default_value ? "1" : "0") == "1"; 144} 145 146void ConfigFile::write_entry(const String& group, const String& key, const String& value) 147{ 148 m_groups.ensure(group).ensure(key) = value; 149 m_dirty = true; 150} 151 152void ConfigFile::write_num_entry(const String& group, const String& key, int value) 153{ 154 write_entry(group, key, String::number(value)); 155} 156void ConfigFile::write_bool_entry(const String& group, const String& key, bool value) 157{ 158 write_entry(group, key, value ? "1" : "0"); 159} 160void ConfigFile::write_color_entry(const String& group, const String& key, Color value) 161{ 162 write_entry(group, key, String::format("%d,%d,%d,%d", value.red(), value.green(), value.blue(), value.alpha())); 163} 164 165bool ConfigFile::sync() 166{ 167 if (!m_dirty) 168 return true; 169 170 FILE* fp = fopen(m_file_name.characters(), "wb"); 171 if (!fp) 172 return false; 173 174 for (auto& it : m_groups) { 175 fprintf(fp, "[%s]\n", it.key.characters()); 176 for (auto& jt : it.value) 177 fprintf(fp, "%s=%s\n", jt.key.characters(), jt.value.characters()); 178 fprintf(fp, "\n"); 179 } 180 181 fclose(fp); 182 183 m_dirty = false; 184 return true; 185} 186 187void ConfigFile::dump() const 188{ 189 for (auto& it : m_groups) { 190 printf("[%s]\n", it.key.characters()); 191 for (auto& jt : it.value) 192 printf("%s=%s\n", jt.key.characters(), jt.value.characters()); 193 printf("\n"); 194 } 195} 196 197Vector<String> ConfigFile::groups() const 198{ 199 return m_groups.keys(); 200} 201 202Vector<String> ConfigFile::keys(const String& group) const 203{ 204 auto it = m_groups.find(group); 205 if (it == m_groups.end()) 206 return {}; 207 return it->value.keys(); 208} 209 210bool ConfigFile::has_key(const String& group, const String& key) const 211{ 212 auto it = m_groups.find(group); 213 if (it == m_groups.end()) 214 return {}; 215 return it->value.contains(key); 216} 217 218bool ConfigFile::has_group(const String& group) const 219{ 220 return m_groups.contains(group); 221} 222 223void ConfigFile::remove_group(const String& group) 224{ 225 m_groups.remove(group); 226 m_dirty = true; 227} 228 229void ConfigFile::remove_entry(const String& group, const String& key) 230{ 231 auto it = m_groups.find(group); 232 if (it == m_groups.end()) 233 return; 234 it->value.remove(key); 235 m_dirty = true; 236} 237 238}