File indexing completed on 2024-05-12 11:49:42
0001 /* 0002 This file is part of the KDE project 0003 0004 SPDX-FileCopyrightText: 2004 Jakub Stachowski <qbast@go2.pl> 0005 SPDX-FileCopyrightText: 2018 Harald Sitter <sitter@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "avahi-servicebrowser_p.h" 0011 #include "avahi_server_interface.h" 0012 #include "avahi_servicebrowser_interface.h" 0013 #include "servicebrowser.h" 0014 #include <QHash> 0015 #include <QHostAddress> 0016 #include <QStringList> 0017 0018 namespace KDNSSD 0019 { 0020 ServiceBrowser::ServiceBrowser(const QString &type, bool autoResolve, const QString &domain, const QString &subtype) 0021 : d(new ServiceBrowserPrivate(this)) 0022 { 0023 Q_D(ServiceBrowser); 0024 d->m_type = type; 0025 d->m_subtype = subtype; 0026 d->m_autoResolve = autoResolve; 0027 d->m_domain = domain; 0028 d->m_timer.setSingleShot(true); 0029 } 0030 0031 ServiceBrowser::State ServiceBrowser::isAvailable() 0032 { 0033 org::freedesktop::Avahi::Server s(QStringLiteral("org.freedesktop.Avahi"), QStringLiteral("/"), QDBusConnection::systemBus()); 0034 QDBusReply<int> rep = s.GetState(); 0035 return (rep.isValid() && rep.value() == 2) ? Working : Stopped; 0036 } 0037 0038 ServiceBrowser::~ServiceBrowser() = default; 0039 0040 bool ServiceBrowser::isAutoResolving() const 0041 { 0042 Q_D(const ServiceBrowser); 0043 return d->m_autoResolve; 0044 } 0045 0046 void ServiceBrowser::startBrowse() 0047 { 0048 Q_D(ServiceBrowser); 0049 if (d->m_running) { 0050 return; 0051 } 0052 0053 // Do not race! 0054 // https://github.com/lathiat/avahi/issues/9 0055 // Avahi's DBus API is incredibly racey with signals getting fired 0056 // immediately after a request was made even though we may not yet be 0057 // listening. In lieu of a proper upstream fix for this we'll unfortunately 0058 // have to resort to this hack: 0059 // We register to all signals regardless of path and then filter them once 0060 // we know what "our" path is. This is much more fragile than a proper 0061 // QDBusInterface assisted signal connection but unfortunately the only way 0062 // we can reliably prevent signals getting lost in the race. 0063 // This uses a fancy trick whereby using QDBusMessage as last argument will 0064 // give us the correct signal argument types as well as the underlying 0065 // message so that we may check the message path. 0066 QDBusConnection::systemBus().connect("org.freedesktop.Avahi", 0067 "", 0068 "org.freedesktop.Avahi.ServiceBrowser", 0069 "ItemNew", 0070 d, 0071 SLOT(gotGlobalItemNew(int, int, QString, QString, QString, uint, QDBusMessage))); 0072 QDBusConnection::systemBus().connect("org.freedesktop.Avahi", 0073 "", 0074 "org.freedesktop.Avahi.ServiceBrowser", 0075 "ItemRemove", 0076 d, 0077 SLOT(gotGlobalItemRemove(int, int, QString, QString, QString, uint, QDBusMessage))); 0078 QDBusConnection::systemBus() 0079 .connect("org.freedesktop.Avahi", "", "org.freedesktop.Avahi.ServiceBrowser", "AllForNow", d, SLOT(gotGlobalAllForNow(QDBusMessage))); 0080 d->m_dbusObjectPath.clear(); 0081 0082 org::freedesktop::Avahi::Server s(QStringLiteral("org.freedesktop.Avahi"), QStringLiteral("/"), QDBusConnection::systemBus()); 0083 0084 QString fullType = d->m_type; 0085 if (!d->m_subtype.isEmpty()) { 0086 fullType = d->m_subtype + QStringLiteral("._sub.") + d->m_type; 0087 } 0088 QDBusReply<QDBusObjectPath> rep = s.ServiceBrowserNew(-1, -1, fullType, domainToDNS(d->m_domain), 0); 0089 if (!rep.isValid()) { 0090 Q_EMIT finished(); 0091 return; 0092 } 0093 0094 d->m_dbusObjectPath = rep.value().path(); 0095 d->m_running = true; 0096 d->m_browserFinished = true; 0097 0098 // This is held because we need to explicitly Free it! 0099 d->m_browser = new org::freedesktop::Avahi::ServiceBrowser(s.service(), d->m_dbusObjectPath, s.connection()); 0100 0101 connect(&d->m_timer, &QTimer::timeout, d, &ServiceBrowserPrivate::browserFinished); 0102 d->m_timer.start(domainIsLocal(d->m_domain) ? TIMEOUT_LAST_SERVICE : TIMEOUT_START_WAN); 0103 } 0104 0105 void ServiceBrowserPrivate::serviceResolved(bool success) 0106 { 0107 QObject *sender_obj = const_cast<QObject *>(sender()); 0108 RemoteService *svr = static_cast<RemoteService *>(sender_obj); 0109 disconnect(svr, SIGNAL(resolved(bool)), this, SLOT(serviceResolved(bool))); 0110 QList<RemoteService::Ptr>::Iterator it = m_duringResolve.begin(); 0111 QList<RemoteService::Ptr>::Iterator itEnd = m_duringResolve.end(); 0112 while (it != itEnd && svr != (*it).data()) { 0113 ++it; 0114 } 0115 if (it != itEnd) { 0116 if (success) { 0117 m_services += (*it); 0118 Q_EMIT m_parent->serviceAdded(RemoteService::Ptr(svr)); 0119 } 0120 m_duringResolve.erase(it); 0121 queryFinished(); 0122 } 0123 } 0124 0125 void ServiceBrowserPrivate::gotGlobalItemNew(int interface, 0126 int protocol, 0127 const QString &name, 0128 const QString &type, 0129 const QString &domain, 0130 uint flags, 0131 QDBusMessage msg) 0132 { 0133 if (!isOurMsg(msg)) { 0134 return; 0135 } 0136 gotNewService(interface, protocol, name, type, domain, flags); 0137 } 0138 0139 void ServiceBrowserPrivate::gotGlobalItemRemove(int interface, 0140 int protocol, 0141 const QString &name, 0142 const QString &type, 0143 const QString &domain, 0144 uint flags, 0145 QDBusMessage msg) 0146 { 0147 if (!isOurMsg(msg)) { 0148 return; 0149 } 0150 gotRemoveService(interface, protocol, name, type, domain, flags); 0151 } 0152 0153 void ServiceBrowserPrivate::gotGlobalAllForNow(QDBusMessage msg) 0154 { 0155 if (!isOurMsg(msg)) { 0156 return; 0157 } 0158 browserFinished(); 0159 } 0160 0161 RemoteService::Ptr ServiceBrowserPrivate::find(RemoteService::Ptr s, const QList<RemoteService::Ptr> &where) const 0162 { 0163 for (const RemoteService::Ptr &i : where) 0164 if (*s == *i) { 0165 return i; 0166 } 0167 return RemoteService::Ptr(); 0168 } 0169 0170 void ServiceBrowserPrivate::gotNewService(int, int, const QString &name, const QString &type, const QString &domain, uint) 0171 { 0172 m_timer.start(TIMEOUT_LAST_SERVICE); 0173 RemoteService::Ptr svr(new RemoteService(name, type, domain)); 0174 if (m_autoResolve) { 0175 connect(svr.data(), SIGNAL(resolved(bool)), this, SLOT(serviceResolved(bool))); 0176 m_duringResolve += svr; 0177 svr->resolveAsync(); 0178 } else { 0179 m_services += svr; 0180 Q_EMIT m_parent->serviceAdded(svr); 0181 } 0182 } 0183 0184 void ServiceBrowserPrivate::gotRemoveService(int, int, const QString &name, const QString &type, const QString &domain, uint) 0185 { 0186 m_timer.start(TIMEOUT_LAST_SERVICE); 0187 RemoteService::Ptr tmpl(new RemoteService(name, type, domain)); 0188 RemoteService::Ptr found = find(tmpl, m_duringResolve); 0189 if (found) { 0190 m_duringResolve.removeAll(found); 0191 return; 0192 } 0193 found = find(tmpl, m_services); 0194 if (!found) { 0195 return; 0196 } 0197 0198 Q_EMIT m_parent->serviceRemoved(found); 0199 m_services.removeAll(found); 0200 } 0201 void ServiceBrowserPrivate::browserFinished() 0202 { 0203 m_timer.stop(); 0204 m_browserFinished = true; 0205 queryFinished(); 0206 } 0207 0208 void ServiceBrowserPrivate::queryFinished() 0209 { 0210 if (!m_duringResolve.count() && m_browserFinished) { 0211 Q_EMIT m_parent->finished(); 0212 } 0213 } 0214 0215 QList<RemoteService::Ptr> ServiceBrowser::services() const 0216 { 0217 Q_D(const ServiceBrowser); 0218 return d->m_services; 0219 } 0220 0221 void ServiceBrowser::virtual_hook(int, void *) 0222 { 0223 } 0224 0225 QHostAddress ServiceBrowser::resolveHostName(const QString &hostname) 0226 { 0227 org::freedesktop::Avahi::Server s(QStringLiteral("org.freedesktop.Avahi"), QStringLiteral("/"), QDBusConnection::systemBus()); 0228 0229 int protocol = 0; 0230 QString name; 0231 int aprotocol = 0; 0232 QString address; 0233 uint flags = 0; 0234 0235 QDBusReply<int> reply = s.ResolveHostName(-1, -1, hostname, 0, (unsigned int)0, protocol, name, aprotocol, address, flags); 0236 0237 if (reply.isValid()) { 0238 return QHostAddress(address); 0239 } else { 0240 return QHostAddress(); 0241 } 0242 } 0243 0244 QString ServiceBrowser::getLocalHostName() 0245 { 0246 org::freedesktop::Avahi::Server s(QStringLiteral("org.freedesktop.Avahi"), QStringLiteral("/"), QDBusConnection::systemBus()); 0247 0248 QDBusReply<QString> reply = s.GetHostName(); 0249 0250 if (reply.isValid()) { 0251 return reply.value(); 0252 } else { 0253 return QString(); 0254 } 0255 } 0256 0257 } 0258 0259 #include "moc_avahi-servicebrowser_p.cpp" 0260 #include "moc_servicebrowser.cpp"