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 "servicebrowser.h"
21 #include "avahi_server_interface.h"
22 #include "avahi_entrygroup_interface.h"
23 
24 namespace KDNSSD
25 {
26 
27 PublicService::PublicService(const QString &name, const QString &type, unsigned int port,
28  const QString &domain, const QStringList &subtypes)
29  : QObject(), ServiceBase(new PublicServicePrivate(this, name, type, domain, port))
30 {
31  K_D;
32  if (domain.isNull()) {
33  d->m_domain = "local.";
34  }
35  d->m_subtypes = subtypes;
36 }
37 
38 PublicService::~PublicService()
39 {
40  stop();
41 }
42 
43 void PublicServicePrivate::tryApply()
44 {
45  if (fillEntryGroup()) {
46  commit();
47  } else {
48  m_parent->stop();
49  emit m_parent->published(false);
50  }
51 }
52 
53 void PublicServicePrivate::gotGlobalStateChanged(int state,
54  const QString &error,
55  QDBusMessage msg)
56 {
57  if (!isOurMsg(msg)) {
58  return;
59  }
60  groupStateChanged(state, error);
61 }
62 
64 {
65  K_D;
66  d->m_serviceName = serviceName;
67  if (d->m_running) {
68  d->m_group->Reset();
69  d->tryApply();
70  }
71 }
72 
74 {
75  K_D;
76  d->m_domain = domain;
77  if (d->m_running) {
78  d->m_group->Reset();
79  d->tryApply();
80  }
81 }
82 
84 {
85  K_D;
86  d->m_type = type;
87  if (d->m_running) {
88  d->m_group->Reset();
89  d->tryApply();
90  }
91 }
92 
94 {
95  K_D;
96  d->m_subtypes = subtypes;
97  if (d->m_running) {
98  d->m_group->Reset();
99  d->tryApply();
100  }
101 }
102 
104 {
105  K_D;
106  return d->m_subtypes;
107 }
108 
109 void PublicService::setPort(unsigned short port)
110 {
111  K_D;
112  d->m_port = port;
113  if (d->m_running) {
114  d->m_group->Reset();
115  d->tryApply();
116  }
117 }
118 
120 {
121  K_D;
122  d->m_textData = textData;
123  if (d->m_running) {
124  d->m_group->Reset();
125  d->tryApply();
126  }
127 }
128 
130 {
131  K_D;
132  return d->m_published;
133 }
134 
136 {
137  K_D;
138  publishAsync();
139  while (d->m_running && !d->m_published) {
141  }
142  return d->m_published;
143 }
144 
146 {
147  K_D;
148  if (d->m_group) {
149  d->m_group->Reset();
150  }
151  d->m_running = false;
152  d->m_published = false;
153 }
154 bool PublicServicePrivate::fillEntryGroup()
155 {
156  registerTypes();
157  if (!m_group) {
158  // Do not race!
159  // https://github.com/lathiat/avahi/issues/9
160  // Avahi's DBus API is incredibly racey with signals getting fired
161  // immediately after a request was made even though we may not yet be
162  // listening. In lieu of a proper upstream fix for this we'll unfortunately
163  // have to resort to this hack:
164  // We register to all signals regardless of path and then filter them once
165  // we know what "our" path is. This is much more fragile than a proper
166  // QDBusInterface assisted signal connection but unfortunately the only way
167  // we can reliably prevent signals getting lost in the race.
168  // This uses a fancy trick whereby using QDBusMessage as last argument will
169  // give us the correct signal argument types as well as the underlying
170  // message so that we may check the message path.
172  .connect("org.freedesktop.Avahi",
173  "",
174  "org.freedesktop.Avahi.EntryGroup",
175  "StateChanged",
176  this,
177  SLOT(gotGlobalStateChanged(int,QString,QDBusMessage)));
178  m_dbusObjectPath.clear();
179 
180  QDBusReply<QDBusObjectPath> rep = m_server->EntryGroupNew();
181  if (!rep.isValid()) {
182  return false;
183  }
184 
185  m_dbusObjectPath = rep.value().path();
186 
187  m_group = new org::freedesktop::Avahi::EntryGroup("org.freedesktop.Avahi",
188  m_dbusObjectPath,
190  }
191  if (m_serviceName.isNull()) {
192  QDBusReply<QString> rep = m_server->GetHostName();
193  if (!rep.isValid()) {
194  return false;
195  }
196  m_serviceName = rep.value();
197  }
198 
199  QList<QByteArray> txt;
201  for (QMap<QString, QByteArray>::ConstIterator it = m_textData.constBegin(); it != itEnd; ++it)
202  if (it.value().isNull()) {
203  txt.append(it.key().toLatin1());
204  } else {
205  txt.append(it.key().toLatin1() + '=' + it.value());
206  }
207 
208  for (;;) {
209  QDBusReply<void> ret = m_group->AddService(-1, -1, 0, m_serviceName, m_type, domainToDNS(m_domain),
210  m_hostName, m_port, txt);
211  if (ret.isValid()) {
212  break;
213  }
214 
215  // serious error, bail out
216  if (ret.error().name() != QLatin1String("org.freedesktop.Avahi.CollisionError")) {
217  return false;
218  }
219 
220  // name collision, try another
221  QDBusReply<QString> rep = m_server->GetAlternativeServiceName(m_serviceName);
222  if (rep.isValid()) {
223  m_serviceName = rep.value();
224  } else {
225  return false;
226  }
227  }
228 
229  for (const QString &subtype : qAsConst(m_subtypes)) {
230  m_group->AddServiceSubtype(-1, -1, 0, m_serviceName, m_type, domainToDNS(m_domain), subtype);
231  }
232  return true;
233 }
234 
235 void PublicServicePrivate::serverStateChanged(int s, const QString &)
236 {
237  if (!m_running) {
238  return;
239  }
240  switch (s) {
241  case AVAHI_SERVER_INVALID:
242  m_parent->stop();
243  emit m_parent->published(false);
244  break;
245  case AVAHI_SERVER_REGISTERING:
246  case AVAHI_SERVER_COLLISION:
247  if (m_group) {
248  m_group->Reset();
249  }
250  m_collision = true;
251  break;
252  case AVAHI_SERVER_RUNNING:
253  if (m_collision) {
254  m_collision = false;
255  tryApply();
256  }
257  }
258 }
259 
261 {
262  K_D;
263  if (d->m_running) {
264  stop();
265  }
266 
267  if (!d->m_server) {
268  d->m_server = new org::freedesktop::Avahi::Server(QStringLiteral("org.freedesktop.Avahi"), QStringLiteral("/"), QDBusConnection::systemBus());
269  connect(d->m_server, SIGNAL(StateChanged(int,QString)), d, SLOT(serverStateChanged(int,QString)));
270  }
271 
272  int state = AVAHI_SERVER_INVALID;
273  QDBusReply<int> rep = d->m_server->GetState();
274 
275  if (rep.isValid()) {
276  state = rep.value();
277  }
278  d->m_running = true;
279  d->m_collision = true; // make it look like server is getting out of collision to force registering
280  d->serverStateChanged(state, QString());
281 }
282 
283 void PublicServicePrivate::groupStateChanged(int s, const QString &reason)
284 {
285  switch (s) {
286  case AVAHI_ENTRY_GROUP_COLLISION: {
287  QDBusReply<QString> rep = m_server->GetAlternativeServiceName(m_serviceName);
288  if (rep.isValid()) {
289  m_parent->setServiceName(rep.value());
290  } else {
291  serverStateChanged(AVAHI_SERVER_INVALID, reason);
292  }
293  break;
294  }
295  case AVAHI_ENTRY_GROUP_ESTABLISHED:
296  m_published = true;
297  emit m_parent->published(true);
298  break;
299  case AVAHI_ENTRY_GROUP_FAILURE:
300  serverStateChanged(AVAHI_SERVER_INVALID, reason);
301  break;
302  }
303 }
304 
305 void PublicService::virtual_hook(int, void *)
306 {
307 }
308 
309 }
310 
311 #include "moc_publicservice.cpp"
312 #include "moc_avahi-publicservice_p.cpp"
void setPort(unsigned short port)
Sets the port.
QString serviceName() const
The name of the service.
Definition: servicebase.cpp:32
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:56
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:39
unsigned short port() const
The port number of the service.
Definition: servicebase.cpp:52
void setSubTypes(const QStringList &subtypes)
Sets the subtypetypes of the service.
QString type() const
The type of the service.
Definition: servicebase.cpp:37
QString domain() const
The domain that the service belongs to.
Definition: servicebase.cpp:42
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.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Fri Aug 7 2020 22:38:33 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.