mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 07:02:44 +00:00 
			
		
		
		
	 8464da1439
			
		
	
	
		8464da1439
		
	
	
	
	
		
			
			`Stream` will be qualified as `AK::Stream` until we remove the `Core::Stream` namespace. `IODevice` now reuses the `SeekMode` that is defined by `SeekableStream`, since defining its own would require us to qualify it with `AK::SeekMode` everywhere.
		
			
				
	
	
		
			249 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			249 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
 | |
|  * Copyright (c) 2021, networkException <networkexception@serenityos.org>
 | |
|  * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-2-Clause
 | |
|  */
 | |
| 
 | |
| #include <AK/LexicalPath.h>
 | |
| #include <AK/StringBuilder.h>
 | |
| #include <LibCore/ConfigFile.h>
 | |
| #include <LibCore/Directory.h>
 | |
| #include <LibCore/StandardPaths.h>
 | |
| #include <LibCore/System.h>
 | |
| #include <pwd.h>
 | |
| #include <sys/types.h>
 | |
| 
 | |
| namespace Core {
 | |
| 
 | |
| ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open_for_lib(DeprecatedString const& lib_name, AllowWriting allow_altering)
 | |
| {
 | |
|     DeprecatedString directory_name = DeprecatedString::formatted("{}/lib", StandardPaths::config_directory());
 | |
|     auto directory = TRY(Directory::create(directory_name, Directory::CreateDirectories::Yes));
 | |
|     auto path = DeprecatedString::formatted("{}/{}.ini", directory, lib_name);
 | |
|     return ConfigFile::open(path, allow_altering);
 | |
| }
 | |
| 
 | |
| ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open_for_app(DeprecatedString const& app_name, AllowWriting allow_altering)
 | |
| {
 | |
|     auto directory = TRY(Directory::create(StandardPaths::config_directory(), Directory::CreateDirectories::Yes));
 | |
|     auto path = DeprecatedString::formatted("{}/{}.ini", directory, app_name);
 | |
|     return ConfigFile::open(path, allow_altering);
 | |
| }
 | |
| 
 | |
| ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open_for_system(DeprecatedString const& app_name, AllowWriting allow_altering)
 | |
| {
 | |
|     auto path = DeprecatedString::formatted("/etc/{}.ini", app_name);
 | |
|     return ConfigFile::open(path, allow_altering);
 | |
| }
 | |
| 
 | |
| ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open(DeprecatedString const& filename, AllowWriting allow_altering)
 | |
| {
 | |
|     auto maybe_file = Stream::File::open(filename, allow_altering == AllowWriting::Yes ? Stream::OpenMode::ReadWrite : Stream::OpenMode::Read);
 | |
|     OwnPtr<Stream::BufferedFile> buffered_file;
 | |
|     if (maybe_file.is_error()) {
 | |
|         // If we attempted to open a read-only file that does not exist, we ignore the error, making it appear
 | |
|         // the same as if we had opened an empty file. This behavior is a little weird, but is required by
 | |
|         // user code, which does not check the config file exists before opening.
 | |
|         if (!(allow_altering == AllowWriting::No && maybe_file.error().code() == ENOENT))
 | |
|             return maybe_file.error();
 | |
|     } else {
 | |
|         buffered_file = TRY(Stream::BufferedFile::create(maybe_file.release_value()));
 | |
|     }
 | |
| 
 | |
|     auto config_file = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) ConfigFile(filename, move(buffered_file))));
 | |
|     TRY(config_file->reparse());
 | |
|     return config_file;
 | |
| }
 | |
| 
 | |
| ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open(DeprecatedString const& filename, int fd)
 | |
| {
 | |
|     auto file = TRY(Stream::File::adopt_fd(fd, Stream::OpenMode::ReadWrite));
 | |
|     return open(filename, move(file));
 | |
| }
 | |
| 
 | |
| ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open(DeprecatedString const& filename, NonnullOwnPtr<Core::Stream::File> file)
 | |
| {
 | |
|     auto buffered_file = TRY(Stream::BufferedFile::create(move(file)));
 | |
| 
 | |
|     auto config_file = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) ConfigFile(filename, move(buffered_file))));
 | |
|     TRY(config_file->reparse());
 | |
|     return config_file;
 | |
| }
 | |
| 
 | |
| ConfigFile::ConfigFile(DeprecatedString const& filename, OwnPtr<Stream::BufferedFile> open_file)
 | |
|     : m_filename(filename)
 | |
|     , m_file(move(open_file))
 | |
| {
 | |
| }
 | |
| 
 | |
| ConfigFile::~ConfigFile()
 | |
| {
 | |
|     MUST(sync());
 | |
| }
 | |
| 
 | |
| ErrorOr<void> ConfigFile::reparse()
 | |
| {
 | |
|     m_groups.clear();
 | |
|     if (!m_file)
 | |
|         return {};
 | |
| 
 | |
|     HashMap<DeprecatedString, DeprecatedString>* current_group = nullptr;
 | |
| 
 | |
|     auto buffer = TRY(ByteBuffer::create_uninitialized(4096));
 | |
|     while (TRY(m_file->can_read_line())) {
 | |
|         auto line = TRY(m_file->read_line(buffer));
 | |
|         size_t i = 0;
 | |
| 
 | |
|         while (i < line.length() && (line[i] == ' ' || line[i] == '\t' || line[i] == '\n'))
 | |
|             ++i;
 | |
| 
 | |
|         if (i >= line.length())
 | |
|             continue;
 | |
| 
 | |
|         switch (line[i]) {
 | |
|         case '#': // Comment, skip entire line.
 | |
|         case ';': // -||-
 | |
|             continue;
 | |
|         case '[': { // Start of new group.
 | |
|             StringBuilder builder;
 | |
|             ++i; // Skip the '['
 | |
|             while (i < line.length() && (line[i] != ']')) {
 | |
|                 builder.append(line[i]);
 | |
|                 ++i;
 | |
|             }
 | |
|             current_group = &m_groups.ensure(builder.to_deprecated_string());
 | |
|             break;
 | |
|         }
 | |
|         default: { // Start of key
 | |
|             StringBuilder key_builder;
 | |
|             StringBuilder value_builder;
 | |
|             while (i < line.length() && (line[i] != '=')) {
 | |
|                 key_builder.append(line[i]);
 | |
|                 ++i;
 | |
|             }
 | |
|             ++i; // Skip the '='
 | |
|             while (i < line.length() && (line[i] != '\n')) {
 | |
|                 value_builder.append(line[i]);
 | |
|                 ++i;
 | |
|             }
 | |
|             if (!current_group) {
 | |
|                 // We're not in a group yet, create one with the name ""...
 | |
|                 current_group = &m_groups.ensure("");
 | |
|             }
 | |
|             auto value_string = value_builder.to_deprecated_string();
 | |
|             current_group->set(key_builder.to_deprecated_string(), value_string.trim_whitespace(TrimMode::Right));
 | |
|         }
 | |
|         }
 | |
|     }
 | |
|     return {};
 | |
| }
 | |
| 
 | |
| DeprecatedString ConfigFile::read_entry(DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& default_value) const
 | |
| {
 | |
|     if (!has_key(group, key)) {
 | |
|         return default_value;
 | |
|     }
 | |
|     auto it = m_groups.find(group);
 | |
|     auto jt = it->value.find(key);
 | |
|     return jt->value;
 | |
| }
 | |
| 
 | |
| bool ConfigFile::read_bool_entry(DeprecatedString const& group, DeprecatedString const& key, bool default_value) const
 | |
| {
 | |
|     auto value = read_entry(group, key, default_value ? "true" : "false");
 | |
|     return value == "1" || value.equals_ignoring_case("true"sv);
 | |
| }
 | |
| 
 | |
| void ConfigFile::write_entry(DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& value)
 | |
| {
 | |
|     m_groups.ensure(group).ensure(key) = value;
 | |
|     m_dirty = true;
 | |
| }
 | |
| 
 | |
| void ConfigFile::write_bool_entry(DeprecatedString const& group, DeprecatedString const& key, bool value)
 | |
| {
 | |
|     write_entry(group, key, value ? "true" : "false");
 | |
| }
 | |
| 
 | |
| ErrorOr<void> ConfigFile::sync()
 | |
| {
 | |
|     if (!m_dirty)
 | |
|         return {};
 | |
| 
 | |
|     if (!m_file)
 | |
|         return Error::from_errno(ENOENT);
 | |
| 
 | |
|     TRY(m_file->truncate(0));
 | |
|     TRY(m_file->seek(0, SeekMode::SetPosition));
 | |
| 
 | |
|     for (auto& it : m_groups) {
 | |
|         TRY(m_file->write(DeprecatedString::formatted("[{}]\n", it.key).bytes()));
 | |
|         for (auto& jt : it.value)
 | |
|             TRY(m_file->write(DeprecatedString::formatted("{}={}\n", jt.key, jt.value).bytes()));
 | |
|         TRY(m_file->write("\n"sv.bytes()));
 | |
|     }
 | |
| 
 | |
|     m_dirty = false;
 | |
|     return {};
 | |
| }
 | |
| 
 | |
| void ConfigFile::dump() const
 | |
| {
 | |
|     for (auto& it : m_groups) {
 | |
|         outln("[{}]", it.key);
 | |
|         for (auto& jt : it.value)
 | |
|             outln("{}={}", jt.key, jt.value);
 | |
|         outln();
 | |
|     }
 | |
| }
 | |
| 
 | |
| Vector<DeprecatedString> ConfigFile::groups() const
 | |
| {
 | |
|     return m_groups.keys();
 | |
| }
 | |
| 
 | |
| Vector<DeprecatedString> ConfigFile::keys(DeprecatedString const& group) const
 | |
| {
 | |
|     auto it = m_groups.find(group);
 | |
|     if (it == m_groups.end())
 | |
|         return {};
 | |
|     return it->value.keys();
 | |
| }
 | |
| 
 | |
| bool ConfigFile::has_key(DeprecatedString const& group, DeprecatedString const& key) const
 | |
| {
 | |
|     auto it = m_groups.find(group);
 | |
|     if (it == m_groups.end())
 | |
|         return {};
 | |
|     return it->value.contains(key);
 | |
| }
 | |
| 
 | |
| bool ConfigFile::has_group(DeprecatedString const& group) const
 | |
| {
 | |
|     return m_groups.contains(group);
 | |
| }
 | |
| 
 | |
| void ConfigFile::add_group(DeprecatedString const& group)
 | |
| {
 | |
|     m_groups.ensure(group);
 | |
|     m_dirty = true;
 | |
| }
 | |
| 
 | |
| void ConfigFile::remove_group(DeprecatedString const& group)
 | |
| {
 | |
|     m_groups.remove(group);
 | |
|     m_dirty = true;
 | |
| }
 | |
| 
 | |
| void ConfigFile::remove_entry(DeprecatedString const& group, DeprecatedString const& key)
 | |
| {
 | |
|     auto it = m_groups.find(group);
 | |
|     if (it == m_groups.end())
 | |
|         return;
 | |
|     it->value.remove(key);
 | |
|     m_dirty = true;
 | |
| }
 | |
| 
 | |
| }
 |