File indexing completed on 2024-04-28 15:22:05
0001 /* 0002 This file is part of the KDE project 0003 0004 SPDX-FileCopyrightText: 2004, 2005 Jakub Stachowski <qbast@go2.pl> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "avahi-publicservice_p.h" 0010 0011 #include <QCoreApplication> 0012 #include <QStringList> 0013 0014 #include "publicservice.h" 0015 0016 #include <config-kdnssd.h> 0017 #if HAVE_SYS_TYPES_H 0018 #include <sys/types.h> 0019 #endif 0020 #include "avahi_entrygroup_interface.h" 0021 #include "avahi_server_interface.h" 0022 #include "servicebrowser.h" 0023 0024 namespace KDNSSD 0025 { 0026 PublicService::PublicService(const QString &name, const QString &type, unsigned int port, const QString &domain, const QStringList &subtypes) 0027 : QObject() 0028 , ServiceBase(new PublicServicePrivate(this, name, type, domain, port)) 0029 { 0030 KDNSSD_D; 0031 if (domain.isNull()) { 0032 d->m_domain = "local."; 0033 } 0034 d->m_subtypes = subtypes; 0035 } 0036 0037 PublicService::~PublicService() 0038 { 0039 stop(); 0040 } 0041 0042 void PublicServicePrivate::tryApply() 0043 { 0044 if (fillEntryGroup()) { 0045 commit(); 0046 } else { 0047 m_parent->stop(); 0048 Q_EMIT m_parent->published(false); 0049 } 0050 } 0051 0052 void PublicServicePrivate::gotGlobalStateChanged(int state, const QString &error, QDBusMessage msg) 0053 { 0054 if (!isOurMsg(msg)) { 0055 return; 0056 } 0057 groupStateChanged(state, error); 0058 } 0059 0060 void PublicService::setServiceName(const QString &serviceName) 0061 { 0062 KDNSSD_D; 0063 d->m_serviceName = serviceName; 0064 if (d->m_running) { 0065 d->m_group->Reset(); 0066 d->tryApply(); 0067 } 0068 } 0069 0070 void PublicService::setDomain(const QString &domain) 0071 { 0072 KDNSSD_D; 0073 d->m_domain = domain; 0074 if (d->m_running) { 0075 d->m_group->Reset(); 0076 d->tryApply(); 0077 } 0078 } 0079 0080 void PublicService::setType(const QString &type) 0081 { 0082 KDNSSD_D; 0083 d->m_type = type; 0084 if (d->m_running) { 0085 d->m_group->Reset(); 0086 d->tryApply(); 0087 } 0088 } 0089 0090 void PublicService::setSubTypes(const QStringList &subtypes) 0091 { 0092 KDNSSD_D; 0093 d->m_subtypes = subtypes; 0094 if (d->m_running) { 0095 d->m_group->Reset(); 0096 d->tryApply(); 0097 } 0098 } 0099 0100 QStringList PublicService::subtypes() const 0101 { 0102 KDNSSD_D; 0103 return d->m_subtypes; 0104 } 0105 0106 void PublicService::setPort(unsigned short port) 0107 { 0108 KDNSSD_D; 0109 d->m_port = port; 0110 if (d->m_running) { 0111 d->m_group->Reset(); 0112 d->tryApply(); 0113 } 0114 } 0115 0116 void PublicService::setTextData(const QMap<QString, QByteArray> &textData) 0117 { 0118 KDNSSD_D; 0119 d->m_textData = textData; 0120 if (d->m_running) { 0121 d->m_group->Reset(); 0122 d->tryApply(); 0123 } 0124 } 0125 0126 bool PublicService::isPublished() const 0127 { 0128 KDNSSD_D; 0129 return d->m_published; 0130 } 0131 0132 bool PublicService::publish() 0133 { 0134 KDNSSD_D; 0135 publishAsync(); 0136 while (d->m_running && !d->m_published) { 0137 QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); 0138 } 0139 return d->m_published; 0140 } 0141 0142 void PublicService::stop() 0143 { 0144 KDNSSD_D; 0145 if (d->m_group) { 0146 d->m_group->Reset(); 0147 } 0148 d->m_running = false; 0149 d->m_published = false; 0150 } 0151 bool PublicServicePrivate::fillEntryGroup() 0152 { 0153 registerTypes(); 0154 if (!m_group) { 0155 // Do not race! 0156 // https://github.com/lathiat/avahi/issues/9 0157 // Avahi's DBus API is incredibly racey with signals getting fired 0158 // immediately after a request was made even though we may not yet be 0159 // listening. In lieu of a proper upstream fix for this we'll unfortunately 0160 // have to resort to this hack: 0161 // We register to all signals regardless of path and then filter them once 0162 // we know what "our" path is. This is much more fragile than a proper 0163 // QDBusInterface assisted signal connection but unfortunately the only way 0164 // we can reliably prevent signals getting lost in the race. 0165 // This uses a fancy trick whereby using QDBusMessage as last argument will 0166 // give us the correct signal argument types as well as the underlying 0167 // message so that we may check the message path. 0168 QDBusConnection::systemBus().connect("org.freedesktop.Avahi", 0169 "", 0170 "org.freedesktop.Avahi.EntryGroup", 0171 "StateChanged", 0172 this, 0173 SLOT(gotGlobalStateChanged(int, QString, QDBusMessage))); 0174 m_dbusObjectPath.clear(); 0175 0176 QDBusReply<QDBusObjectPath> rep = m_server->EntryGroupNew(); 0177 if (!rep.isValid()) { 0178 return false; 0179 } 0180 0181 m_dbusObjectPath = rep.value().path(); 0182 0183 m_group = new org::freedesktop::Avahi::EntryGroup("org.freedesktop.Avahi", m_dbusObjectPath, QDBusConnection::systemBus()); 0184 } 0185 if (m_serviceName.isNull()) { 0186 QDBusReply<QString> rep = m_server->GetHostName(); 0187 if (!rep.isValid()) { 0188 return false; 0189 } 0190 m_serviceName = rep.value(); 0191 } 0192 0193 QList<QByteArray> txt; 0194 QMap<QString, QByteArray>::ConstIterator itEnd = m_textData.constEnd(); 0195 for (QMap<QString, QByteArray>::ConstIterator it = m_textData.constBegin(); it != itEnd; ++it) 0196 if (it.value().isNull()) { 0197 txt.append(it.key().toLatin1()); 0198 } else { 0199 txt.append(it.key().toLatin1() + '=' + it.value()); 0200 } 0201 0202 for (;;) { 0203 QDBusReply<void> ret = m_group->AddService(-1, -1, 0, m_serviceName, m_type, domainToDNS(m_domain), m_hostName, m_port, txt); 0204 if (ret.isValid()) { 0205 break; 0206 } 0207 0208 // serious error, bail out 0209 if (ret.error().name() != QLatin1String("org.freedesktop.Avahi.CollisionError")) { 0210 return false; 0211 } 0212 0213 // name collision, try another 0214 QDBusReply<QString> rep = m_server->GetAlternativeServiceName(m_serviceName); 0215 if (rep.isValid()) { 0216 m_serviceName = rep.value(); 0217 } else { 0218 return false; 0219 } 0220 } 0221 0222 for (const QString &subtype : std::as_const(m_subtypes)) { 0223 m_group->AddServiceSubtype(-1, -1, 0, m_serviceName, m_type, domainToDNS(m_domain), subtype); 0224 } 0225 return true; 0226 } 0227 0228 void PublicServicePrivate::serverStateChanged(int s, const QString &) 0229 { 0230 if (!m_running) { 0231 return; 0232 } 0233 switch (s) { 0234 case AVAHI_SERVER_INVALID: 0235 m_parent->stop(); 0236 Q_EMIT m_parent->published(false); 0237 break; 0238 case AVAHI_SERVER_REGISTERING: 0239 case AVAHI_SERVER_COLLISION: 0240 if (m_group) { 0241 m_group->Reset(); 0242 } 0243 m_collision = true; 0244 break; 0245 case AVAHI_SERVER_RUNNING: 0246 if (m_collision) { 0247 m_collision = false; 0248 tryApply(); 0249 } 0250 } 0251 } 0252 0253 void PublicService::publishAsync() 0254 { 0255 KDNSSD_D; 0256 if (d->m_running) { 0257 stop(); 0258 } 0259 0260 if (!d->m_server) { 0261 d->m_server = new org::freedesktop::Avahi::Server(QStringLiteral("org.freedesktop.Avahi"), QStringLiteral("/"), QDBusConnection::systemBus()); 0262 connect(d->m_server, SIGNAL(StateChanged(int, QString)), d, SLOT(serverStateChanged(int, QString))); 0263 } 0264 0265 int state = AVAHI_SERVER_INVALID; 0266 QDBusReply<int> rep = d->m_server->GetState(); 0267 0268 if (rep.isValid()) { 0269 state = rep.value(); 0270 } 0271 d->m_running = true; 0272 d->m_collision = true; // make it look like server is getting out of collision to force registering 0273 d->serverStateChanged(state, QString()); 0274 } 0275 0276 void PublicServicePrivate::groupStateChanged(int s, const QString &reason) 0277 { 0278 switch (s) { 0279 case AVAHI_ENTRY_GROUP_COLLISION: { 0280 QDBusReply<QString> rep = m_server->GetAlternativeServiceName(m_serviceName); 0281 if (rep.isValid()) { 0282 m_parent->setServiceName(rep.value()); 0283 } else { 0284 serverStateChanged(AVAHI_SERVER_INVALID, reason); 0285 } 0286 break; 0287 } 0288 case AVAHI_ENTRY_GROUP_ESTABLISHED: 0289 m_published = true; 0290 Q_EMIT m_parent->published(true); 0291 break; 0292 case AVAHI_ENTRY_GROUP_FAILURE: 0293 serverStateChanged(AVAHI_SERVER_INVALID, reason); 0294 break; 0295 } 0296 } 0297 0298 void PublicService::virtual_hook(int, void *) 0299 { 0300 } 0301 0302 } 0303 0304 #include "moc_avahi-publicservice_p.cpp" 0305 #include "moc_publicservice.cpp"