KDNSSD

avahi-publicservice.cpp
1/*
2 This file is part of the KDE project
3
4 SPDX-FileCopyrightText: 2004, 2005 Jakub Stachowski <qbast@go2.pl>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "avahi-publicservice_p.h"
10
11#include <QCoreApplication>
12#include <QStringList>
13
14#include "publicservice.h"
15
16#include <config-kdnssd.h>
17#if HAVE_SYS_TYPES_H
18#include <sys/types.h>
19#endif
20#include "avahi_entrygroup_interface.h"
21#include "avahi_server_interface.h"
22#include "servicebrowser.h"
23
24namespace KDNSSD
25{
26PublicService::PublicService(const QString &name, const QString &type, unsigned int port, const QString &domain, const QStringList &subtypes)
27 : QObject()
28 , ServiceBase(new PublicServicePrivate(this, name, type, domain, port))
29{
30 KDNSSD_D;
31 if (domain.isNull()) {
32 d->m_domain = "local.";
33 }
34 d->m_subtypes = subtypes;
35}
36
37PublicService::~PublicService()
38{
39 stop();
40}
41
42void PublicServicePrivate::tryApply()
43{
44 if (fillEntryGroup()) {
45 commit();
46 } else {
47 m_parent->stop();
48 Q_EMIT m_parent->published(false);
49 }
50}
51
52void PublicServicePrivate::gotGlobalStateChanged(int state, const QString &error, QDBusMessage msg)
53{
54 if (!isOurMsg(msg)) {
55 return;
56 }
57 groupStateChanged(state, error);
58}
59
60void PublicService::setServiceName(const QString &serviceName)
61{
62 KDNSSD_D;
63 d->m_serviceName = serviceName;
64 if (d->m_running) {
65 d->m_group->Reset();
66 d->tryApply();
67 }
68}
69
71{
72 KDNSSD_D;
73 d->m_domain = domain;
74 if (d->m_running) {
75 d->m_group->Reset();
76 d->tryApply();
77 }
78}
79
81{
82 KDNSSD_D;
83 d->m_type = type;
84 if (d->m_running) {
85 d->m_group->Reset();
86 d->tryApply();
87 }
88}
89
91{
92 KDNSSD_D;
93 d->m_subtypes = subtypes;
94 if (d->m_running) {
95 d->m_group->Reset();
96 d->tryApply();
97 }
98}
99
101{
102 KDNSSD_D;
103 return d->m_subtypes;
104}
105
106void PublicService::setPort(unsigned short port)
107{
108 KDNSSD_D;
109 d->m_port = port;
110 if (d->m_running) {
111 d->m_group->Reset();
112 d->tryApply();
113 }
114}
115
117{
118 KDNSSD_D;
119 d->m_textData = textData;
120 if (d->m_running) {
121 d->m_group->Reset();
122 d->tryApply();
123 }
124}
125
127{
128 KDNSSD_D;
129 return d->m_published;
130}
131
133{
134 KDNSSD_D;
135 publishAsync();
136 while (d->m_running && !d->m_published) {
138 }
139 return d->m_published;
140}
141
143{
144 KDNSSD_D;
145 if (d->m_group) {
146 d->m_group->Reset();
147 }
148 d->m_running = false;
149 d->m_published = false;
150}
151bool PublicServicePrivate::fillEntryGroup()
152{
153 registerTypes();
154 if (!m_group) {
155 // Do not race!
156 // https://github.com/lathiat/avahi/issues/9
157 // Avahi's DBus API is incredibly racey with signals getting fired
158 // immediately after a request was made even though we may not yet be
159 // listening. In lieu of a proper upstream fix for this we'll unfortunately
160 // have to resort to this hack:
161 // We register to all signals regardless of path and then filter them once
162 // we know what "our" path is. This is much more fragile than a proper
163 // QDBusInterface assisted signal connection but unfortunately the only way
164 // we can reliably prevent signals getting lost in the race.
165 // This uses a fancy trick whereby using QDBusMessage as last argument will
166 // give us the correct signal argument types as well as the underlying
167 // message so that we may check the message path.
168 QDBusConnection::systemBus().connect("org.freedesktop.Avahi",
169 "",
170 "org.freedesktop.Avahi.EntryGroup",
171 "StateChanged",
172 this,
173 SLOT(gotGlobalStateChanged(int, QString, QDBusMessage)));
174 m_dbusObjectPath.clear();
175
176 QDBusReply<QDBusObjectPath> rep = m_server->EntryGroupNew();
177 if (!rep.isValid()) {
178 return false;
179 }
180
181 m_dbusObjectPath = rep.value().path();
182
183 m_group = new org::freedesktop::Avahi::EntryGroup("org.freedesktop.Avahi", m_dbusObjectPath, QDBusConnection::systemBus());
184 }
185 if (m_serviceName.isNull()) {
186 QDBusReply<QString> rep = m_server->GetHostName();
187 if (!rep.isValid()) {
188 return false;
189 }
190 m_serviceName = rep.value();
191 }
192
195 for (QMap<QString, QByteArray>::ConstIterator it = m_textData.constBegin(); it != itEnd; ++it)
196 if (it.value().isNull()) {
197 txt.append(it.key().toLatin1());
198 } else {
199 txt.append(it.key().toLatin1() + '=' + it.value());
200 }
201
202 for (;;) {
203 QDBusReply<void> ret = m_group->AddService(-1, -1, 0, m_serviceName, m_type, domainToDNS(m_domain), m_hostName, m_port, txt);
204 if (ret.isValid()) {
205 break;
206 }
207
208 // serious error, bail out
209 if (ret.error().name() != QLatin1String("org.freedesktop.Avahi.CollisionError")) {
210 return false;
211 }
212
213 // name collision, try another
214 QDBusReply<QString> rep = m_server->GetAlternativeServiceName(m_serviceName);
215 if (rep.isValid()) {
216 m_serviceName = rep.value();
217 } else {
218 return false;
219 }
220 }
221
222 for (const QString &subtype : std::as_const(m_subtypes)) {
223 m_group->AddServiceSubtype(-1, -1, 0, m_serviceName, m_type, domainToDNS(m_domain), subtype);
224 }
225 return true;
226}
227
228void PublicServicePrivate::serverStateChanged(int s, const QString &)
229{
230 if (!m_running) {
231 return;
232 }
233 switch (s) {
234 case AVAHI_SERVER_INVALID:
235 m_parent->stop();
236 Q_EMIT m_parent->published(false);
237 break;
238 case AVAHI_SERVER_REGISTERING:
239 case AVAHI_SERVER_COLLISION:
240 if (m_group) {
241 m_group->Reset();
242 }
243 m_collision = true;
244 break;
245 case AVAHI_SERVER_RUNNING:
246 if (m_collision) {
247 m_collision = false;
248 tryApply();
249 }
250 }
251}
252
254{
255 KDNSSD_D;
256 if (d->m_running) {
257 stop();
258 }
259
260 if (!d->m_server) {
261 d->m_server = new org::freedesktop::Avahi::Server(QStringLiteral("org.freedesktop.Avahi"), QStringLiteral("/"), QDBusConnection::systemBus());
262 connect(d->m_server, SIGNAL(StateChanged(int, QString)), d, SLOT(serverStateChanged(int, QString)));
263 }
264
265 int state = AVAHI_SERVER_INVALID;
266 QDBusReply<int> rep = d->m_server->GetState();
267
268 if (rep.isValid()) {
269 state = rep.value();
270 }
271 d->m_running = true;
272 d->m_collision = true; // make it look like server is getting out of collision to force registering
273 d->serverStateChanged(state, QString());
274}
275
276void PublicServicePrivate::groupStateChanged(int s, const QString &reason)
277{
278 switch (s) {
279 case AVAHI_ENTRY_GROUP_COLLISION: {
280 QDBusReply<QString> rep = m_server->GetAlternativeServiceName(m_serviceName);
281 if (rep.isValid()) {
282 m_parent->setServiceName(rep.value());
283 } else {
284 serverStateChanged(AVAHI_SERVER_INVALID, reason);
285 }
286 break;
287 }
288 case AVAHI_ENTRY_GROUP_ESTABLISHED:
289 m_published = true;
290 Q_EMIT m_parent->published(true);
291 break;
292 case AVAHI_ENTRY_GROUP_FAILURE:
293 serverStateChanged(AVAHI_SERVER_INVALID, reason);
294 break;
295 }
296}
297
298void PublicService::virtual_hook(int, void *)
299{
300}
301
302}
303
304#include "moc_avahi-publicservice_p.cpp"
305#include "moc_publicservice.cpp"
void stop()
Stops publishing or aborts an incomplete publish request.
bool publish()
Publish the service synchronously.
void setServiceName(const QString &serviceName)
Sets the name of the service.
void setDomain(const QString &domain)
Sets the domain where the service is published.
void setSubTypes(const QStringList &subtypes)
Sets the subtypetypes of the service.
PublicService(const QString &name=QString(), const QString &type=QString(), unsigned int port=0, const QString &domain=QString(), const QStringList &subtypes=QStringList())
Creates a service description that can be published.
void setPort(unsigned short port)
Sets the port.
void setType(const QString &type)
Sets the service type.
void published(bool successful)
Emitted when publishing is complete.
bool isPublished() const
Whether the service is currently published.
void setTextData(const QMap< QString, QByteArray > &textData)
Sets new text properties.
void publishAsync()
Publish the service asynchronously.
QStringList subtypes() const
The subtypes of service.
Describes a service.
Definition servicebase.h:41
QString serviceName() const
The name of the service.
unsigned short port() const
The port number of the service.
QMap< QString, QByteArray > textData() const
Additional text data associated with the service.
QString domain() const
The domain that the service belongs to.
QString type() const
The type of the service.
void processEvents(QEventLoop::ProcessEventsFlags flags)
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
QDBusConnection systemBus()
QString name() const const
const QDBusError & error()
bool isValid() const const
void append(QList< T > &&value)
const_iterator constBegin() const const
const_iterator constEnd() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool isNull() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:48 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.