From cde21a477055cbe304e662d8c55b8b15df20dd8a Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 22 Oct 2017 22:28:03 +0200 Subject: [PATCH] added zeroconf discovery --- .../discovery/avahi/avahiserviceentry.cpp | 180 ++++++++++++++ .../discovery/avahi/avahiserviceentry.h | 75 ++++++ guh-control/discovery/avahi/qt-watch.cpp | 193 ++++++++++++++ guh-control/discovery/avahi/qt-watch.h | 35 +++ guh-control/discovery/avahi/qtavahiclient.cpp | 121 +++++++++ guh-control/discovery/avahi/qtavahiclient.h | 73 ++++++ .../discovery/avahi/qtavahiservice.cpp | 235 ++++++++++++++++++ guh-control/discovery/avahi/qtavahiservice.h | 80 ++++++ .../discovery/avahi/qtavahiservice_p.cpp | 81 ++++++ .../discovery/avahi/qtavahiservice_p.h | 56 +++++ .../discovery/avahi/qtavahiservicebrowser.cpp | 102 ++++++++ .../discovery/avahi/qtavahiservicebrowser.h | 63 +++++ .../avahi/qtavahiservicebrowser_p.cpp | 218 ++++++++++++++++ .../discovery/avahi/qtavahiservicebrowser_p.h | 82 ++++++ guh-control/discovery/discoverydevice.cpp | 176 +++++++++++++ .../{upnpdevice.h => discoverydevice.h} | 12 +- ...pdiscoverymodel.cpp => discoverymodel.cpp} | 41 ++- ...{upnpdiscoverymodel.h => discoverymodel.h} | 21 +- guh-control/discovery/upnpdevice.cpp | 176 ------------- guh-control/discovery/upnpdiscovery.cpp | 79 +++--- guh-control/discovery/upnpdiscovery.h | 16 +- guh-control/discovery/zeroconfdiscovery.cpp | 70 ++++++ guh-control/discovery/zeroconfdiscovery.h | 45 ++++ guh-control/main.cpp | 2 + guh-control/tcpsocketinterface.cpp | 2 +- guh-control/ui/ConnectPage.qml | 26 +- guh-control/ui/main.qml | 6 +- 27 files changed, 2004 insertions(+), 262 deletions(-) create mode 100644 guh-control/discovery/avahi/avahiserviceentry.cpp create mode 100644 guh-control/discovery/avahi/avahiserviceentry.h create mode 100644 guh-control/discovery/avahi/qt-watch.cpp create mode 100644 guh-control/discovery/avahi/qt-watch.h create mode 100644 guh-control/discovery/avahi/qtavahiclient.cpp create mode 100644 guh-control/discovery/avahi/qtavahiclient.h create mode 100644 guh-control/discovery/avahi/qtavahiservice.cpp create mode 100644 guh-control/discovery/avahi/qtavahiservice.h create mode 100644 guh-control/discovery/avahi/qtavahiservice_p.cpp create mode 100644 guh-control/discovery/avahi/qtavahiservice_p.h create mode 100644 guh-control/discovery/avahi/qtavahiservicebrowser.cpp create mode 100644 guh-control/discovery/avahi/qtavahiservicebrowser.h create mode 100644 guh-control/discovery/avahi/qtavahiservicebrowser_p.cpp create mode 100644 guh-control/discovery/avahi/qtavahiservicebrowser_p.h create mode 100644 guh-control/discovery/discoverydevice.cpp rename guh-control/discovery/{upnpdevice.h => discoverydevice.h} (93%) rename guh-control/discovery/{upnpdiscoverymodel.cpp => discoverymodel.cpp} (71%) rename guh-control/discovery/{upnpdiscoverymodel.h => discoverymodel.h} (84%) delete mode 100644 guh-control/discovery/upnpdevice.cpp create mode 100644 guh-control/discovery/zeroconfdiscovery.cpp create mode 100644 guh-control/discovery/zeroconfdiscovery.h diff --git a/guh-control/discovery/avahi/avahiserviceentry.cpp b/guh-control/discovery/avahi/avahiserviceentry.cpp new file mode 100644 index 00000000..3bd0bc71 --- /dev/null +++ b/guh-control/discovery/avahi/avahiserviceentry.cpp @@ -0,0 +1,180 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*! + \class AvahiServiceEntry + \brief Holds information about an avahi service entry. + + \ingroup types + \inmodule libguh + +*/ + +#include "avahiserviceentry.h" + +/*! Constructs an empty invalid \l{AvahiServiceEntry}*/ +AvahiServiceEntry::AvahiServiceEntry() : + m_port(0), + m_protocol(QAbstractSocket::UnknownNetworkLayerProtocol) +{ + +} + + +/*! Constructs a new \l{AvahiServiceEntry} with the given \a name, \a serviceType, \a hostAddress, \a domain, \a hostName, \a port, \a protocol, \a txt and \a flags.*/ +AvahiServiceEntry::AvahiServiceEntry(QString name, QString serviceType, QHostAddress hostAddress, QString domain, QString hostName, quint16 port, QAbstractSocket::NetworkLayerProtocol protocol, QStringList txt, AvahiLookupResultFlags flags) : + m_name(name), + m_serviceType(serviceType), + m_hostAddress(hostAddress), + m_domain(domain), + m_hostName(hostName), + m_port(port), + m_protocol(protocol), + m_txt(txt), + m_flags(flags) +{ + +} + +/*! Returns the name of this \l{AvahiServiceEntry}.*/ +QString AvahiServiceEntry::name() const +{ + return m_name; +} + +/*! Returns the service type of this \l{AvahiServiceEntry}.*/ +QString AvahiServiceEntry::serviceType() const +{ + return m_serviceType; +} + +/*! Returns the host address of this \l{AvahiServiceEntry}.*/ +QHostAddress AvahiServiceEntry::hostAddress() const +{ + return m_hostAddress; +} + +/*! Returns the domain of this \l{AvahiServiceEntry}.*/ +QString AvahiServiceEntry::domain() const +{ + return m_domain; +} + +/*! Returns the host name of this \l{AvahiServiceEntry}.*/ +QString AvahiServiceEntry::hostName() const +{ + return m_hostName; +} + +/*! Returns the port of this \l{AvahiServiceEntry}.*/ +quint16 AvahiServiceEntry::port() const +{ + return m_port; +} + +/*! Returns the network protocol of this \l{AvahiServiceEntry}.*/ +QAbstractSocket::NetworkLayerProtocol AvahiServiceEntry::protocol() const +{ + return m_protocol; +} + +/*! Returns the avahi flags of this \l{AvahiServiceEntry}.*/ +AvahiLookupResultFlags AvahiServiceEntry::flags() const +{ + return m_flags; +} + +/*! Returns the txt string list of this \l{AvahiServiceEntry}.*/ +QStringList AvahiServiceEntry::txt() const +{ + return m_txt; +} + +/*! Returns true if this \l{AvahiServiceEntry} is valid.*/ +bool AvahiServiceEntry::isValid() const +{ + return !m_hostAddress.isNull() && !m_hostName.isEmpty() && m_port != 0 && m_protocol != QAbstractSocket::UnknownNetworkLayerProtocol; +} + +/*! Returns true if this \l{AvahiServiceEntry} is cached.*/ +bool AvahiServiceEntry::isChached() const +{ + return m_flags & AVAHI_LOOKUP_RESULT_CACHED; +} + +/*! Returns true if this \l{AvahiServiceEntry} was found in the wide area.*/ +bool AvahiServiceEntry::isWideArea() const +{ + return m_flags & AVAHI_LOOKUP_RESULT_WIDE_AREA; +} + +/*! Returns true if this \l{AvahiServiceEntry} is a multicast service.*/ +bool AvahiServiceEntry::isMulticast() const +{ + return m_flags & AVAHI_LOOKUP_RESULT_MULTICAST; +} + +/*! Returns true if this \l{AvahiServiceEntry} was found local.*/ +bool AvahiServiceEntry::isLocal() const +{ + return m_flags & AVAHI_LOOKUP_RESULT_LOCAL; +} + +/*! Returns true if this \l{AvahiServiceEntry} is our own service.*/ +bool AvahiServiceEntry::isOurOwn() const +{ + return m_flags & AVAHI_LOOKUP_RESULT_OUR_OWN; +} + +/*! Returns true if this \l{AvahiServiceEntry} is equal to \a other; otherwise returns false.*/ +bool AvahiServiceEntry::operator ==(const AvahiServiceEntry &other) const +{ + return other.name() == m_name && + other.serviceType() == m_serviceType && + other.hostAddress() == m_hostAddress && + other.domain() == m_domain && + other.hostName() == m_hostName && + other.port() == m_port && + other.protocol() == m_protocol && + other.flags() == m_flags && + other.txt() == m_txt; +} + +/*! Returns true if this \l{AvahiServiceEntry} is not equal to \a other; otherwise returns false.*/ +bool AvahiServiceEntry::operator !=(const AvahiServiceEntry &other) const +{ + return !operator==(other); +} + +/*! Writes the given \a entry to the specified \a dbg.*/ +QDebug operator <<(QDebug dbg, const AvahiServiceEntry &entry) +{ + dbg.nospace() << "AvahiServiceEntry("; + dbg << entry.name() << ")" << endl; + dbg << " location: " << entry.hostAddress().toString() << ":" << entry.port() << endl; + dbg << " hostname: " << entry.hostName() << endl; + dbg << " domain: " << entry.domain() << endl; + dbg << "service type: " << entry.serviceType() << endl; + dbg << " protocol: " << entry.protocol() << endl; + dbg << " txt: " << entry.txt() << endl; + return dbg; +} diff --git a/guh-control/discovery/avahi/avahiserviceentry.h b/guh-control/discovery/avahi/avahiserviceentry.h new file mode 100644 index 00000000..8c691162 --- /dev/null +++ b/guh-control/discovery/avahi/avahiserviceentry.h @@ -0,0 +1,75 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef AVAHISERVICEENTRY_H +#define AVAHISERVICEENTRY_H + +#include +#include +#include +#include +#include + +class AvahiServiceEntry +{ +public: + AvahiServiceEntry(); + AvahiServiceEntry(QString name, QString serviceType, QHostAddress hostAddress, QString domain, QString hostName, quint16 port, QAbstractSocket::NetworkLayerProtocol protocol, QStringList txt, AvahiLookupResultFlags flags); + + QString name() const; + QString serviceType() const; + QHostAddress hostAddress() const; + QString domain() const; + QString hostName() const; + quint16 port() const; + QAbstractSocket::NetworkLayerProtocol protocol() const; + AvahiLookupResultFlags flags() const; + QStringList txt() const; + + bool isValid() const; + + bool isChached() const; + bool isWideArea() const; + bool isMulticast() const; + bool isLocal() const; + bool isOurOwn() const; + + bool operator ==(const AvahiServiceEntry &other) const; + bool operator !=(const AvahiServiceEntry &other) const; + +private: + QString m_name; + QString m_serviceType; + QHostAddress m_hostAddress; + QString m_domain; + QString m_hostName; + quint16 m_port; + QAbstractSocket::NetworkLayerProtocol m_protocol; + QStringList m_txt; + AvahiLookupResultFlags m_flags; + +}; + +QDebug operator <<(QDebug dbg, const AvahiServiceEntry &entry); +Q_DECLARE_METATYPE(AvahiServiceEntry) + +#endif // AVAHISERVICEENTRY_H diff --git a/guh-control/discovery/avahi/qt-watch.cpp b/guh-control/discovery/avahi/qt-watch.cpp new file mode 100644 index 00000000..6bca5789 --- /dev/null +++ b/guh-control/discovery/avahi/qt-watch.cpp @@ -0,0 +1,193 @@ +/*** + This file is part of avahi. + + avahi is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + avahi is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General + Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. + + Modified: (C) 2016 Simon Stürz + +***/ + +#include +#include +#include + +#include +#include + +#include "qt-watch.h" + +class AvahiWatch : public QObject +{ + Q_OBJECT +public: + AvahiWatch(int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void* userdata); + ~AvahiWatch() { } + AvahiWatchEvent getEvents() const { return m_incallback ? m_lastEvent : (AvahiWatchEvent)0; } + void setWatchedEvents(AvahiWatchEvent event); + +private slots: + void gotIn(); + void gotOut(); + +private: + QSocketNotifier* m_in; + QSocketNotifier* m_out; + AvahiWatchCallback m_callback; + AvahiWatchEvent m_lastEvent; + int m_fd; + void* m_userdata; + bool m_incallback; +}; + +class AvahiTimeout : public QObject +{ + Q_OBJECT + +public: + AvahiTimeout(const struct timeval* tv, AvahiTimeoutCallback callback, void* userdata); + ~AvahiTimeout() {} + void update(const struct timeval* tv); + +private slots: + void timeout(); + +private: + QTimer m_timer; + AvahiTimeoutCallback m_callback; + void* m_userdata; +}; + + + +AvahiWatch::AvahiWatch(int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void* userdata) : + m_in(0), + m_out(0), + m_callback(callback), + m_fd(fd), + m_userdata(userdata), + m_incallback(false) +{ + setWatchedEvents(event); +} + +void AvahiWatch::gotIn() +{ + m_lastEvent = AVAHI_WATCH_IN; + m_incallback = true; + m_callback(this, m_fd, m_lastEvent, m_userdata); + m_incallback = false; +} + +void AvahiWatch::gotOut() +{ + m_lastEvent = AVAHI_WATCH_OUT; + m_incallback = true; + m_callback(this, m_fd, m_lastEvent, m_userdata); + m_incallback = false; +} + +void AvahiWatch::setWatchedEvents(AvahiWatchEvent event) +{ + if (!(event & AVAHI_WATCH_IN)) { delete m_in; m_in = 0; } + if (!(event & AVAHI_WATCH_OUT)) { delete m_out; m_out = 0; } + if (event & AVAHI_WATCH_IN) { + m_in = new QSocketNotifier(m_fd,QSocketNotifier::Read, this); + connect(m_in, SIGNAL(activated(int)), SLOT(gotIn())); + } + if (event & AVAHI_WATCH_OUT) { + m_out = new QSocketNotifier(m_fd, QSocketNotifier::Write, this); + connect(m_out, SIGNAL(activated(int)), SLOT(gotOut())); + } +} + +AvahiTimeout::AvahiTimeout(const struct timeval* tv, AvahiTimeoutCallback callback, void *userdata) : + m_callback(callback), + m_userdata(userdata) +{ + connect(&m_timer, SIGNAL(timeout()), this, SLOT(timeout())); + m_timer.setSingleShot(true); + update(tv); +} + +void AvahiTimeout::update(const struct timeval *tv) +{ + m_timer.stop(); + if (tv) { + AvahiUsec u = avahi_age(tv)/1000; + m_timer.start( (u>0) ? 0 : -u); + } +} + +void AvahiTimeout::timeout() +{ + m_callback(this, m_userdata); +} + +static AvahiWatch* q_watch_new(const AvahiPoll *api, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void *userdata) +{ + Q_UNUSED(api) + return new AvahiWatch(fd, event, callback, userdata); +} + +static void q_watch_update(AvahiWatch *w, AvahiWatchEvent events) +{ + w->setWatchedEvents(events); +} + +static AvahiWatchEvent q_watch_get_events(AvahiWatch *w) +{ + return w->getEvents(); +} + +static void q_watch_free(AvahiWatch *w) +{ + delete w; +} + +static AvahiTimeout* q_timeout_new(const AvahiPoll *api, const struct timeval *tv, AvahiTimeoutCallback callback, void *userdata) +{ + Q_UNUSED(api) + return new AvahiTimeout(tv, callback, userdata); +} + +static void q_timeout_update(AvahiTimeout *t, const struct timeval *tv) +{ + t->update(tv); +} + +static void q_timeout_free(AvahiTimeout *t) +{ + delete t; +} + +const AvahiPoll* avahi_qt_poll_get(void) +{ + static const AvahiPoll qt_poll = { + NULL, + q_watch_new, + q_watch_update, + q_watch_get_events, + q_watch_free, + q_timeout_new, + q_timeout_update, + q_timeout_free + }; + + return &qt_poll; +} + +#include "qt-watch.moc" + diff --git a/guh-control/discovery/avahi/qt-watch.h b/guh-control/discovery/avahi/qt-watch.h new file mode 100644 index 00000000..ea5c1fc5 --- /dev/null +++ b/guh-control/discovery/avahi/qt-watch.h @@ -0,0 +1,35 @@ +#ifndef QAVAHI_H +#define QAVAHI_H + +/*** + This file is part of avahi. + + avahi is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + avahi is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General + Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. + + Modified: (C) 2016 Simon Stürz + +***/ + +/** \file qt-watch.h Qt main loop adapter */ + +#include + +AVAHI_C_DECL_BEGIN + +/** Setup abstract poll structure for integration with Qt main loop */ +const AvahiPoll* avahi_qt_poll_get(void); + +AVAHI_C_DECL_END + +#endif // QAVAHI_H diff --git a/guh-control/discovery/avahi/qtavahiclient.cpp b/guh-control/discovery/avahi/qtavahiclient.cpp new file mode 100644 index 00000000..8a7dfde2 --- /dev/null +++ b/guh-control/discovery/avahi/qtavahiclient.cpp @@ -0,0 +1,121 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "qt-watch.h" +#include "qtavahiclient.h" + +#include + +QtAvahiClient::QtAvahiClient(QObject *parent) : + QObject(parent), + poll(avahi_qt_poll_get()), + client(0), + error(0), + m_state(QtAvahiClientStateNone) +{ + connect(this, &QtAvahiClient::clientStateChangedInternal, this, &QtAvahiClient::onClientStateChanged); +} + +QtAvahiClient::~QtAvahiClient() +{ + if (client) + avahi_client_free(client); + +} + +QtAvahiClient::QtAvahiClientState QtAvahiClient::state() const +{ + return m_state; +} + +void QtAvahiClient::start() +{ + if (client) + return; + + avahi_client_new(poll, (AvahiClientFlags) 0, QtAvahiClient::callback, this, &error); +} + +QString QtAvahiClient::errorString() const +{ + return QString(avahi_strerror(error)); +} + +void QtAvahiClient::callback(AvahiClient *client, AvahiClientState state, void *userdata) +{ + QtAvahiClient *serviceClient = static_cast(userdata); + if (!serviceClient) + return; + + serviceClient->client = client; + + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + emit serviceClient->clientStateChangedInternal(QtAvahiClientStateRunning); + break; + case AVAHI_CLIENT_FAILURE: + emit serviceClient->clientStateChangedInternal(QtAvahiClientStateFailure); + break; + case AVAHI_CLIENT_S_COLLISION: + emit serviceClient->clientStateChangedInternal(QtAvahiClientStateCollision); + break; + case AVAHI_CLIENT_S_REGISTERING: + emit serviceClient->clientStateChangedInternal(QtAvahiClientStateRegistering); + break; + case AVAHI_CLIENT_CONNECTING: + emit serviceClient->clientStateChangedInternal(QtAvahiClientStateConnecting); + break; + } +} + +void QtAvahiClient::onClientStateChanged(const QtAvahiClient::QtAvahiClientState &state) +{ + if (m_state == state) + return; + + m_state = state; + +// switch (m_state) { +// case QtAvahiClientStateNone: +// break; +// case QtAvahiClientStateRunning: +// qCDebug(dcAvahi()) << "Client running."; +// break; +// case QtAvahiClientStateFailure: +// qCWarning(dcAvahi()) << "Client failure:" << errorString(); +// break; +// case QtAvahiClientStateCollision: +// qCWarning(dcAvahi()) << "Client collision:" << errorString(); +// break; +// case QtAvahiClientStateRegistering: +// qCDebug(dcAvahi()) << "Client registering..."; +// break; +// case QtAvahiClientStateConnecting: +// qCDebug(dcAvahi()) << "Client connecting..."; +// break; +// default: +// break; +// } + + emit clientStateChanged(m_state); +} + diff --git a/guh-control/discovery/avahi/qtavahiclient.h b/guh-control/discovery/avahi/qtavahiclient.h new file mode 100644 index 00000000..b81999a8 --- /dev/null +++ b/guh-control/discovery/avahi/qtavahiclient.h @@ -0,0 +1,73 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef QTAVAHICLIENT_H +#define QTAVAHICLIENT_H + +#include +#include + +class QtAvahiClient : public QObject +{ + Q_OBJECT + Q_ENUMS(QtAvahiClientState) + +public: + enum QtAvahiClientState { + QtAvahiClientStateNone, + QtAvahiClientStateRunning, + QtAvahiClientStateFailure, + QtAvahiClientStateCollision, + QtAvahiClientStateRegistering, + QtAvahiClientStateConnecting + }; + + explicit QtAvahiClient(QObject *parent = 0); + ~QtAvahiClient(); + + QtAvahiClientState state() const; + +private: + friend class QtAvahiService; + friend class QtAvahiServiceBrowser; + friend class QtAvahiServiceBrowserPrivate; + + const AvahiPoll *poll; + AvahiClient *client; + int error; + QtAvahiClientState m_state; + + void start(); + QString errorString() const; + + static void callback(AvahiClient *client, AvahiClientState state, void *userdata); + +private slots: + void onClientStateChanged(const QtAvahiClientState &state); + +signals: + void clientStateChanged(const QtAvahiClientState &state); + void clientStateChangedInternal(const QtAvahiClientState &state); + +}; + +#endif // QTAVAHICLIENT_H diff --git a/guh-control/discovery/avahi/qtavahiservice.cpp b/guh-control/discovery/avahi/qtavahiservice.cpp new file mode 100644 index 00000000..bbcc1d2c --- /dev/null +++ b/guh-control/discovery/avahi/qtavahiservice.cpp @@ -0,0 +1,235 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*! + \class QtAvahiService + \brief Allows to publish an avahi service to the network. + + \inmodule libguh +*/ + +/*! \enum QtAvahiService::QtAvahiServiceState + + This enum type specifies the state of a \l{QtAvahiService}. + + \value QtAvahiServiceStateUncomitted + The group has not yet been commited, the user must still call avahi_entry_group_commit(). + \value QtAvahiServiceStateRegistering + The entries of the group are currently being registered. + \value QtAvahiServiceStateEstablished + The entries have successfully been established. + \value QtAvahiServiceStateCollision + A name collision for one of the entries in the group has been detected, the entries have been withdrawn. + \value QtAvahiServiceStateFailure + Some kind of failure happened, the entries have been withdrawn. + +*/ + +/*! \fn void QtAvahiService::serviceStateChanged(const QtAvahiServiceState &state); + This signal will be emitted when the \a state of this \l{QtAvahiService} has changed. +*/ + +#include "qtavahiservice.h" +#include "qtavahiservice_p.h" +#include + + +/*! Constructs a new \l{QtAvahiService} with the given \a parent. */ +QtAvahiService::QtAvahiService(QObject *parent) : + QObject(parent), + d_ptr(new QtAvahiServicePrivate), + m_state(QtAvahiServiceStateUncomitted) +{ + connect(this, &QtAvahiService::serviceStateChanged, this, &QtAvahiService::onStateChanged); + + d_ptr->client = new QtAvahiClient(this); + d_ptr->client->start(); +} + +/*! Destructs this \l{QtAvahiService}. */ +QtAvahiService::~QtAvahiService() +{ + if (d_ptr->group) + avahi_entry_group_free(d_ptr->group); + + delete d_ptr; +} + +/*! Returns the port of this \l{QtAvahiService}. */ +quint16 QtAvahiService::port() const +{ + return d_ptr->port; +} + +/*! Returns the name of this \l{QtAvahiService}. */ +QString QtAvahiService::name() const +{ + return d_ptr->name; +} + +/*! Returns the service type of this \l{QtAvahiService}. */ +QString QtAvahiService::serviceType() const +{ + return d_ptr->type; +} + +QHash QtAvahiService::txtRecords() const +{ + return d_ptr->txtRecords; +} + +QtAvahiService::QtAvahiServiceState QtAvahiService::state() const +{ + return m_state; +} + +/*! Register a new \l{QtAvahiService} with the given \a name and \a port. The service type can be specified with the \a serviceType string. The \a txtRecords records inform about additional information. Returns true if the service could be registered. */ +bool QtAvahiService::registerService(const QString &name, const quint16 &port, const QString &serviceType, const QHash &txtRecords) +{ + // Check if the client is running + if (!d_ptr->client->client || AVAHI_CLIENT_S_RUNNING != avahi_client_get_state(d_ptr->client->client)) { + qWarning() << "Could not register service" << name << port << serviceType << ". The client is not available."; + return false; + } + + d_ptr->name = name; + d_ptr->port = port; + d_ptr->type = serviceType; + d_ptr->txtRecords = txtRecords; + + // If the group is not set yet, create it + if (!d_ptr->group) + d_ptr->group = avahi_entry_group_new(d_ptr->client->client, QtAvahiServicePrivate::callback, this); + + // If the group is empty + if (avahi_entry_group_is_empty(d_ptr->group)) { + // Add the service + d_ptr->error = avahi_entry_group_add_service_strlst(d_ptr->group, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + (AvahiPublishFlags) 0, + d_ptr->name.toLatin1().data(), + d_ptr->type.toLatin1().data(), + 0, + 0, + (uint16_t)d_ptr->port, + QtAvahiServicePrivate::createTxtList(txtRecords)); + + // Verify if the group has to be comitted + if (d_ptr->error) { + + if (d_ptr->error == AVAHI_ERR_COLLISION) { + if (!handlCollision()) { + qWarning() << this << "error:" << avahi_strerror(d_ptr->error); + return false; + } + + + } else { + qWarning() << this << "error:" << avahi_strerror(d_ptr->error); + return false; + } + } + + // Commit the service + d_ptr->error = avahi_entry_group_commit(d_ptr->group); + if (d_ptr->error) { + qWarning() << this << "error:" << avahi_strerror(d_ptr->error); + return false; + } + } else { + qWarning() << "Service already registered. Please reset the service before reusing it."; + return false; + } + + return true; +} + +/*! Remove this service from the local network. This \l{QtAvahiService} can be reused to register a new avahi service. */ +void QtAvahiService::resetService() +{ + if (!d_ptr->group) + return; + + avahi_entry_group_reset(d_ptr->group); +} + +/*! Returns true if the service group was added and commited to the network without errors. */ +bool QtAvahiService::isValid() const +{ + return (d_ptr->group && !d_ptr->error); +} + +/*! Returns the error string of this \l{QtAvahiService}. */ +QString QtAvahiService::errorString() const +{ + if (!d_ptr->client->client) + return "Invalid client."; + + return avahi_strerror(avahi_client_errno(d_ptr->client->client)); +} + +bool QtAvahiService::handlCollision() +{ + QString alternativeServiceName = avahi_alternative_service_name(name().toStdString().data()); + qDebug() << "Service name colision. Picking alternative service name" << alternativeServiceName; + + resetService(); + return registerService(alternativeServiceName, port(), serviceType(), txtRecords()); +} + +void QtAvahiService::onStateChanged(const QtAvahiServiceState &state) +{ + if (m_state == state) + return; + + m_state = state; + + switch (m_state) { + case QtAvahiServiceStateUncomitted: + qDebug() << this << "state changed: uncomitted"; + break; + case QtAvahiServiceStateRegistering: + qDebug() << this << "state changed: registering..."; + break; + case QtAvahiServiceStateEstablished: + qDebug() << this << "state changed: established"; + break; + case QtAvahiServiceStateCollision: + qDebug() << this << "state changed: collision"; + handlCollision(); + break; + case QtAvahiServiceStateFailure: + qWarning() << this << "failure: " << errorString(); + break; + default: + break; + } + +} + +QDebug operator <<(QDebug dbg, QtAvahiService *service) +{ + dbg.nospace() << "AvahiService("; + dbg << service->name() << ", " << service->serviceType() << ", " << service->port() << ") "; + return dbg; +} diff --git a/guh-control/discovery/avahi/qtavahiservice.h b/guh-control/discovery/avahi/qtavahiservice.h new file mode 100644 index 00000000..116e0fa7 --- /dev/null +++ b/guh-control/discovery/avahi/qtavahiservice.h @@ -0,0 +1,80 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef QTAVAHISERVICE_H +#define QTAVAHISERVICE_H + +#include +#include +#include + +class QtAvahiServicePrivate; + +class QtAvahiService : public QObject +{ + Q_OBJECT + Q_ENUMS(QtAvahiServiceState) + +public: + enum QtAvahiServiceState { + QtAvahiServiceStateUncomitted = 0, + QtAvahiServiceStateRegistering = 1, + QtAvahiServiceStateEstablished = 2, + QtAvahiServiceStateCollision = 3, + QtAvahiServiceStateFailure = 4 + }; + + explicit QtAvahiService(QObject *parent = 0); + ~QtAvahiService(); + + quint16 port() const; + QString name() const; + QString serviceType() const; + QHash txtRecords() const; + QtAvahiServiceState state() const; + + bool registerService(const QString &name, const quint16 &port, const QString &serviceType = "_http._tcp", const QHash &txtRecords = QHash()); + void resetService(); + + bool isValid() const; + QString errorString() const; + +signals: + void serviceStateChanged(const QtAvahiServiceState &state); + +protected: + QtAvahiServicePrivate *d_ptr; + +private slots: + bool handlCollision(); + void onStateChanged(const QtAvahiServiceState &state); + +private: + QtAvahiServiceState m_state; + Q_DECLARE_PRIVATE(QtAvahiService) + +}; + +QDebug operator <<(QDebug dbg, QtAvahiService *service); + + +#endif // QTAVAHISERVICE_H diff --git a/guh-control/discovery/avahi/qtavahiservice_p.cpp b/guh-control/discovery/avahi/qtavahiservice_p.cpp new file mode 100644 index 00000000..caadcb32 --- /dev/null +++ b/guh-control/discovery/avahi/qtavahiservice_p.cpp @@ -0,0 +1,81 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "qtavahiservice_p.h" + +#include +#include +#include + +QtAvahiServicePrivate::QtAvahiServicePrivate() : + client(0), + group(0), + error(0) +{ + +} + +void QtAvahiServicePrivate::callback(AvahiEntryGroup *group, AvahiEntryGroupState state, void *userdata) +{ + Q_UNUSED(group); + + QtAvahiService *service = static_cast(userdata); + if (!service) + return; + + if (service->state() == (QtAvahiService::QtAvahiServiceState)state) + return; + + switch (state) { + case AVAHI_ENTRY_GROUP_UNCOMMITED: + emit service->serviceStateChanged(QtAvahiService::QtAvahiServiceStateUncomitted); + break; + case AVAHI_ENTRY_GROUP_REGISTERING: + emit service->serviceStateChanged(QtAvahiService::QtAvahiServiceStateRegistering); + break; + case AVAHI_ENTRY_GROUP_ESTABLISHED: + emit service->serviceStateChanged(QtAvahiService::QtAvahiServiceStateEstablished); + break; + case AVAHI_ENTRY_GROUP_COLLISION: + emit service->serviceStateChanged(QtAvahiService::QtAvahiServiceStateCollision); + break; + case AVAHI_ENTRY_GROUP_FAILURE: + emit service->serviceStateChanged(QtAvahiService::QtAvahiServiceStateFailure); + break; + } +} + +AvahiStringList *QtAvahiServicePrivate::createTxtList(const QHash &txt) +{ + AvahiStringList *list = NULL; + if (txt.isEmpty()) + return list; + + const QStringList keys = txt.keys(); + list = avahi_string_list_new((keys.first() + '=' + txt[keys.first()]).toLatin1().constData(), nullptr); + for (const QString &key : keys.mid(1)) { + list = avahi_string_list_add_pair(list, key.toLatin1().constData(), txt[key].toLatin1().constData()); + } + + return list; +} + diff --git a/guh-control/discovery/avahi/qtavahiservice_p.h b/guh-control/discovery/avahi/qtavahiservice_p.h new file mode 100644 index 00000000..2b395dea --- /dev/null +++ b/guh-control/discovery/avahi/qtavahiservice_p.h @@ -0,0 +1,56 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef QTAVAHISERVICEPRIVATE_P +#define QTAVAHISERVICEPRIVATE_P + +#include +#include + +#include "qtavahiservice.h" +#include "qtavahiclient.h" + +#include +#include +#include + +class QtAvahiServicePrivate +{ +public: + QtAvahiServicePrivate(); + + static void callback(AvahiEntryGroup *group, AvahiEntryGroupState state, void *userdata); + + QtAvahiClient *client; + AvahiEntryGroup *group; + QString name; + quint16 port; + QString type; + QHash txtRecords; + int error; + + static AvahiStringList *createTxtList(const QHash &txt); + +}; + +#endif // QTAVAHISERVICEPRIVATE_P + diff --git a/guh-control/discovery/avahi/qtavahiservicebrowser.cpp b/guh-control/discovery/avahi/qtavahiservicebrowser.cpp new file mode 100644 index 00000000..b8f3dd27 --- /dev/null +++ b/guh-control/discovery/avahi/qtavahiservicebrowser.cpp @@ -0,0 +1,102 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*! + \class QtAvahiServiceBrowser + \brief Allows to browse avahi services in the local network. + + \ingroup hardware + \inmodule libguh +*/ + +/*! \fn void QtAvahiServiceBrowser::serviceEntryAdded(const AvahiServiceEntry &entry); + This signal will be emitted when a new \a entry was added to the current entry list. +*/ + +/*! \fn void QtAvahiServiceBrowser::serviceEntryRemoved(const AvahiServiceEntry &entry); + This signal will be emitted when a new \a entry was removed from the current entry list. +*/ + +#include "qtavahiservicebrowser.h" +#include "qtavahiservicebrowser_p.h" + +#include + +/*! Constructs a new \l{QtAvahiServiceBrowser} with the given \a parent. */ +QtAvahiServiceBrowser::QtAvahiServiceBrowser(QObject *parent) : + QObject(parent), + d_ptr(new QtAvahiServiceBrowserPrivate(new QtAvahiClient)) +{ + connect(d_ptr->client, &QtAvahiClient::clientStateChanged, this, &QtAvahiServiceBrowser::onClientStateChanged); +} + +/*! Destructs this \l{QtAvahiServiceBrowser}. */ +QtAvahiServiceBrowser::~QtAvahiServiceBrowser() +{ + // Delete each service browser + foreach (const QString &serviceType, d_ptr->serviceBrowserTable.keys()) { + AvahiServiceBrowser *browser = d_ptr->serviceBrowserTable.take(serviceType); + if (browser) { + avahi_service_browser_free(browser); + } + } + + // Delete the service type browser + if (d_ptr->serviceTypeBrowser) + avahi_service_type_browser_free(d_ptr->serviceTypeBrowser); + + delete d_ptr; +} + +/*! Enables this \l{QtAvahiServiceBrowser} and starts the service browsing. */ +void QtAvahiServiceBrowser::enable() +{ + d_ptr->client->start(); +} + +/*! Returns the current \l{AvahiServiceEntry} list of this \l{QtAvahiServiceBrowser}. */ +QList QtAvahiServiceBrowser::serviceEntries() const +{ + return m_serviceEntries; +} + +void QtAvahiServiceBrowser::onClientStateChanged(const QtAvahiClient::QtAvahiClientState &state) +{ + if (state == QtAvahiClient::QtAvahiClientStateRunning) { + qDebug() << "Service browser client connected."; + // Return if we already have a service type browser + if (d_ptr->serviceTypeBrowser) + return; + + d_ptr->serviceTypeBrowser = avahi_service_type_browser_new(d_ptr->client->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, (AvahiLookupFlags) 0, QtAvahiServiceBrowserPrivate::callbackServiceTypeBrowser, this); + } else if (state == QtAvahiClient::QtAvahiClientStateFailure) { + qWarning() << "Service browser client failure:" << d_ptr->client->errorString(); + } +} + +void QtAvahiServiceBrowser::createServiceBrowser(const char *serviceType) +{ + // create a new service browser for the given serviceType + AvahiServiceBrowser *browser = avahi_service_browser_new(d_ptr->client->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, serviceType, NULL, (AvahiLookupFlags) 0, QtAvahiServiceBrowserPrivate::callbackServiceBrowser, this); + d_ptr->serviceBrowserTable.insert(serviceType, browser); +} + diff --git a/guh-control/discovery/avahi/qtavahiservicebrowser.h b/guh-control/discovery/avahi/qtavahiservicebrowser.h new file mode 100644 index 00000000..92ffd4f3 --- /dev/null +++ b/guh-control/discovery/avahi/qtavahiservicebrowser.h @@ -0,0 +1,63 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef QTAVAHISERVICEBROWSER_H +#define QTAVAHISERVICEBROWSER_H + +#include +#include + +#include "qtavahiclient.h" +#include "avahiserviceentry.h" + +class QtAvahiServiceBrowserPrivate; + +class QtAvahiServiceBrowser : public QObject +{ + Q_OBJECT +public: + explicit QtAvahiServiceBrowser(QObject *parent = 0); + ~QtAvahiServiceBrowser(); + + void enable(); + + QList serviceEntries() const; + +signals: + void serviceEntryAdded(const AvahiServiceEntry &entry); + void serviceEntryRemoved(const AvahiServiceEntry &entry); + +private slots: + void onClientStateChanged(const QtAvahiClient::QtAvahiClientState &state); + +private: + QtAvahiServiceBrowserPrivate *d_ptr; + + QList m_serviceEntries; + QStringList m_serviceTypes; + + void createServiceBrowser(const char* serviceType); + + Q_DECLARE_PRIVATE(QtAvahiServiceBrowser) +}; + +#endif // QTAVAHISERVICEBROWSER_H diff --git a/guh-control/discovery/avahi/qtavahiservicebrowser_p.cpp b/guh-control/discovery/avahi/qtavahiservicebrowser_p.cpp new file mode 100644 index 00000000..313b2572 --- /dev/null +++ b/guh-control/discovery/avahi/qtavahiservicebrowser_p.cpp @@ -0,0 +1,218 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "qtavahiservicebrowser_p.h" +#include "qtavahiservicebrowser.h" +#include "avahiserviceentry.h" + +#include +#include +#include + +QtAvahiServiceBrowserPrivate::QtAvahiServiceBrowserPrivate(QtAvahiClient *client) : + client(client), + serviceTypeBrowser(NULL) +{ + +} + +QtAvahiServiceBrowserPrivate::~QtAvahiServiceBrowserPrivate() +{ + foreach (AvahiServiceResolver *resolver, m_serviceResolvers) { + avahi_service_resolver_free(resolver); + } +} + +void QtAvahiServiceBrowserPrivate::callbackServiceTypeBrowser(AvahiServiceTypeBrowser *browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *type, const char *domain, AvahiLookupResultFlags flags, void *userdata) +{ + Q_UNUSED(browser) + Q_UNUSED(interface) + Q_UNUSED(protocol) + Q_UNUSED(domain) + Q_UNUSED(flags) + + QtAvahiServiceBrowser *serviceBrowser = static_cast(userdata); + if (!serviceBrowser) + return; + + switch (event) { + case AVAHI_BROWSER_NEW: + if (!serviceBrowser->m_serviceTypes.contains(type)) { + serviceBrowser->m_serviceTypes.append(type); + qDebug() << "[+] Service browser" << type; + serviceBrowser->createServiceBrowser(type); + } + break; + case AVAHI_BROWSER_REMOVE: + // Note: the browser for this serviceType will be deleted once all + // services from this type are removed + break; + case AVAHI_BROWSER_CACHE_EXHAUSTED: + break; + case AVAHI_BROWSER_ALL_FOR_NOW: + break; + case AVAHI_BROWSER_FAILURE: + qWarning() << "Service type browser error:" << QString(avahi_strerror(avahi_client_errno(serviceBrowser->d_ptr->client->client))); + break; + } +} + +void QtAvahiServiceBrowserPrivate::callbackServiceBrowser(AvahiServiceBrowser *browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void *userdata) +{ + Q_UNUSED(browser); + Q_UNUSED(flags); + + QtAvahiServiceBrowser *serviceBrowser = static_cast(userdata); + if (!serviceBrowser) + return; + + switch (event) { + case AVAHI_BROWSER_NEW: { + // Start resolving new service + AvahiServiceResolver *resolver = avahi_service_resolver_new(serviceBrowser->d_ptr->client->client, + interface, + protocol, + name, + type, + domain, + AVAHI_PROTO_UNSPEC, + (AvahiLookupFlags) 0, + QtAvahiServiceBrowserPrivate::callbackServiceResolver, + serviceBrowser); + if (resolver) { + serviceBrowser->d_ptr->m_serviceResolvers.append(resolver); + } else { + qWarning() << "Failed to resolve service" << QString(name) << ":" << avahi_strerror(avahi_client_errno(serviceBrowser->d_ptr->client->client)); + } + break; + } + case AVAHI_BROWSER_REMOVE: { + // Remove the service + foreach (const AvahiServiceEntry &entry, serviceBrowser->m_serviceEntries) { + // Check not only the name, but also the protocol + if (entry.name() == name && entry.protocol() == QtAvahiServiceBrowserPrivate::convertProtocol(protocol)) { + serviceBrowser->m_serviceEntries.removeAll(entry); + emit serviceBrowser->serviceEntryRemoved(entry); + } + } + + // Check if this was the last service for this serviceType + foreach (const AvahiServiceEntry &entry, serviceBrowser->m_serviceEntries) { + if (entry.serviceType() == type) + return; + } + + // This was the last service for this serviceType, lets delete the corresponding browser + AvahiServiceBrowser *browser = serviceBrowser->d_ptr->serviceBrowserTable.take(type); + if (browser) + avahi_service_browser_free(browser); + + serviceBrowser->m_serviceTypes.removeAll(type); + break; + } + case AVAHI_BROWSER_ALL_FOR_NOW: + break; + case AVAHI_BROWSER_CACHE_EXHAUSTED: + break; + case AVAHI_BROWSER_FAILURE: + qWarning() << "Service browser error:" << QString(avahi_strerror(avahi_client_errno(serviceBrowser->d_ptr->client->client))); + break; + } + +} + +void QtAvahiServiceBrowserPrivate::callbackServiceResolver(AvahiServiceResolver *resolver, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) +{ + Q_UNUSED(interface); + Q_UNUSED(type); + Q_UNUSED(txt); + + QPointer serviceBrowser = static_cast(userdata); + if (serviceBrowser.isNull()) + return; + + switch (event) { + case AVAHI_RESOLVER_FAILURE: + break; + case AVAHI_RESOLVER_FOUND: { + char a[AVAHI_ADDRESS_STR_MAX]; + avahi_address_snprint(a, sizeof(a), address); + + // convert protocol + QAbstractSocket::NetworkLayerProtocol networkProtocol = QtAvahiServiceBrowserPrivate::convertProtocol(protocol); + QStringList txtList = QtAvahiServiceBrowserPrivate::convertTxtList(txt); + + // create the new resolved service entry + AvahiServiceEntry entry = AvahiServiceEntry(name, + type, + QHostAddress(QString(a)), + QString(domain), + QString(host_name), + (quint16)port, + networkProtocol, + txtList, + flags); + + serviceBrowser->m_serviceEntries.append(entry); + emit serviceBrowser->serviceEntryAdded(entry); + } + } + serviceBrowser->d_ptr->m_serviceResolvers.removeAll(resolver); + avahi_service_resolver_free(resolver); + +} + +QStringList QtAvahiServiceBrowserPrivate::convertTxtList(AvahiStringList *txt) +{ + if (!txt) + return QStringList(); + + QStringList txtList; + txtList.append(QString(reinterpret_cast(txt->text))); + + while (txt->next) { + AvahiStringList *next = txt->next; + txtList.append(QString(reinterpret_cast(next->text))); + txt = next; + } + + return txtList; +} + +QAbstractSocket::NetworkLayerProtocol QtAvahiServiceBrowserPrivate::convertProtocol(const AvahiProtocol &protocol) +{ + QAbstractSocket::NetworkLayerProtocol networkProtocol = QAbstractSocket::UnknownNetworkLayerProtocol; + + switch (protocol) { + case AVAHI_PROTO_INET: + networkProtocol = QAbstractSocket::IPv4Protocol; + break; + case AVAHI_PROTO_INET6: + networkProtocol = QAbstractSocket::IPv6Protocol; + break; + case AVAHI_PROTO_UNSPEC: + networkProtocol = QAbstractSocket::UnknownNetworkLayerProtocol; + break; + } + return networkProtocol; +} + diff --git a/guh-control/discovery/avahi/qtavahiservicebrowser_p.h b/guh-control/discovery/avahi/qtavahiservicebrowser_p.h new file mode 100644 index 00000000..8cffe896 --- /dev/null +++ b/guh-control/discovery/avahi/qtavahiservicebrowser_p.h @@ -0,0 +1,82 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stürz * + * * + * This file is part of guh. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef QTAVAHISERVICEBROWSERPRIVATE_H +#define QTAVAHISERVICEBROWSERPRIVATE_H + +#include +#include +#include + +#include "qtavahiclient.h" +#include "qtavahiservice.h" + +class QtAvahiServiceBrowserPrivate +{ +public: + QtAvahiServiceBrowserPrivate(QtAvahiClient *client); + ~QtAvahiServiceBrowserPrivate(); + + // Callback members + static void callbackServiceTypeBrowser(AvahiServiceTypeBrowser *browser, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *type, + const char *domain, + AvahiLookupResultFlags flags, + void *userdata); + + static void callbackServiceBrowser(AvahiServiceBrowser *browser, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *type, + const char *domain, + AvahiLookupResultFlags flags, + void *userdata); + + static void callbackServiceResolver(AvahiServiceResolver *resolver, AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiResolverEvent event, + const char *name, + const char *type, + const char *domain, + const char *host_name, + const AvahiAddress *address, + uint16_t port, + AvahiStringList *txt, + AvahiLookupResultFlags flags, + void *userdata); + + // Convert members + static QStringList convertTxtList(AvahiStringList *txt); + static QAbstractSocket::NetworkLayerProtocol convertProtocol(const AvahiProtocol &protocol); + + QtAvahiClient *client; + AvahiServiceTypeBrowser *serviceTypeBrowser; + QHash serviceBrowserTable; + QList m_serviceResolvers; +}; + +#endif // QTAVAHISERVICEBROWSERPRIVATE_H diff --git a/guh-control/discovery/discoverydevice.cpp b/guh-control/discovery/discoverydevice.cpp new file mode 100644 index 00000000..a2780eaf --- /dev/null +++ b/guh-control/discovery/discoverydevice.cpp @@ -0,0 +1,176 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of guh-ubuntu. * + * * + * guh-ubuntu is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * guh-ubuntu is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh-ubuntu. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "discoverydevice.h" + +#include + +DiscoveryDevice::DiscoveryDevice() +{ +} + +QUrl DiscoveryDevice::location() const +{ + return m_location; +} + +void DiscoveryDevice::setLocation(const QUrl &location) +{ + m_location = location; +} + +QString DiscoveryDevice::webSocketUrl() const +{ + return m_webSocketUrl; +} + +void DiscoveryDevice::setWebSocketUrl(const QString &webSocketUrl) +{ + m_webSocketUrl = webSocketUrl; +} + +QString DiscoveryDevice::guhRpcUrl() const +{ + return m_guhRpcUrl; +} + +void DiscoveryDevice::setGuhRpcUrl(const QString &guhRpcUrl) +{ + m_guhRpcUrl = guhRpcUrl; +} + +QHostAddress DiscoveryDevice::hostAddress() const +{ + return m_hostAddress; +} + +void DiscoveryDevice::setHostAddress(const QHostAddress &hostAddress) +{ + m_hostAddress = hostAddress; +} + +int DiscoveryDevice::port() const +{ + return m_port; +} + +void DiscoveryDevice::setPort(const int &port) +{ + m_port = port; +} + +QString DiscoveryDevice::friendlyName() const +{ + return m_friendlyName; +} + +void DiscoveryDevice::setFriendlyName(const QString &friendlyName) +{ + m_friendlyName = friendlyName; +} + +QString DiscoveryDevice::manufacturer() const +{ + return m_manufacturer; +} + +void DiscoveryDevice::setManufacturer(const QString &manufacturer) +{ + m_manufacturer = manufacturer; +} + +QUrl DiscoveryDevice::manufacturerURL() const +{ + return m_manufacturerURL; +} + +void DiscoveryDevice::setManufacturerURL(const QUrl &manufacturerURL) +{ + m_manufacturerURL = manufacturerURL; +} + +QString DiscoveryDevice::modelDescription() const +{ + return m_modelDescription; +} + +void DiscoveryDevice::setModelDescription(const QString &modelDescription) +{ + m_modelDescription = modelDescription; +} + +QString DiscoveryDevice::modelName() const +{ + return m_modelName; +} + +void DiscoveryDevice::setModelName(const QString &modelName) +{ + m_modelName = modelName; +} + +QString DiscoveryDevice::modelNumber() const +{ + return m_modelNumber; +} + +void DiscoveryDevice::setModelNumber(const QString &modelNumber) +{ + m_modelNumber = modelNumber; +} + +QUrl DiscoveryDevice::modelURL() const +{ + return m_modelURL; +} + +void DiscoveryDevice::setModelURL(const QUrl &modelURL) +{ + m_modelURL = modelURL; +} + +QString DiscoveryDevice::uuid() const +{ + return m_uuid; +} + +void DiscoveryDevice::setUuid(const QString &uuid) +{ + m_uuid = uuid; +} + +QDebug operator<<(QDebug debug, const DiscoveryDevice &DiscoveryDevice) +{ + debug << "----------------------------------------------\n"; + debug << "UPnP device on " << QString("%1:%2").arg(DiscoveryDevice.hostAddress().toString()).arg(DiscoveryDevice.port()) << "\n"; + debug << "location | " << DiscoveryDevice.location().toString() << "\n"; + debug << "websocket | " << DiscoveryDevice.webSocketUrl() << "\n"; + debug << "guhrpc | " << DiscoveryDevice.guhRpcUrl() << "\n"; + debug << "friendly name | " << DiscoveryDevice.friendlyName() << "\n"; + debug << "manufacturer | " << DiscoveryDevice.manufacturer() << "\n"; + debug << "manufacturer URL | " << DiscoveryDevice.manufacturerURL().toString() << "\n"; + debug << "model name | " << DiscoveryDevice.modelName() << "\n"; + debug << "model number | " << DiscoveryDevice.modelNumber() << "\n"; + debug << "model description | " << DiscoveryDevice.modelDescription() << "\n"; + debug << "model URL | " << DiscoveryDevice.modelURL().toString() << "\n"; + debug << "UUID | " << DiscoveryDevice.uuid() << "\n"; + + return debug; +} diff --git a/guh-control/discovery/upnpdevice.h b/guh-control/discovery/discoverydevice.h similarity index 93% rename from guh-control/discovery/upnpdevice.h rename to guh-control/discovery/discoverydevice.h index 75173424..8d9b40de 100644 --- a/guh-control/discovery/upnpdevice.h +++ b/guh-control/discovery/discoverydevice.h @@ -18,17 +18,17 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#ifndef UPNPDEVICE_H -#define UPNPDEVICE_H +#ifndef DISCOVERYDEVICE_H +#define DISCOVERYDEVICE_H #include #include #include -class UpnpDevice +class DiscoveryDevice { public: - explicit UpnpDevice(); + explicit DiscoveryDevice(); QUrl location() const; void setLocation(const QUrl &location); @@ -88,6 +88,6 @@ private: QString m_uuid; }; -QDebug operator<< (QDebug debug, const UpnpDevice &upnpDevice); +QDebug operator<< (QDebug debug, const DiscoveryDevice &discoveryDevice); -#endif // UPNPDEVICE_H +#endif // DISCOVERYDEVICE_H diff --git a/guh-control/discovery/upnpdiscoverymodel.cpp b/guh-control/discovery/discoverymodel.cpp similarity index 71% rename from guh-control/discovery/upnpdiscoverymodel.cpp rename to guh-control/discovery/discoverymodel.cpp index 52853add..ca79f216 100644 --- a/guh-control/discovery/upnpdiscoverymodel.cpp +++ b/guh-control/discovery/discoverymodel.cpp @@ -18,30 +18,30 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#include "upnpdiscoverymodel.h" +#include "discoverymodel.h" -UpnpDiscoveryModel::UpnpDiscoveryModel(QObject *parent) : +DiscoveryModel::DiscoveryModel(QObject *parent) : QAbstractListModel(parent) { } -QList UpnpDiscoveryModel::devices() +QList DiscoveryModel::devices() { return m_devices; } -int UpnpDiscoveryModel::rowCount(const QModelIndex &parent) const +int DiscoveryModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) return m_devices.count(); } -QVariant UpnpDiscoveryModel::data(const QModelIndex &index, int role) const +QVariant DiscoveryModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= m_devices.count()) return QVariant(); - UpnpDevice device = m_devices.at(index.row()); + DiscoveryDevice device = m_devices.at(index.row()); if (role == NameRole) { return device.friendlyName(); } else if (role == HostAddressRole) { @@ -58,22 +58,29 @@ QVariant UpnpDiscoveryModel::data(const QModelIndex &index, int role) const return QVariant(); } -void UpnpDiscoveryModel::addDevice(UpnpDevice device) +void DiscoveryModel::addDevice(const DiscoveryDevice &device) { + for (int i = 0; i < m_devices.count(); i++) { + if (m_devices.at(i).uuid() == device.uuid()) { + m_devices[i] = device; + emit dataChanged(index(i), index(i)); + return; + } + } beginInsertRows(QModelIndex(), m_devices.count(), m_devices.count()); m_devices.append(device); endInsertRows(); emit countChanged(); } -QString UpnpDiscoveryModel::get(int index, const QByteArray &role) const +QString DiscoveryModel::get(int index, const QByteArray &role) const { return data(this->index(index), roleNames().key(role)).toString(); } -bool UpnpDiscoveryModel::contains(const QString &uuid) const +bool DiscoveryModel::contains(const QString &uuid) const { - foreach (const UpnpDevice &dev, m_devices) { + foreach (const DiscoveryDevice &dev, m_devices) { if (dev.uuid() == uuid) { return true; } @@ -81,7 +88,17 @@ bool UpnpDiscoveryModel::contains(const QString &uuid) const return false; } -void UpnpDiscoveryModel::clearModel() +DiscoveryDevice DiscoveryModel::find(const QHostAddress &address) const +{ + foreach (const DiscoveryDevice &dev, m_devices) { + if (dev.hostAddress() == address) { + return dev; + } + } + return DiscoveryDevice(); +} + +void DiscoveryModel::clearModel() { beginResetModel(); m_devices.clear(); @@ -89,7 +106,7 @@ void UpnpDiscoveryModel::clearModel() emit countChanged(); } -QHash UpnpDiscoveryModel::roleNames() const +QHash DiscoveryModel::roleNames() const { QHash roles; roles[NameRole] = "name"; diff --git a/guh-control/discovery/upnpdiscoverymodel.h b/guh-control/discovery/discoverymodel.h similarity index 84% rename from guh-control/discovery/upnpdiscoverymodel.h rename to guh-control/discovery/discoverymodel.h index 47907431..cdec68aa 100644 --- a/guh-control/discovery/upnpdiscoverymodel.h +++ b/guh-control/discovery/discoverymodel.h @@ -18,15 +18,15 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#ifndef UPNPDISCOVERYMODEL_H -#define UPNPDISCOVERYMODEL_H +#ifndef DISCOVERYMODEL_H +#define DISCOVERYMODEL_H #include #include -#include "upnpdevice.h" +#include "discoverydevice.h" -class UpnpDiscoveryModel : public QAbstractListModel +class DiscoveryModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(int count READ rowCount NOTIFY countChanged) @@ -40,17 +40,18 @@ public: VersionRole }; - explicit UpnpDiscoveryModel(QObject *parent = 0); + explicit DiscoveryModel(QObject *parent = 0); - QList devices(); + QList devices(); int rowCount(const QModelIndex & parent = QModelIndex()) const; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; - void addDevice(UpnpDevice device); + void addDevice(const DiscoveryDevice &device); Q_INVOKABLE QString get(int index, const QByteArray &role) const; bool contains(const QString &uuid) const; + DiscoveryDevice find(const QHostAddress &address) const; void clearModel(); @@ -61,9 +62,7 @@ protected: QHash roleNames() const; private: - QList m_devices; - - + QList m_devices; }; -#endif // UPNPDISCOVERYMODEL_H +#endif // DISCOVERYMODEL_H diff --git a/guh-control/discovery/upnpdevice.cpp b/guh-control/discovery/upnpdevice.cpp deleted file mode 100644 index 7b982f71..00000000 --- a/guh-control/discovery/upnpdevice.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Copyright (C) 2015 Simon Stuerz * - * * - * This file is part of guh-ubuntu. * - * * - * guh-ubuntu is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, version 3 of the License. * - * * - * guh-ubuntu is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with guh-ubuntu. If not, see . * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include "upnpdevice.h" - -#include - -UpnpDevice::UpnpDevice() -{ -} - -QUrl UpnpDevice::location() const -{ - return m_location; -} - -void UpnpDevice::setLocation(const QUrl &location) -{ - m_location = location; -} - -QString UpnpDevice::webSocketUrl() const -{ - return m_webSocketUrl; -} - -void UpnpDevice::setWebSocketUrl(const QString &webSocketUrl) -{ - m_webSocketUrl = webSocketUrl; -} - -QString UpnpDevice::guhRpcUrl() const -{ - return m_guhRpcUrl; -} - -void UpnpDevice::setGuhRpcUrl(const QString &guhRpcUrl) -{ - m_guhRpcUrl = guhRpcUrl; -} - -QHostAddress UpnpDevice::hostAddress() const -{ - return m_hostAddress; -} - -void UpnpDevice::setHostAddress(const QHostAddress &hostAddress) -{ - m_hostAddress = hostAddress; -} - -int UpnpDevice::port() const -{ - return m_port; -} - -void UpnpDevice::setPort(const int &port) -{ - m_port = port; -} - -QString UpnpDevice::friendlyName() const -{ - return m_friendlyName; -} - -void UpnpDevice::setFriendlyName(const QString &friendlyName) -{ - m_friendlyName = friendlyName; -} - -QString UpnpDevice::manufacturer() const -{ - return m_manufacturer; -} - -void UpnpDevice::setManufacturer(const QString &manufacturer) -{ - m_manufacturer = manufacturer; -} - -QUrl UpnpDevice::manufacturerURL() const -{ - return m_manufacturerURL; -} - -void UpnpDevice::setManufacturerURL(const QUrl &manufacturerURL) -{ - m_manufacturerURL = manufacturerURL; -} - -QString UpnpDevice::modelDescription() const -{ - return m_modelDescription; -} - -void UpnpDevice::setModelDescription(const QString &modelDescription) -{ - m_modelDescription = modelDescription; -} - -QString UpnpDevice::modelName() const -{ - return m_modelName; -} - -void UpnpDevice::setModelName(const QString &modelName) -{ - m_modelName = modelName; -} - -QString UpnpDevice::modelNumber() const -{ - return m_modelNumber; -} - -void UpnpDevice::setModelNumber(const QString &modelNumber) -{ - m_modelNumber = modelNumber; -} - -QUrl UpnpDevice::modelURL() const -{ - return m_modelURL; -} - -void UpnpDevice::setModelURL(const QUrl &modelURL) -{ - m_modelURL = modelURL; -} - -QString UpnpDevice::uuid() const -{ - return m_uuid; -} - -void UpnpDevice::setUuid(const QString &uuid) -{ - m_uuid = uuid; -} - -QDebug operator<<(QDebug debug, const UpnpDevice &upnpDevice) -{ - debug << "----------------------------------------------\n"; - debug << "UPnP device on " << QString("%1:%2").arg(upnpDevice.hostAddress().toString()).arg(upnpDevice.port()) << "\n"; - debug << "location | " << upnpDevice.location().toString() << "\n"; - debug << "websocket | " << upnpDevice.webSocketUrl() << "\n"; - debug << "guhrpc | " << upnpDevice.guhRpcUrl() << "\n"; - debug << "friendly name | " << upnpDevice.friendlyName() << "\n"; - debug << "manufacturer | " << upnpDevice.manufacturer() << "\n"; - debug << "manufacturer URL | " << upnpDevice.manufacturerURL().toString() << "\n"; - debug << "model name | " << upnpDevice.modelName() << "\n"; - debug << "model number | " << upnpDevice.modelNumber() << "\n"; - debug << "model description | " << upnpDevice.modelDescription() << "\n"; - debug << "model URL | " << upnpDevice.modelURL().toString() << "\n"; - debug << "UUID | " << upnpDevice.uuid() << "\n"; - - return debug; -} diff --git a/guh-control/discovery/upnpdiscovery.cpp b/guh-control/discovery/upnpdiscovery.cpp index dea9be7b..a1366414 100644 --- a/guh-control/discovery/upnpdiscovery.cpp +++ b/guh-control/discovery/upnpdiscovery.cpp @@ -32,12 +32,13 @@ UpnpDiscovery::UpnpDiscovery(QObject *parent) : m_networkAccessManager = new QNetworkAccessManager(this); connect(m_networkAccessManager, &QNetworkAccessManager::finished, this, &UpnpDiscovery::networkReplyFinished); - m_discoveryModel = new UpnpDiscoveryModel(this); + m_discoveryModel = new DiscoveryModel(this); - m_timer = new QTimer(this); - m_timer->setSingleShot(true); - m_timer->setInterval(5000); - connect(m_timer, &QTimer::timeout, this, &UpnpDiscovery::onTimeout); + m_timer.setSingleShot(true); + m_timer.setInterval(5000); + connect(&m_timer, &QTimer::timeout, this, &UpnpDiscovery::onTimeout); + m_repeatTimer.setInterval(500); + connect(&m_repeatTimer, &QTimer::timeout, this, &UpnpDiscovery::writeDiscoveryPacket); // bind udp socket and join multicast group m_port = 1900; @@ -68,7 +69,7 @@ bool UpnpDiscovery::discovering() const return m_discovering; } -UpnpDiscoveryModel *UpnpDiscovery::discoveryModel() +DiscoveryModel *UpnpDiscovery::discoveryModel() { return m_discoveryModel; } @@ -86,25 +87,21 @@ void UpnpDiscovery::discover() } qDebug() << "start discovering..."; - m_timer->start(); + m_timer.start(); + m_repeatTimer.start(); // m_discoveryModel->clearModel(); m_foundDevices.clear(); setDiscovering(true); - QByteArray ssdpSearchMessage = QByteArray("M-SEARCH * HTTP/1.1\r\n" - "HOST:239.255.255.250:1900\r\n" - "MAN:\"ssdp:discover\"\r\n" - "MX:2\r\n" - "ST: ssdp:all\r\n\r\n"); - - writeDatagram(ssdpSearchMessage, m_host, m_port); + writeDiscoveryPacket(); } void UpnpDiscovery::stopDiscovery() { qDebug() << "stop discovering"; - m_timer->stop(); + m_timer.stop(); + m_repeatTimer.stop(); m_discoveryModel->clearModel(); setDiscovering(false); } @@ -121,6 +118,17 @@ void UpnpDiscovery::setAvailable(const bool &available) emit availableChanged(); } +void UpnpDiscovery::writeDiscoveryPacket() +{ + QByteArray ssdpSearchMessage = QByteArray("M-SEARCH * HTTP/1.1\r\n" + "HOST:239.255.255.250:1900\r\n" + "MAN:\"ssdp:discover\"\r\n" + "MX:2\r\n" + "ST: ssdp:all\r\n\r\n"); + + writeDatagram(ssdpSearchMessage, m_host, m_port); +} + void UpnpDiscovery::error(QAbstractSocket::SocketError error) { qWarning() << "UPnP socket error:" << error << errorString(); @@ -165,17 +173,17 @@ void UpnpDiscovery::readData() if (!m_foundDevices.contains(location) && isGuh) { m_foundDevices.append(location); - UpnpDevice upnpDevice; - upnpDevice.setHostAddress(hostAddress); - upnpDevice.setPort(port); - upnpDevice.setLocation(location.toString()); + DiscoveryDevice discoveryDevice; + discoveryDevice.setHostAddress(hostAddress); + discoveryDevice.setPort(port); + discoveryDevice.setLocation(location.toString()); qDebug() << "Getting server data from:" << location; QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(location)); connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList &errors){ reply->ignoreSslErrors(errors); }); - m_runningReplies.insert(reply, upnpDevice); + m_runningReplies.insert(reply, discoveryDevice); } } } @@ -187,7 +195,7 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply) switch (status) { case(200):{ QByteArray data = reply->readAll(); - UpnpDevice upnpDevice = m_runningReplies.take(reply); + DiscoveryDevice discoveryDevice = m_runningReplies.take(reply); // parse XML data QXmlStreamReader xml(data); @@ -199,13 +207,13 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply) if (xml.isStartElement()) { if (xml.name().toString() == "websocketURL") { - upnpDevice.setWebSocketUrl(xml.readElementText()); + discoveryDevice.setWebSocketUrl(xml.readElementText()); } } if (xml.isStartElement()) { if (xml.name().toString() == "guhRpcURL") { - upnpDevice.setGuhRpcUrl(xml.readElementText()); + discoveryDevice.setGuhRpcUrl(xml.readElementText()); } } @@ -213,28 +221,28 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply) if (xml.name().toString() == "device") { while (!xml.atEnd()) { if (xml.name() == "friendlyName" && xml.isStartElement()) { - upnpDevice.setFriendlyName(xml.readElementText()); + discoveryDevice.setFriendlyName(xml.readElementText()); } if (xml.name() == "manufacturer" && xml.isStartElement()) { - upnpDevice.setManufacturer(xml.readElementText()); + discoveryDevice.setManufacturer(xml.readElementText()); } if (xml.name() == "manufacturerURL" && xml.isStartElement()) { - upnpDevice.setManufacturerURL(QUrl(xml.readElementText())); + discoveryDevice.setManufacturerURL(QUrl(xml.readElementText())); } if (xml.name() == "modelDescription" && xml.isStartElement()) { - upnpDevice.setModelDescription(xml.readElementText()); + discoveryDevice.setModelDescription(xml.readElementText()); } if (xml.name() == "modelName" && xml.isStartElement()) { - upnpDevice.setModelName(xml.readElementText()); + discoveryDevice.setModelName(xml.readElementText()); } if (xml.name() == "modelNumber" && xml.isStartElement()) { - upnpDevice.setModelNumber(xml.readElementText()); + discoveryDevice.setModelNumber(xml.readElementText()); } if (xml.name() == "modelURL" && xml.isStartElement()) { - upnpDevice.setModelURL(QUrl(xml.readElementText())); + discoveryDevice.setModelURL(QUrl(xml.readElementText())); } if (xml.name() == "UDN" && xml.isStartElement()) { - upnpDevice.setUuid(xml.readElementText()); + discoveryDevice.setUuid(xml.readElementText()); } xml.readNext(); } @@ -244,10 +252,10 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply) } - if (upnpDevice.friendlyName().contains("guh")) { - qDebug() << upnpDevice; - if (!m_discoveryModel->contains(upnpDevice.uuid())) { - m_discoveryModel->addDevice(upnpDevice); + if (discoveryDevice.friendlyName().contains("guh")) { + qDebug() << discoveryDevice; + if (!m_discoveryModel->contains(discoveryDevice.uuid())) { + m_discoveryModel->addDevice(discoveryDevice); } } @@ -264,5 +272,6 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply) void UpnpDiscovery::onTimeout() { qDebug() << "discovery timeout"; + m_repeatTimer.stop(); setDiscovering(false); } diff --git a/guh-control/discovery/upnpdiscovery.h b/guh-control/discovery/upnpdiscovery.h index 081ca3a9..c68c75c6 100644 --- a/guh-control/discovery/upnpdiscovery.h +++ b/guh-control/discovery/upnpdiscovery.h @@ -27,21 +27,21 @@ #include #include -#include "upnpdevice.h" -#include "upnpdiscoverymodel.h" +#include "discoverydevice.h" +#include "discoverymodel.h" class UpnpDiscovery : public QUdpSocket { Q_OBJECT Q_PROPERTY(bool discovering READ discovering NOTIFY discoveringChanged) Q_PROPERTY(bool available READ available NOTIFY availableChanged) - Q_PROPERTY(UpnpDiscoveryModel *discoveryModel READ discoveryModel NOTIFY discoveryModelChanged) + Q_PROPERTY(DiscoveryModel *discoveryModel READ discoveryModel NOTIFY discoveryModelChanged) public: explicit UpnpDiscovery(QObject *parent = 0); bool discovering() const; - UpnpDiscoveryModel *discoveryModel(); + DiscoveryModel *discoveryModel(); bool available() const; @@ -51,16 +51,17 @@ public: private: QNetworkAccessManager *m_networkAccessManager; - QTimer *m_timer; + QTimer m_timer; + QTimer m_repeatTimer; QHostAddress m_host; qint16 m_port; - UpnpDiscoveryModel *m_discoveryModel; + DiscoveryModel *m_discoveryModel; bool m_discovering; bool m_available; - QHash m_runningReplies; + QHash m_runningReplies; QList m_foundDevices; void setDiscovering(const bool &discovering); @@ -72,6 +73,7 @@ signals: void discoveryModelChanged(); private slots: + void writeDiscoveryPacket(); void error(QAbstractSocket::SocketError error); void readData(); void networkReplyFinished(QNetworkReply *reply); diff --git a/guh-control/discovery/zeroconfdiscovery.cpp b/guh-control/discovery/zeroconfdiscovery.cpp new file mode 100644 index 00000000..7a637fa6 --- /dev/null +++ b/guh-control/discovery/zeroconfdiscovery.cpp @@ -0,0 +1,70 @@ +#include "zeroconfdiscovery.h" + +#include + + +ZeroconfDiscovery::ZeroconfDiscovery(QObject *parent) : QObject(parent) +{ + m_discoveryModel = new DiscoveryModel(this); + +#ifdef WITH_AVAHI + m_serviceBrowser = new QtAvahiServiceBrowser(this); + connect(m_serviceBrowser, &QtAvahiServiceBrowser::serviceEntryAdded, this, &ZeroconfDiscovery::serviceEntryAdded); + m_serviceBrowser->enable(); +#endif +} + +bool ZeroconfDiscovery::available() const +{ +#ifdef WITH_AVAHI + return true; +#else + return false; +#endif +} + +bool ZeroconfDiscovery::discovering() const +{ + return true; +} + +DiscoveryModel *ZeroconfDiscovery::discoveryModel() const +{ + return m_discoveryModel; +} +#ifdef WITH_AVAHI +void ZeroconfDiscovery::serviceEntryAdded(const AvahiServiceEntry &entry) +{ + if (!entry.name().startsWith("guhIO") || entry.serviceType() != "_jsonrpc._tcp") { + return; + } + qDebug() << "avahi service entry added" << entry.name() << entry.hostAddress() << entry.port() << entry.txt() << entry.serviceType(); + + QString uuid; + bool sslEnabled = false; + foreach (const QString &txt, entry.txt()) { + QPair txtRecord = qMakePair(txt.split("=").first(), txt.split("=").at(1)); + if (!sslEnabled && txtRecord.first == "sslEnabled") { + sslEnabled = (txtRecord.second == "true"); + } + if (txtRecord.first == "uuid") { + uuid = txtRecord.second; + } + } + + DiscoveryDevice dev = m_discoveryModel->find(entry.hostAddress()); + if (dev.uuid() == uuid && dev.guhRpcUrl().startsWith("guhs") && !sslEnabled) { + // We already have this host and with a more secure configuration... skip this one... + return; + } + dev.setUuid(uuid); + dev.setHostAddress(entry.hostAddress()); + dev.setPort(entry.port()); + dev.setFriendlyName(entry.hostName()); + dev.setGuhRpcUrl(QString("%1://%2:%3").arg(sslEnabled ? "guhs" : "guh").arg(entry.hostAddress().toString()).arg(entry.port())); + m_discoveryModel->addDevice(dev); + +// DiscoveryDevice *dev = new DiscoveryDevice(); +// dev->setFriendlyName(entry.hostName()); +} +#endif diff --git a/guh-control/discovery/zeroconfdiscovery.h b/guh-control/discovery/zeroconfdiscovery.h new file mode 100644 index 00000000..bf63f78e --- /dev/null +++ b/guh-control/discovery/zeroconfdiscovery.h @@ -0,0 +1,45 @@ +#ifndef ZEROCONFDISCOVERY_H +#define ZEROCONFDISCOVERY_H + +#ifdef WITH_AVAHI +#include "avahi/qtavahiservicebrowser.h" +#endif + +#include "discoverymodel.h" + +#include + +class ZeroconfDiscovery : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool discovering READ discovering NOTIFY discoveringChanged) + Q_PROPERTY(bool available READ available CONSTANT) + Q_PROPERTY(DiscoveryModel* discoveryModel READ discoveryModel CONSTANT) + +public: + explicit ZeroconfDiscovery(QObject *parent = nullptr); + + bool available() const; + bool discovering() const; + + DiscoveryModel* discoveryModel() const; + +signals: + void discoveringChanged(); + +public slots: + +private: + DiscoveryModel *m_discoveryModel; + +#ifdef WITH_AVAHI + QtAvahiServiceBrowser *m_serviceBrowser; + +private slots: + void serviceEntryAdded(const AvahiServiceEntry &entry); + +#endif + +}; + +#endif // ZEROCONFDISCOVERY_H diff --git a/guh-control/main.cpp b/guh-control/main.cpp index c3f48061..3f797d0a 100644 --- a/guh-control/main.cpp +++ b/guh-control/main.cpp @@ -31,6 +31,7 @@ #include "pluginsproxy.h" #include "devicediscovery.h" #include "discovery/upnpdiscovery.h" +#include "discovery/zeroconfdiscovery.h" #include "interfacesmodel.h" int main(int argc, char *argv[]) @@ -85,6 +86,7 @@ int main(int argc, char *argv[]) qmlRegisterType(uri, 1, 0, "PluginsProxy"); qmlRegisterType(uri, 1, 0, "UpnpDiscovery"); + qmlRegisterType(uri, 1, 0, "ZeroconfDiscovery"); Engine::instance(); diff --git a/guh-control/tcpsocketinterface.cpp b/guh-control/tcpsocketinterface.cpp index 19c51bf1..3aae1766 100644 --- a/guh-control/tcpsocketinterface.cpp +++ b/guh-control/tcpsocketinterface.cpp @@ -47,7 +47,7 @@ void TcpSocketInterface::connect(const QUrl &url) { m_url = url; if (url.scheme() == "guhs") { - qDebug() << "connecting to" << url.host(); + qDebug() << "connecting to" << url.host() << url.port(); m_socket.connectToHostEncrypted(url.host(), url.port()); } else if (url.scheme() == "guh") { m_socket.connectToHost(url.host(), url.port()); diff --git a/guh-control/ui/ConnectPage.qml b/guh-control/ui/ConnectPage.qml index a40a824b..82edbd7a 100644 --- a/guh-control/ui/ConnectPage.qml +++ b/guh-control/ui/ConnectPage.qml @@ -12,8 +12,8 @@ Page { if (settings.lastConnectedHost.length > 0) { internalPageStack.push(connectingPage) Engine.connection.connect(settings.lastConnectedHost) - } else if (upnpDiscovery.discoveryModel.count <= 1) { - upnpDiscovery.discover() + } else if (discovery.discoveryModel.count <= 1) { + discovery.discover() } else { internalPageStack.pop(); internalPageStack.push(searchResultsPage) @@ -29,25 +29,25 @@ Page { certDialog.open(); } onConnectionError: { - upnpDiscovery.discover(); + discovery.discover(); } } Connections { - target: upnpDiscovery + target: discovery onDiscoveringChanged: { - print("discoverying changed", upnpDiscovery.discovering) - if (upnpDiscovery.discovering) { + print("discoverying changed", discovery.discovering) + if (discovery.discovering) { internalPageStack.pop(); } else { - switch (upnpDiscovery.discoveryModel.count) { + switch (discovery.discoveryModel.count) { case 0: internalPageStack.push(noResultsPage); break; case 1: - Engine.connection.connect(upnpDiscovery.discoveryModel.get(0, "guhRpcUrl")) + Engine.connection.connect(discovery.discoveryModel.get(0, "guhRpcUrl")) internalPageStack.push(connectingPage); break; default: @@ -77,7 +77,7 @@ Page { BusyIndicator { anchors.horizontalCenter: parent.horizontalCenter - running: upnpDiscovery.discovering + running: discovery.discovering } } } @@ -107,7 +107,7 @@ Page { Button { Layout.fillWidth: true text: "Try again!" - onClicked: upnpDiscovery.discover() + onClicked: discovery.discover() } } } @@ -136,7 +136,7 @@ Page { Label { Layout.fillWidth: true - text: qsTr("There are %1 guh boxes in your network! Which one would you like to use?").arg(upnpDiscovery.discoveryModel.count) + text: qsTr("There are %1 guh boxes in your network! Which one would you like to use?").arg(discovery.discoveryModel.count) wrapMode: Text.WordWrap } } @@ -150,7 +150,7 @@ Page { ListView { Layout.fillWidth: true Layout.fillHeight: true - model: upnpDiscovery.discoveryModel + model: discovery.discoveryModel delegate: ItemDelegate { width: parent.width height: app.delegateHeight @@ -194,7 +194,7 @@ Page { text: "Search again!" Layout.fillWidth: true onClicked: { - upnpDiscovery.discover() + discovery.discover() } } } diff --git a/guh-control/ui/main.qml b/guh-control/ui/main.qml index 32256709..2cddf9a8 100644 --- a/guh-control/ui/main.qml +++ b/guh-control/ui/main.qml @@ -71,6 +71,10 @@ ApplicationWindow { } UpnpDiscovery { - id: upnpDiscovery + id: discovery } + +// ZeroconfDiscovery { +// id: discovery +// } }