mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 08:47:34 +00:00
ConfigServer+LibConfig: Add way for clients to listen for config changes
This patch adds a Config::Listener abstract class that anyone can inherit from and receive notifications when configuration values change. We don't yet monitor file system changes, so these only work for changes made by ConfigServer itself. In order to receive these notifications, clients must monitor the domain by calling monitor_domain(). Only pledged domains can be monitored. Note that the client initiating the change does not get notified.
This commit is contained in:
parent
9509f2ff87
commit
edf7843409
9 changed files with 188 additions and 15 deletions
|
@ -1,5 +1,6 @@
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
Client.cpp
|
Client.cpp
|
||||||
|
Listener.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(GENERATED_SOURCES
|
set(GENERATED_SOURCES
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <LibConfig/Client.h>
|
#include <LibConfig/Client.h>
|
||||||
|
#include <LibConfig/Listener.h>
|
||||||
|
|
||||||
namespace Config {
|
namespace Config {
|
||||||
|
|
||||||
|
@ -24,6 +25,11 @@ void Client::pledge_domains(Vector<String> const& domains)
|
||||||
async_pledge_domains(domains);
|
async_pledge_domains(domains);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Client::monitor_domain(String const& domain)
|
||||||
|
{
|
||||||
|
async_monitor_domain(domain);
|
||||||
|
}
|
||||||
|
|
||||||
String Client::read_string(StringView domain, StringView group, StringView key, StringView fallback)
|
String Client::read_string(StringView domain, StringView group, StringView key, StringView fallback)
|
||||||
{
|
{
|
||||||
return read_string_value(domain, group, key).value_or(fallback);
|
return read_string_value(domain, group, key).value_or(fallback);
|
||||||
|
@ -54,4 +60,25 @@ void Client::write_bool(StringView domain, StringView group, StringView key, boo
|
||||||
async_write_bool_value(domain, group, key, value);
|
async_write_bool_value(domain, group, key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Client::notify_changed_string_value(String const& domain, String const& group, String const& key, String const& value)
|
||||||
|
{
|
||||||
|
Listener::for_each([&](auto& listener) {
|
||||||
|
listener.config_string_did_change(domain, group, key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::notify_changed_i32_value(String const& domain, String const& group, String const& key, i32 value)
|
||||||
|
{
|
||||||
|
Listener::for_each([&](auto& listener) {
|
||||||
|
listener.config_i32_did_change(domain, group, key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::notify_changed_bool_value(String const& domain, String const& group, String const& key, bool value)
|
||||||
|
{
|
||||||
|
Listener::for_each([&](auto& listener) {
|
||||||
|
listener.config_bool_did_change(domain, group, key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ class Client final
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void pledge_domains(Vector<String> const&);
|
void pledge_domains(Vector<String> const&);
|
||||||
|
void monitor_domain(String const&);
|
||||||
|
|
||||||
String read_string(StringView domain, StringView group, StringView key, StringView fallback);
|
String read_string(StringView domain, StringView group, StringView key, StringView fallback);
|
||||||
i32 read_i32(StringView domain, StringView group, StringView key, i32 fallback);
|
i32 read_i32(StringView domain, StringView group, StringView key, i32 fallback);
|
||||||
|
@ -38,6 +39,10 @@ private:
|
||||||
: IPC::ServerConnection<ConfigClientEndpoint, ConfigServerEndpoint>(*this, "/tmp/portal/config")
|
: IPC::ServerConnection<ConfigClientEndpoint, ConfigServerEndpoint>(*this, "/tmp/portal/config")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void notify_changed_string_value(String const& domain, String const& group, String const& key, String const& value) override;
|
||||||
|
void notify_changed_i32_value(String const& domain, String const& group, String const& key, i32 value) override;
|
||||||
|
void notify_changed_bool_value(String const& domain, String const& group, String const& key, bool value) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline String read_string(StringView domain, StringView group, StringView key, StringView fallback = {})
|
inline String read_string(StringView domain, StringView group, StringView key, StringView fallback = {})
|
||||||
|
@ -80,4 +85,9 @@ inline void pledge_domains(String const& domains)
|
||||||
Client::the().pledge_domains({ domains });
|
Client::the().pledge_domains({ domains });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void monitor_domain(String const& domain)
|
||||||
|
{
|
||||||
|
Client::the().monitor_domain(domain);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
44
Userland/Libraries/LibConfig/Listener.cpp
Normal file
44
Userland/Libraries/LibConfig/Listener.cpp
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/Function.h>
|
||||||
|
#include <AK/HashTable.h>
|
||||||
|
#include <AK/String.h>
|
||||||
|
#include <LibConfig/Listener.h>
|
||||||
|
|
||||||
|
namespace Config {
|
||||||
|
|
||||||
|
static HashTable<Listener*> s_listeners;
|
||||||
|
|
||||||
|
Listener::Listener()
|
||||||
|
{
|
||||||
|
s_listeners.set(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Listener::~Listener()
|
||||||
|
{
|
||||||
|
s_listeners.remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Listener::for_each(Function<void(Listener&)> callback)
|
||||||
|
{
|
||||||
|
for (auto* listener : s_listeners)
|
||||||
|
callback(*listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Listener::config_string_did_change(String const&, String const&, String const&, String const&)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Listener::config_i32_did_change(String const&, String const&, String const&, i32)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Listener::config_bool_did_change(String const&, String const&, String const&, bool)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
27
Userland/Libraries/LibConfig/Listener.h
Normal file
27
Userland/Libraries/LibConfig/Listener.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Forward.h>
|
||||||
|
|
||||||
|
namespace Config {
|
||||||
|
|
||||||
|
class Listener {
|
||||||
|
public:
|
||||||
|
virtual ~Listener();
|
||||||
|
|
||||||
|
static void for_each(Function<void(Listener&)>);
|
||||||
|
|
||||||
|
virtual void config_string_did_change(String const& domain, String const& group, String const& key, String const& value);
|
||||||
|
virtual void config_i32_did_change(String const& domain, String const& group, String const& key, i32 value);
|
||||||
|
virtual void config_bool_did_change(String const& domain, String const& group, String const& key, bool value);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Listener();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -12,6 +12,25 @@ namespace ConfigServer {
|
||||||
|
|
||||||
static HashMap<int, RefPtr<ClientConnection>> s_connections;
|
static HashMap<int, RefPtr<ClientConnection>> s_connections;
|
||||||
|
|
||||||
|
struct CachedDomain {
|
||||||
|
String domain;
|
||||||
|
NonnullRefPtr<Core::ConfigFile> config;
|
||||||
|
};
|
||||||
|
|
||||||
|
static HashMap<String, NonnullOwnPtr<CachedDomain>> s_cache;
|
||||||
|
|
||||||
|
static Core::ConfigFile& ensure_domain_config(String const& domain)
|
||||||
|
{
|
||||||
|
auto it = s_cache.find(domain);
|
||||||
|
if (it != s_cache.end())
|
||||||
|
return *it->value->config;
|
||||||
|
|
||||||
|
auto config = Core::ConfigFile::open_for_app(domain, Core::ConfigFile::AllowWriting::Yes);
|
||||||
|
auto cache_entry = make<CachedDomain>(domain, config);
|
||||||
|
s_cache.set(domain, move(cache_entry));
|
||||||
|
return *config;
|
||||||
|
}
|
||||||
|
|
||||||
ClientConnection::ClientConnection(NonnullRefPtr<Core::LocalSocket> client_socket, int client_id)
|
ClientConnection::ClientConnection(NonnullRefPtr<Core::LocalSocket> client_socket, int client_id)
|
||||||
: IPC::ClientConnection<ConfigClientEndpoint, ConfigServerEndpoint>(*this, move(client_socket), client_id)
|
: IPC::ClientConnection<ConfigClientEndpoint, ConfigServerEndpoint>(*this, move(client_socket), client_id)
|
||||||
{
|
{
|
||||||
|
@ -38,6 +57,16 @@ void ClientConnection::pledge_domains(Vector<String> const& domains)
|
||||||
m_pledged_domains.set(domain);
|
m_pledged_domains.set(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ClientConnection::monitor_domain(String const& domain)
|
||||||
|
{
|
||||||
|
if (m_has_pledged && !m_pledged_domains.contains(domain)) {
|
||||||
|
did_misbehave("Attempt to monitor non-pledged domain");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_monitored_domains.set(domain);
|
||||||
|
}
|
||||||
|
|
||||||
bool ClientConnection::validate_access(String const& domain, String const& group, String const& key)
|
bool ClientConnection::validate_access(String const& domain, String const& group, String const& key)
|
||||||
{
|
{
|
||||||
if (!m_has_pledged)
|
if (!m_has_pledged)
|
||||||
|
@ -53,10 +82,10 @@ Messages::ConfigServer::ReadStringValueResponse ClientConnection::read_string_va
|
||||||
if (!validate_access(domain, group, key))
|
if (!validate_access(domain, group, key))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
auto config = Core::ConfigFile::open_for_app(domain);
|
auto& config = ensure_domain_config(domain);
|
||||||
if (!config->has_key(group, key))
|
if (!config.has_key(group, key))
|
||||||
return Optional<String> {};
|
return Optional<String> {};
|
||||||
return Optional<String> { config->read_entry(group, key) };
|
return Optional<String> { config.read_entry(group, key) };
|
||||||
}
|
}
|
||||||
|
|
||||||
Messages::ConfigServer::ReadI32ValueResponse ClientConnection::read_i32_value(String const& domain, String const& group, String const& key)
|
Messages::ConfigServer::ReadI32ValueResponse ClientConnection::read_i32_value(String const& domain, String const& group, String const& key)
|
||||||
|
@ -64,10 +93,10 @@ Messages::ConfigServer::ReadI32ValueResponse ClientConnection::read_i32_value(St
|
||||||
if (!validate_access(domain, group, key))
|
if (!validate_access(domain, group, key))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
auto config = Core::ConfigFile::open_for_app(domain);
|
auto& config = ensure_domain_config(domain);
|
||||||
if (!config->has_key(group, key))
|
if (!config.has_key(group, key))
|
||||||
return Optional<i32> {};
|
return Optional<i32> {};
|
||||||
return Optional<i32> { config->read_num_entry(group, key) };
|
return Optional<i32> { config.read_num_entry(group, key) };
|
||||||
}
|
}
|
||||||
|
|
||||||
Messages::ConfigServer::ReadBoolValueResponse ClientConnection::read_bool_value(String const& domain, String const& group, String const& key)
|
Messages::ConfigServer::ReadBoolValueResponse ClientConnection::read_bool_value(String const& domain, String const& group, String const& key)
|
||||||
|
@ -75,10 +104,10 @@ Messages::ConfigServer::ReadBoolValueResponse ClientConnection::read_bool_value(
|
||||||
if (!validate_access(domain, group, key))
|
if (!validate_access(domain, group, key))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
auto config = Core::ConfigFile::open_for_app(domain);
|
auto& config = ensure_domain_config(domain);
|
||||||
if (!config->has_key(group, key))
|
if (!config.has_key(group, key))
|
||||||
return Optional<bool> {};
|
return Optional<bool> {};
|
||||||
return Optional<bool> { config->read_bool_entry(group, key) };
|
return Optional<bool> { config.read_bool_entry(group, key) };
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientConnection::write_string_value(String const& domain, String const& group, String const& key, String const& value)
|
void ClientConnection::write_string_value(String const& domain, String const& group, String const& key, String const& value)
|
||||||
|
@ -86,8 +115,17 @@ void ClientConnection::write_string_value(String const& domain, String const& gr
|
||||||
if (!validate_access(domain, group, key))
|
if (!validate_access(domain, group, key))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto config = Core::ConfigFile::open_for_app(domain, Core::ConfigFile::AllowWriting::Yes);
|
auto& config = ensure_domain_config(domain);
|
||||||
config->write_entry(group, key, value);
|
|
||||||
|
if (config.has_key(group, key) && config.read_entry(group, key) == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
config.write_entry(group, key, value);
|
||||||
|
|
||||||
|
for (auto& it : s_connections) {
|
||||||
|
if (it.value != this && it.value->m_monitored_domains.contains(domain))
|
||||||
|
it.value->async_notify_changed_string_value(domain, group, key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientConnection::write_i32_value(String const& domain, String const& group, String const& key, i32 value)
|
void ClientConnection::write_i32_value(String const& domain, String const& group, String const& key, i32 value)
|
||||||
|
@ -95,8 +133,17 @@ void ClientConnection::write_i32_value(String const& domain, String const& group
|
||||||
if (!validate_access(domain, group, key))
|
if (!validate_access(domain, group, key))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto config = Core::ConfigFile::open_for_app(domain, Core::ConfigFile::AllowWriting::Yes);
|
auto& config = ensure_domain_config(domain);
|
||||||
config->write_num_entry(group, key, value);
|
|
||||||
|
if (config.has_key(group, key) && config.read_num_entry(group, key) == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
config.write_num_entry(group, key, value);
|
||||||
|
|
||||||
|
for (auto& it : s_connections) {
|
||||||
|
if (it.value != this && it.value->m_monitored_domains.contains(domain))
|
||||||
|
it.value->async_notify_changed_i32_value(domain, group, key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientConnection::write_bool_value(String const& domain, String const& group, String const& key, bool value)
|
void ClientConnection::write_bool_value(String const& domain, String const& group, String const& key, bool value)
|
||||||
|
@ -104,8 +151,17 @@ void ClientConnection::write_bool_value(String const& domain, String const& grou
|
||||||
if (!validate_access(domain, group, key))
|
if (!validate_access(domain, group, key))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto config = Core::ConfigFile::open_for_app(domain, Core::ConfigFile::AllowWriting::Yes);
|
auto& config = ensure_domain_config(domain);
|
||||||
config->write_bool_entry(group, key, value);
|
|
||||||
|
if (config.has_key(group, key) && config.read_bool_entry(group, key) == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
config.write_bool_entry(group, key, value);
|
||||||
|
|
||||||
|
for (auto& it : s_connections) {
|
||||||
|
if (it.value != this && it.value->m_monitored_domains.contains(domain))
|
||||||
|
it.value->async_notify_changed_bool_value(domain, group, key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ private:
|
||||||
explicit ClientConnection(NonnullRefPtr<Core::LocalSocket>, int client_id);
|
explicit ClientConnection(NonnullRefPtr<Core::LocalSocket>, int client_id);
|
||||||
|
|
||||||
virtual void pledge_domains(Vector<String> const&) override;
|
virtual void pledge_domains(Vector<String> const&) override;
|
||||||
|
virtual void monitor_domain(String const&) override;
|
||||||
virtual Messages::ConfigServer::ReadStringValueResponse read_string_value([[maybe_unused]] String const& domain, [[maybe_unused]] String const& group, [[maybe_unused]] String const& key) override;
|
virtual Messages::ConfigServer::ReadStringValueResponse read_string_value([[maybe_unused]] String const& domain, [[maybe_unused]] String const& group, [[maybe_unused]] String const& key) override;
|
||||||
virtual Messages::ConfigServer::ReadI32ValueResponse read_i32_value([[maybe_unused]] String const& domain, [[maybe_unused]] String const& group, [[maybe_unused]] String const& key) override;
|
virtual Messages::ConfigServer::ReadI32ValueResponse read_i32_value([[maybe_unused]] String const& domain, [[maybe_unused]] String const& group, [[maybe_unused]] String const& key) override;
|
||||||
virtual Messages::ConfigServer::ReadBoolValueResponse read_bool_value([[maybe_unused]] String const& domain, [[maybe_unused]] String const& group, [[maybe_unused]] String const& key) override;
|
virtual Messages::ConfigServer::ReadBoolValueResponse read_bool_value([[maybe_unused]] String const& domain, [[maybe_unused]] String const& group, [[maybe_unused]] String const& key) override;
|
||||||
|
@ -35,6 +36,8 @@ private:
|
||||||
|
|
||||||
bool m_has_pledged { false };
|
bool m_has_pledged { false };
|
||||||
HashTable<String> m_pledged_domains;
|
HashTable<String> m_pledged_domains;
|
||||||
|
|
||||||
|
HashTable<String> m_monitored_domains;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
endpoint ConfigClient
|
endpoint ConfigClient
|
||||||
{
|
{
|
||||||
|
notify_changed_string_value(String domain, String group, String key, String value) =|
|
||||||
|
notify_changed_i32_value(String domain, String group, String key, i32 value) =|
|
||||||
|
notify_changed_bool_value(String domain, String group, String key, bool value) =|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ endpoint ConfigServer
|
||||||
{
|
{
|
||||||
pledge_domains(Vector<String> domains) =|
|
pledge_domains(Vector<String> domains) =|
|
||||||
|
|
||||||
|
monitor_domain(String domain) =|
|
||||||
|
|
||||||
read_string_value(String domain, String group, String key) => (Optional<String> value)
|
read_string_value(String domain, String group, String key) => (Optional<String> value)
|
||||||
read_i32_value(String domain, String group, String key) => (Optional<i32> value)
|
read_i32_value(String domain, String group, String key) => (Optional<i32> value)
|
||||||
read_bool_value(String domain, String group, String key) => (Optional<bool> value)
|
read_bool_value(String domain, String group, String key) => (Optional<bool> value)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue