KDNSSD

avahi-publicservice.cpp
1 /*
2  This file is part of the KDE project
3 
4  SPDX-FileCopyrightText: 2004, 2005 Jakub Stachowski <[email protected]>
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 
24 namespace KDNSSD
25 {
26 PublicService::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 
37 PublicService::~PublicService()
38 {
39  stop();
40 }
41 
42 void PublicServicePrivate::tryApply()
43 {
44  if (fillEntryGroup()) {
45  commit();
46  } else {
47  m_parent->stop();
48  Q_EMIT m_parent->published(false);
49  }
50 }
51 
52 void PublicServicePrivate::gotGlobalStateChanged(int state, const QString &error, QDBusMessage msg)
53 {
54  if (!isOurMsg(msg)) {
55  return;
56  }
57  groupStateChanged(state, error);
58 }
59 
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 
106 void 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 }
151 bool 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 
193  QList<QByteArray> txt;
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 : qAsConst(m_subtypes)) {
223  m_group->AddServiceSubtype(-1, -1, 0, m_serviceName, m_type, domainToDNS(m_domain), subtype);
224  }
225  return true;
226 }
227 
228 void 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 
276 void 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 
298 void PublicService::virtual_hook(int, void *)
299 {
300 }
301 
302 }
303 
304 #include "moc_avahi-publicservice_p.cpp"
305 #include "moc_publicservice.cpp"
void setPort(unsigned short port)
Sets the port.
QString serviceName() const
The name of the service.
Definition: servicebase.cpp:28
void setTextData(const QMap< QString, QByteArray > &textData)
Sets new text properties.
QMap::const_iterator constBegin() const const
QString name() const const
QDBusConnection systemBus()
bool isValid() const const
void setType(const QString &type)
Sets the service type.
bool isNull() const const
QMap< QString, QByteArray > textData() const
Additional text data associated with the service.
Definition: servicebase.cpp:52
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.
bool publish()
Publish the service synchronously.
void publishAsync()
Publish the service asynchronously.
void processEvents(QEventLoop::ProcessEventsFlags flags)
void append(const T &value)
QDBusReply::Type value() const const
QMap::const_iterator constEnd() const const
Describes a service.
Definition: servicebase.h:40
unsigned short port() const
The port number of the service.
Definition: servicebase.cpp:48
void setSubTypes(const QStringList &subtypes)
Sets the subtypetypes of the service.
QString type() const
The type of the service.
Definition: servicebase.cpp:33
QString domain() const
The domain that the service belongs to.
Definition: servicebase.cpp:38
const QDBusError & error()
void setServiceName(const QString &serviceName)
Sets the name of the service.
void stop()
Stops publishing or aborts an incomplete publish request.
QStringList subtypes() const
The subtypes of service.
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void setDomain(const QString &domain)
Sets the domain where the service is published.
bool isPublished() const
Whether the service is currently published.
Q_EMITQ_EMIT
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun Jun 20 2021 22:40:22 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.