lib/avahi/Publisher: make the service list dynamic

This allows editing the list of services at any time instead of
passing a constant list at construction time.  To do this, Service
instances are now caller-owned and managed in an IntrusiveList instead
of Publisher-owned in a std::forward_list.
This commit is contained in:
Max Kellermann 2024-01-17 18:10:28 +01:00 committed by Max Kellermann
parent b20b773189
commit 461da92064
5 changed files with 91 additions and 30 deletions

View File

@ -38,28 +38,41 @@ MakePidName(const char *prefix) noexcept
} }
Publisher::Publisher(Client &_client, const char *_name, Publisher::Publisher(Client &_client, const char *_name,
std::forward_list<Service> _services,
ErrorHandler &_error_handler) noexcept ErrorHandler &_error_handler) noexcept
:error_handler(_error_handler), :error_handler(_error_handler),
name(MakePidName(_name)), name(MakePidName(_name)),
client(_client), client(_client),
defer_register_services(client.GetEventLoop(), defer_register_services(client.GetEventLoop(),
BIND_THIS_METHOD(DeferredRegisterServices)), BIND_THIS_METHOD(DeferredRegisterServices))
services(std::move(_services))
{ {
assert(!services.empty());
client.AddListener(*this); client.AddListener(*this);
if (client.IsConnected())
defer_register_services.Schedule();
} }
Publisher::~Publisher() noexcept Publisher::~Publisher() noexcept
{ {
assert(services.empty());
client.RemoveListener(*this); client.RemoveListener(*this);
} }
void
Publisher::AddService(Service &service) noexcept
{
services.push_back(service);
if (visible && client.IsConnected())
defer_register_services.Schedule();
}
void
Publisher::RemoveService(Service &service) noexcept
{
services.erase(services.iterator_to(service));
if (visible && client.IsConnected())
defer_register_services.Schedule();
}
inline void inline void
Publisher::GroupCallback(AvahiEntryGroup *g, Publisher::GroupCallback(AvahiEntryGroup *g,
AvahiEntryGroupState state) noexcept AvahiEntryGroupState state) noexcept
@ -82,6 +95,7 @@ try {
} }
/* And recreate the services */ /* And recreate the services */
should_reset_group = false;
RegisterServices(*g); RegisterServices(*g);
break; break;
@ -122,7 +136,7 @@ AddService(AvahiEntryGroup &group, const Service &service,
static void static void
AddServices(AvahiEntryGroup &group, AddServices(AvahiEntryGroup &group,
const std::forward_list<Service> &services, const char *name) const IntrusiveList<Service> &services, const char *name)
{ {
for (const auto &i : services) for (const auto &i : services)
AddService(group, i, name); AddService(group, i, name);
@ -131,11 +145,20 @@ AddServices(AvahiEntryGroup &group,
void void
Publisher::RegisterServices(AvahiEntryGroup &g) Publisher::RegisterServices(AvahiEntryGroup &g)
{ {
if (should_reset_group) {
should_reset_group = false;
avahi_entry_group_reset(&g);
}
AddServices(g, services, name.c_str()); AddServices(g, services, name.c_str());
if (int error = avahi_entry_group_commit(&g); if (!services.empty()) {
error != AVAHI_OK) should_reset_group = true;
throw MakeError(error, "Failed to commit Avahi service group");
if (int error = avahi_entry_group_commit(&g);
error != AVAHI_OK)
throw MakeError(error, "Failed to commit Avahi service group");
}
} }
void void
@ -144,6 +167,8 @@ Publisher::RegisterServices(AvahiClient *c)
assert(visible); assert(visible);
if (!group) { if (!group) {
assert(!should_reset_group);
group.reset(avahi_entry_group_new(c, GroupCallback, this)); group.reset(avahi_entry_group_new(c, GroupCallback, this));
if (!group) if (!group)
throw MakeError(*c, "Failed to create Avahi service group"); throw MakeError(*c, "Failed to create Avahi service group");
@ -175,8 +200,12 @@ Publisher::HideServices() noexcept
defer_register_services.Cancel(); defer_register_services.Cancel();
if (group) if (group) {
should_reset_group = false;
avahi_entry_group_reset(group.get()); avahi_entry_group_reset(group.get());
} else {
assert(!should_reset_group);
}
} }
void void
@ -194,7 +223,10 @@ Publisher::ShowServices() noexcept
void void
Publisher::OnAvahiConnect(AvahiClient *c) noexcept Publisher::OnAvahiConnect(AvahiClient *c) noexcept
{ {
if (visible) assert(!group);
assert(!should_reset_group);
if (visible && !services.empty())
try { try {
RegisterServices(c); RegisterServices(c);
} catch (...) { } catch (...) {
@ -206,12 +238,14 @@ void
Publisher::OnAvahiDisconnect() noexcept Publisher::OnAvahiDisconnect() noexcept
{ {
group.reset(); group.reset();
should_reset_group = false;
} }
void void
Publisher::OnAvahiChanged() noexcept Publisher::OnAvahiChanged() noexcept
{ {
group.reset(); group.reset();
should_reset_group = false;
} }
} // namespace Avahi } // namespace Avahi

View File

@ -7,11 +7,11 @@
#include "EntryGroup.hxx" #include "EntryGroup.hxx"
#include "ConnectionListener.hxx" #include "ConnectionListener.hxx"
#include "event/DeferEvent.hxx" #include "event/DeferEvent.hxx"
#include "util/IntrusiveList.hxx"
#include <avahi-client/publish.h> #include <avahi-client/publish.h>
#include <string> #include <string>
#include <forward_list>
class SocketAddress; class SocketAddress;
@ -36,7 +36,14 @@ class Publisher final : ConnectionListener {
EntryGroupPtr group; EntryGroupPtr group;
const std::forward_list<Service> services; IntrusiveList<Service> services;
/**
* Should avahi_entry_group_reset() be called by the next
* RegisterServices() call? This is true if the #gorup is
* non-empty.
*/
bool should_reset_group = false;
/** /**
* Shall the published services be visible? This is controlled by * Shall the published services be visible? This is controlled by
@ -46,13 +53,28 @@ class Publisher final : ConnectionListener {
public: public:
Publisher(Client &client, const char *_name, Publisher(Client &client, const char *_name,
std::forward_list<Service> _services,
ErrorHandler &_error_handler) noexcept; ErrorHandler &_error_handler) noexcept;
~Publisher() noexcept; ~Publisher() noexcept;
Publisher(const Publisher &) = delete; Publisher(const Publisher &) = delete;
Publisher &operator=(const Publisher &) = delete; Publisher &operator=(const Publisher &) = delete;
/**
* Publish another service.
*
* @param service a #Service instance owned by the caller (it
* must remain valid until you call RemoveService())
*/
void AddService(Service &service) noexcept;
/**
* Unpublish a service.
*
* @param service a #Service instance which was previously
* passed to AddService()
*/
void RemoveService(Service &service) noexcept;
/** /**
* Temporarily hide all registered services. You can undo this * Temporarily hide all registered services. You can undo this
* with ShowServices(). * with ShowServices().

View File

@ -4,6 +4,8 @@
#pragma once #pragma once
#include "util/IntrusiveList.hxx"
#include <avahi-common/address.h> #include <avahi-common/address.h>
#include <cstdint> #include <cstdint>
@ -14,7 +16,7 @@ namespace Avahi {
/** /**
* A service that will be published by class #Publisher. * A service that will be published by class #Publisher.
*/ */
struct Service { struct Service : IntrusiveListHook<> {
AvahiIfIndex interface = AVAHI_IF_UNSPEC; AvahiIfIndex interface = AVAHI_IF_UNSPEC;
AvahiProtocol protocol = AVAHI_PROTO_UNSPEC; AvahiProtocol protocol = AVAHI_PROTO_UNSPEC;
std::string type; std::string type;

View File

@ -4,7 +4,6 @@
#include "Helper.hxx" #include "Helper.hxx"
#include "lib/avahi/Client.hxx" #include "lib/avahi/Client.hxx"
#include "lib/avahi/ErrorHandler.hxx" #include "lib/avahi/ErrorHandler.hxx"
#include "lib/avahi/Service.hxx"
#include "lib/fmt/RuntimeError.hxx" #include "lib/fmt/RuntimeError.hxx"
#include "Log.hxx" #include "Log.hxx"
@ -29,12 +28,19 @@ static std::weak_ptr<SharedAvahiClient> shared_avahi_client;
inline inline
AvahiHelper::AvahiHelper(std::shared_ptr<SharedAvahiClient> _client, AvahiHelper::AvahiHelper(std::shared_ptr<SharedAvahiClient> _client,
const char *service_name, const char *service_name,
std::forward_list<Avahi::Service> &&services) const char *service_type, unsigned port)
:client(std::move(_client)), :client(std::move(_client)),
publisher(client->client, service_name, publisher(client->client, service_name, *client),
std::move(services), *client) {} service(AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
service_type, port)
{
publisher.AddService(service);
}
AvahiHelper::~AvahiHelper() noexcept = default; AvahiHelper::~AvahiHelper() noexcept
{
publisher.RemoveService(service);
}
std::unique_ptr<AvahiHelper> std::unique_ptr<AvahiHelper>
AvahiInit(EventLoop &event_loop, const char *service_name, AvahiInit(EventLoop &event_loop, const char *service_name,
@ -49,11 +55,6 @@ AvahiInit(EventLoop &event_loop, const char *service_name,
shared_avahi_client = client = shared_avahi_client = client =
std::make_shared<SharedAvahiClient>(event_loop); std::make_shared<SharedAvahiClient>(event_loop);
std::forward_list<Avahi::Service> services;
services.emplace_front(AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC,
service_type, port);
return std::make_unique<AvahiHelper>(std::move(client), service_name, return std::make_unique<AvahiHelper>(std::move(client), service_name,
std::move(services)); service_type, port);
} }

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include "lib/avahi/Publisher.hxx" #include "lib/avahi/Publisher.hxx"
#include "lib/avahi/Service.hxx"
#include <memory> #include <memory>
@ -15,11 +16,12 @@ class SharedAvahiClient;
class AvahiHelper final { class AvahiHelper final {
std::shared_ptr<SharedAvahiClient> client; std::shared_ptr<SharedAvahiClient> client;
Avahi::Publisher publisher; Avahi::Publisher publisher;
Avahi::Service service;
public: public:
AvahiHelper(std::shared_ptr<SharedAvahiClient> _client, AvahiHelper(std::shared_ptr<SharedAvahiClient> _client,
const char *service_name, const char *service_name,
std::forward_list<Avahi::Service> &&services); const char *service_type, unsigned port);
~AvahiHelper() noexcept; ~AvahiHelper() noexcept;
}; };