commit 1ac6367f2510b9e88ebe8866ffdb34d68ad7a1c8 Author: Stefan Bühler Date: Sat Jul 24 18:16:44 2010 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0930f61 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.pro.user +Makefile +moc_* +*.o +*.so.* +ui_* +*.so +qcp/qcp diff --git a/libqcp/libqcp.pro b/libqcp/libqcp.pro new file mode 100644 index 0000000..da55f48 --- /dev/null +++ b/libqcp/libqcp.pro @@ -0,0 +1,16 @@ +# ------------------------------------------------- +# Project created by QtCreator 2010-07-21T19:59:16 +# ------------------------------------------------- +QT += network + +# QT -= gui +TARGET = qcp +TEMPLATE = lib +DEFINES += LIBQCP_LIBRARY +SOURCES += qcp.cpp \ + qcpmodel.cpp +HEADERS += qcp.h \ + libqcp_global.h \ + qcpmodel.h +TARGETDEPS += ../libqtavahi/libqtavahi.so +LIBS += ../libqtavahi/libqtavahi.so diff --git a/libqcp/libqcp_global.h b/libqcp/libqcp_global.h new file mode 100644 index 0000000..58e3d86 --- /dev/null +++ b/libqcp/libqcp_global.h @@ -0,0 +1,12 @@ +#ifndef LIBQCP_GLOBAL_H +#define LIBQCP_GLOBAL_H + +#include + +#if defined(LIBQCP_LIBRARY) +# define LIBQCPSHARED_EXPORT Q_DECL_EXPORT +#else +# define LIBQCPSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // LIBQCP_GLOBAL_H diff --git a/libqcp/qcp.cpp b/libqcp/qcp.cpp new file mode 100644 index 0000000..6fbdf56 --- /dev/null +++ b/libqcp/qcp.cpp @@ -0,0 +1,61 @@ +#include "qcp.h" + +#define SERVICETYPE "_qcp._tcp" + +namespace QCP { + Browse::Browse(QObject *parent) + : QObject(parent), m_av_browse(new Avahi::Browse(SERVICETYPE, this)) { + connect(m_av_browse, SIGNAL(found(Avahi::Service)), this, SLOT(foundService(Avahi::Service))); + connect(m_av_browse, SIGNAL(lost(Avahi::Service)), this, SLOT(lostService(Avahi::Service))); + connect(m_av_browse, SIGNAL(started()), this, SIGNAL(started())); + connect(m_av_browse, SIGNAL(stopped()), this, SIGNAL(stopped())); + } + + WatchShare *Browse::watch(const Avahi::Service &service, QObject *parent) { + ShareData *sd = shareData(service); + WatchShare *ws; + if (!sd) return 0; + + sd->watcherList.append(ws = new WatchShare(parent, Share(this, service))); + ws->m_list_it = sd->watcherList.end()-1; + + return ws; + } + + void Browse::removeWatch(WatcherList::iterator it, const Avahi::Service &service) { + ShareData *sd = shareData(service); + if (!sd || WatcherList::iterator() == it) return; + + sd->watcherList.erase(it); + } + + void Browse::foundService(Avahi::Service s) { + ShareData *sd = new ShareData(); + s.setData(sd); + emit found(Share(this, s)); + } + + void Browse::lostService(Avahi::Service s) { + emit lost(Share(this, s)); + ShareData *sd = shareData(s); + if (0 != sd) { + while (!sd->watcherList.empty()) { + WatchShare *ws = sd->watcherList.first(); + emit ws->lost(); + if (ws == sd->watcherList.first()) { + ws->m_list_it = WatcherList::iterator(); + sd->watcherList.pop_front(); + } + } + delete sd; + } + sd->data = 0; + } + + + WatchShare *Share::watch(QObject *parent) { + return m_browse->watch(m_service, parent); + } + +} + diff --git a/libqcp/qcp.h b/libqcp/qcp.h new file mode 100644 index 0000000..4b6725c --- /dev/null +++ b/libqcp/qcp.h @@ -0,0 +1,107 @@ +#ifndef QCP_H +#define QCP_H + +#include "libqcp_global.h" + +#include +#include + +#include "../libqtavahi/qtavahi.h" + +namespace QCP { + class LIBQCPSHARED_EXPORT Share; + class LIBQCPSHARED_EXPORT WatchShare; + class LIBQCPSHARED_EXPORT Browse; +} + +namespace QCP { + /* The private data of the Avahi::Service instances belongs to QCP ! :) */ + + class LIBQCPSHARED_EXPORT Browse : public QObject { + Q_OBJECT + private: + friend class QCP::Share; + friend class QCP::WatchShare; + + typedef QLinkedList WatcherList; + struct ShareData { + WatcherList watcherList; + void *data; + }; + + public: + Browse(QObject *parent = 0); + + Avahi::Browse* avahiBrowse() { return m_av_browse; } + + inline bool start() { return m_av_browse->start(); } + inline void stop() { m_av_browse->stop(); } + + signals: + void found(QCP::Share s); + void lost(QCP::Share s); + + void started(); + void stopped(); + + private slots: + void foundService(Avahi::Service s); + void lostService(Avahi::Service s); + + private: + WatchShare *watch(const Avahi::Service &service, QObject *parent); + void removeWatch(WatcherList::iterator it, const Avahi::Service &service); + + static inline ShareData* shareData(const Avahi::Service &service) { + return static_cast(service.data()); + } + + Avahi::Browse *m_av_browse; + }; + + class LIBQCPSHARED_EXPORT Share { + private: + friend class QCP::Browse; + friend class QCP::WatchShare; + + public: + inline Share(QCP::Browse *browse, Avahi::Service service) : m_browse(browse), m_service(service) { } + + public: + Avahi::Service avahiService() const { return m_service; } + + WatchShare *watch(QObject *parent = 0); + + void* data() const { Browse::ShareData *sd = Browse::shareData(m_service); return sd ? sd->data : 0; } + void setData(void *data) const { Browse::ShareData *sd = Browse::shareData(m_service); if (sd) sd->data = data; } + + private: + QCP::Browse *m_browse; + Avahi::Service m_service; + }; + + class LIBQCPSHARED_EXPORT WatchShare : public QObject { + Q_OBJECT + + friend class QCP::Share; + friend class QCP::Browse; + private: + inline WatchShare(QObject *parent, Share share) : QObject(parent), m_share(share) { } + + public: + virtual ~WatchShare() { + m_share.m_browse->removeWatch(m_list_it, m_share.m_service); + } + + inline Share share() { return m_share; } + + signals: + void lost(); + + private: + Share m_share; + Browse::WatcherList::iterator m_list_it; + }; +} + +#endif // QCP_H diff --git a/libqcp/qcpmodel.cpp b/libqcp/qcpmodel.cpp new file mode 100644 index 0000000..6b0c0ce --- /dev/null +++ b/libqcp/qcpmodel.cpp @@ -0,0 +1,91 @@ +#include "qcpmodel.h" + +namespace QCP { + + namespace model { + Entry::Entry(Row *parent, const Avahi::Service &s, const QCP::Share &share) + : Avahi::model::Entry(parent, s), m_share(share) { + } + + QVariant Entry::data(int col, int role) { + switch (role) { + case Qt::DisplayRole: + switch (col) { + case 0: return service.hostname(); + case 1: return QString("%1:%2").arg(service.address().toString()).arg(service.port()); + case 2: return "Hello World!"; + } + // case Qt::UserRole + default: + return QVariant(); + } + return QVariant(); + } + + EntryList::EntryList(Row *parent, const Avahi::ServiceKey &k) + : Avahi::model::EntryList(parent, k) { } + + int EntryList::columnCount() { + return 3; + } + + QVariant EntryList::data(int col, int role) { + switch (role) { + case Qt::DisplayRole: + switch (col) { + case 0: return key.name; + } + // case Qt::UserRole + default: + return QVariant(); + } + return QVariant(); + } + + Avahi::model::Entry* EntryList::newEntry(const Avahi::Service &s, void *priv) { + QCP::Share *share = static_cast< QCP::Share* >(priv); + return new Entry(this, s, *share); + } + + + int Root::columnCount() { + return 3; + } + + EntryList* Root::newList(const Avahi::ServiceKey &k) { + return new EntryList(this, k); + } + + } + + + BrowseModel::BrowseModel(QObject *parent) + : Avahi::BrowseModel(new QCP::model::Root(), parent), m_browse(new QCP::Browse(this)) { + connect(m_browse, SIGNAL(found(QCP::Share)), this, SLOT(foundShare(QCP::Share))); + connect(m_browse, SIGNAL(lost(QCP::Share)), this, SLOT(lostShare(QCP::Share))); + + m_browse->start(); + } + + QVariant BrowseModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (orientation != Qt::Horizontal) return QVariant(); + switch (role) { + case Qt::DisplayRole: + switch (section) { + case 0: return "Name"; + case 1: return "Address"; + case 2: return "Version"; + } + break; + } + return QVariant(); + } + + void BrowseModel::foundShare(QCP::Share s) { + insert(s.avahiService(), &s); + } + + void BrowseModel::lostShare(QCP::Share s) { + remove(s.avahiService()); + } +} diff --git a/libqcp/qcpmodel.h b/libqcp/qcpmodel.h new file mode 100644 index 0000000..fe34284 --- /dev/null +++ b/libqcp/qcpmodel.h @@ -0,0 +1,59 @@ +#ifndef QCPMODEL_H +#define QCPMODEL_H + +#include "qcp.h" +#include "../libqtavahi/qtavahimodel.h" + +namespace QCP { + namespace model { + class LIBQCPSHARED_EXPORT Entry : public Avahi::model::Entry { + public: + Entry(Row *parent, const Avahi::Service &s, const QCP::Share &share); + + virtual QVariant data(int col, int role); + + protected: + QCP::Share m_share; + }; + + class LIBQCPSHARED_EXPORT EntryList : public Avahi::model::EntryList { + public: + EntryList(Row *parent, const Avahi::ServiceKey &k); + + virtual int columnCount(); + + virtual QVariant data(int col, int role); + + protected: + virtual Avahi::model::Entry* newEntry(const Avahi::Service &s, void *priv); + }; + + class LIBQCPSHARED_EXPORT Root : public Avahi::model::Root { + public: + virtual int columnCount(); + + protected: + virtual EntryList* newList(const Avahi::ServiceKey &k); + }; + } + + class LIBQCPSHARED_EXPORT BrowseModel : public Avahi::BrowseModel { + Q_OBJECT + + public: + BrowseModel(QObject *parent = 0); + + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + QCP::Browse *browse() { return m_browse; } + + protected slots: + void foundShare(QCP::Share s); + void lostShare(QCP::Share s); + + private: + QCP::Browse *m_browse; + }; +} + +#endif // QCPMODEL_H diff --git a/libqtavahi/libqtavahi.pro b/libqtavahi/libqtavahi.pro new file mode 100644 index 0000000..f8dd72c --- /dev/null +++ b/libqtavahi/libqtavahi.pro @@ -0,0 +1,17 @@ +# ------------------------------------------------- +# Project created by QtCreator 2010-07-23T21:04:59 +# ------------------------------------------------- +QT += network +QT -= gui +TARGET = qtavahi +TEMPLATE = lib +DEFINES += LIBQTAVAHI_LIBRARY +SOURCES += qtavahi.cpp \ + qtavahimodel.cpp +HEADERS += qtavahi.h \ + libqtavahi_global.h \ + qtavahi_p.h \ + qtavahimodel.h +LIBS += -lavahi-common \ + -lavahi-client \ + -lavahi-qt4 diff --git a/libqtavahi/libqtavahi_global.h b/libqtavahi/libqtavahi_global.h new file mode 100644 index 0000000..ebc0035 --- /dev/null +++ b/libqtavahi/libqtavahi_global.h @@ -0,0 +1,12 @@ +#ifndef LIBQTAVAHI_GLOBAL_H +#define LIBQTAVAHI_GLOBAL_H + +#include + +#if defined(LIBQTAVAHI_LIBRARY) +# define LIBQTAVAHISHARED_EXPORT Q_DECL_EXPORT +#else +# define LIBQTAVAHISHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // LIBQTAVAHI_GLOBAL_H diff --git a/libqtavahi/qtavahi.cpp b/libqtavahi/qtavahi.cpp new file mode 100644 index 0000000..eaa2e38 --- /dev/null +++ b/libqtavahi/qtavahi.cpp @@ -0,0 +1,563 @@ + +#include "qtavahi.h" + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +namespace Avahi { + + static QHostAddress fromAvahiAddress(const AvahiAddress *address, AvahiIfIndex interface) { + switch (address->proto) { + case AVAHI_PROTO_INET: + return QHostAddress(qFromBigEndian((quint32) address->data.ipv4.address)); + case AVAHI_PROTO_INET6: { + struct sockaddr_in6 a; + memset(&a, 0, sizeof(a)); + a.sin6_family = AF_INET6; + a.sin6_scope_id = interface; + memcpy(a.sin6_addr.s6_addr, address->data.ipv6.address, 16); + return QHostAddress((const sockaddr*) &a); + } + case AVAHI_PROTO_UNSPEC: + return QHostAddress(); + } + return QHostAddress(); + } + + namespace priv { + + static void browsep_client_callback(AvahiClient *c, AvahiClientState state, void * userdata); + static void browsep_browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, + const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void* userdata); + static void browsep_resolve_callback(AvahiServiceResolver *r, 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); + } +} + +namespace Avahi { + namespace priv { + + class BrowseP { + public: + static const ::AvahiPoll *m_qtpoll; + + QString m_serviceType; + + bool m_initialized; + bool m_running; + + ::AvahiClient *m_client; + ::AvahiServiceBrowser *m_browser; + + QByteArray m_service_type; + + Avahi::Browse *m_parent; + + QHash m_list; + + BrowseP(QString servicetype, Browse *parent) + : m_serviceType(servicetype), + m_initialized(false), m_running(false), m_client(0), m_browser(0), + m_service_type(servicetype.toUtf8()), m_parent(parent) { + int err; + + if (!m_qtpoll) { + if (!(m_qtpoll = avahi_qt_poll_get())) { + qWarning("avahi_qt_poll_get failed"); + return; + } + } + + if (!(m_client = avahi_client_new(m_qtpoll, (AvahiClientFlags) 0, browsep_client_callback, this, &err))) { + qWarning("avahi_client_new failed: %s", avahi_strerror(err)); + return; + } + + m_initialized = true; + } + + void clearList() { + QList l = m_list.values(); + m_list.clear(); + for (int i = 0; i < l.size(); ++i) { + emit m_parent->lost(l.at(i)); + } + } + + void release() { + stop(); + + if (!m_initialized) return; + + if (m_client) { + avahi_client_free(m_client); + m_client = 0; + } + + m_initialized = false; + } + + ~BrowseP() { + release(); + } + + void client_callback(AvahiClient *c, AvahiClientState state) { + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + break; + case AVAHI_CLIENT_FAILURE: + qWarning("avahi client failure: %s", avahi_strerror(avahi_client_errno(c))); + release(); + break; + case AVAHI_CLIENT_S_COLLISION: + case AVAHI_CLIENT_S_REGISTERING: + break; + case AVAHI_CLIENT_CONNECTING: + break; + } + } + + void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, + const char *name, const char *type, const char *domain, AvahiLookupResultFlags /* flags */) { + if (!m_browser) m_browser = b; + Q_ASSERT(m_browser == b); + + if (protocol != AVAHI_PROTO_INET && protocol != AVAHI_PROTO_INET6) return; /* ignore non IPv[46] for now */ + + /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ + + switch (event) { + case AVAHI_BROWSER_FAILURE: + qWarning("(Browser) %s", avahi_strerror(avahi_client_errno(m_client))); + release(); + return; + + case AVAHI_BROWSER_NEW: + qDebug("(Browser) NEW: service '%s' of type '%s' in domain '%s'", name, type, domain); + + /* We ignore the returned resolver object. In the callback + * function we free it. If the server is terminated before + * the callback function is called the server will free + * the resolver for us. */ + + if (!(avahi_service_resolver_new(m_client, interface, protocol, name, type, domain, protocol, (AvahiLookupFlags) 0, browsep_resolve_callback, this))) { + qWarning("Failed to resolve service '%s' on '%s': %s", name, domain, avahi_strerror(avahi_client_errno(m_client))); + } + + break; + + case AVAHI_BROWSER_REMOVE: { + ServiceKey k(interface, protocol, name, domain); + Service s = m_list.value(k); + if (s.valid()) { + m_list.remove(k); + emit m_parent->lost(s); + } + } + break; + + case AVAHI_BROWSER_ALL_FOR_NOW: + break; + case AVAHI_BROWSER_CACHE_EXHAUSTED: + break; + } + } + + void resolve_callback(AvahiServiceResolver *r, 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 */) { + Q_ASSERT(r); + + // if (protocol != AVAHI_PROTO_INET) goto done; /* ignore non IPv4 for now */ + if (protocol != AVAHI_PROTO_INET && protocol != AVAHI_PROTO_INET6) goto done; /* ignore non IPv[46] for now */ + + /* Called whenever a service has been resolved successfully or timed out */ + + switch (event) { + case AVAHI_RESOLVER_FAILURE: + qDebug("(Resolver) Failed to resolve service '%s' of type '%s' at '%s' in domain '%s': %s", name, type, host_name, domain, avahi_strerror(avahi_client_errno(m_client))); + break; + + case AVAHI_RESOLVER_FOUND: { + qDebug("(Resolver) found service '%s' of type '%s' at '%s' in domain '%s'", name, type, host_name, domain); + QHash qtxt; + AvahiStringList *psl; + for (psl = txt ; psl ; psl = psl->next) { + QString e = QString::fromUtf8((const char*) psl->text, psl->size), key, value; + int eqPos = e.indexOf(QChar('"')); + if (eqPos == 0) continue; /* ignore empty keys */ + if (eqPos < -1) { /* "boolean" */ + key = e; + } else { + key = e.left(eqPos - 1); + value = e.mid(eqPos + 1); + } + key = key.toLower(); + qtxt.insert(key, value); + } + + ServiceKey k(interface, protocol, name, domain); + Service s = m_list.value(k); + if (s.valid()) { + m_list.remove(k); + emit m_parent->lost(s); + } + + s = Service(QString::fromUtf8(name), QString::fromUtf8(host_name), QString::fromUtf8(domain), fromAvahiAddress(address, interface), interface, protocol, port, qtxt); + m_list.insert(k, s); + emit m_parent->found(s); + } + } + + done: + avahi_service_resolver_free(r); + } + + bool start() { + if (m_running) return true; + if (!m_initialized) return false; + + clearList(); + + if (NULL == (m_browser = avahi_service_browser_new(m_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, m_service_type.constData(), NULL, (AvahiLookupFlags) 0, browsep_browse_callback, this))) { + qWarning("Failed to create service browser: %s", avahi_strerror(avahi_client_errno(m_client))); + return false; + } + + emit m_parent->started(); + m_running = true; + + return true; + } + + void stop() { + clearList(); + if (m_running) { + emit m_parent->stopped(); + m_running = false; + } + if (m_browser) { + avahi_service_browser_free(m_browser); + m_browser = 0; + } + } + }; + const AvahiPoll *BrowseP::m_qtpoll = 0; + + static void browsep_client_callback(AvahiClient *c, AvahiClientState state, void * userdata) { + BrowseP *p = static_cast(userdata); + p->client_callback(c, state); + } + + static void browsep_browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, + const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void* userdata) { + BrowseP *p = static_cast(userdata); + p->browse_callback(b, interface, protocol, event, name, type, domain, flags); + } + + static void browsep_resolve_callback(AvahiServiceResolver *r, 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) { + BrowseP *p = static_cast(userdata); + p->resolve_callback(r, interface, protocol, event, name, type, domain, host_name, address, port, txt, flags); + } + + static void announcep_client_callback(AvahiClient *c, AvahiClientState state, void * userdata); + static void announcep_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata); + + class AnnounceP { + public: + static const AvahiPoll *m_qtpoll; + + QString m_serviceType, m_name; + quint16 m_port; + QHash m_txt; + + bool m_initialized; + bool m_running; + + AvahiClient *m_client; + AvahiEntryGroup *m_group; + + QByteArray m_service_type; + + Announce *m_parent; + + char *m_raw_name; /* avahi string */ + QList m_txt_list; + ::AvahiStringList *m_raw_txt; + + AnnounceP(QString serviceType, QString name, quint16 port, QHash txt, Announce *parent) + : m_serviceType(serviceType), m_name(name), m_port(port), m_txt(txt), + m_initialized(false), m_running(false), m_client(0), m_group(0), + m_service_type(serviceType.toUtf8()), m_parent(parent), + m_raw_name(avahi_strdup(name.toUtf8().constData())), m_raw_txt(0) { + int err; + + QHashIterator i(m_txt); + while (i.hasNext()) { + QByteArray a; + i.next(); + if (i.value().isNull()) { + m_txt_list.append(a = i.key().toUtf8()); + } else { + m_txt_list.append(a = QString("%1=%2").arg(i.key(), i.value()).toUtf8()); + } + AvahiStringList *l = avahi_string_list_new(a.constData(), NULL); + l->next = m_raw_txt; + m_raw_txt = l; + } + + if (!m_qtpoll) { + if (!(m_qtpoll = avahi_qt_poll_get())) { + qWarning("avahi_qt_poll_get failed"); + return; + } + } + + if (!(m_client = avahi_client_new(m_qtpoll, (AvahiClientFlags) 0, announcep_client_callback, this, &err))) { + qWarning("avahi_client_new failed: %s", avahi_strerror(err)); + return; + } + + m_initialized = true; + + start(); + } + + void release() { + if (m_running) { + m_running = false; + } + if (!m_initialized) return; + + if (m_group) { + qDebug("avahi unregister service"); + avahi_entry_group_reset(m_group); + avahi_entry_group_free(m_group); + m_group = 0; + } + + if (m_client) { + avahi_client_free(m_client); + m_client = 0; + } + + m_initialized = false; + + emit m_parent->stopped(); + } + + ~AnnounceP() { + release(); + if (m_raw_name) { + avahi_free(m_raw_name); + m_raw_name = 0; + } + if (m_raw_txt) { + avahi_string_list_free(m_raw_txt); + m_raw_txt = 0; + } + } + + void client_callback(AvahiClient *c, AvahiClientState state) { + m_client = c; + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + create_services(); + break; + case AVAHI_CLIENT_FAILURE: + qDebug("avahi client failure"); + release(); + break; + case AVAHI_CLIENT_S_COLLISION: + case AVAHI_CLIENT_S_REGISTERING: + if (m_group) { + avahi_entry_group_reset(m_group); + } + break; + case AVAHI_CLIENT_CONNECTING: + break; + } + } + + void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state) { + m_group = g; + + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED : + emit m_parent->established(); + break; + + case AVAHI_ENTRY_GROUP_COLLISION : + { + char *n = avahi_alternative_service_name(m_raw_name); + avahi_free(m_raw_name); + m_raw_name = n; + m_name = QString::fromUtf8(m_raw_name); + } + + /* And recreate the services */ + avahi_entry_group_reset(m_group); + create_services(); + /* same name should result in AVAHI_ERR_COLLISION in create_services, emit collision there. */ + break; + + case AVAHI_ENTRY_GROUP_FAILURE : + qDebug("Entry group failure: %s", avahi_strerror(avahi_client_errno(m_client))); + + release(); + break; + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + break; + } + } + + bool create_services() { + int ret; + + if (!m_group) { + if (!(m_group = avahi_entry_group_new(m_client, announcep_entry_group_callback, this))) { + qDebug("avahi_entry_group_new failed: %s", avahi_strerror(avahi_client_errno(m_client))); + release(); + return false; + } + } + + again: + if (avahi_entry_group_is_empty(m_group)) { + if ((ret = avahi_entry_group_add_service_strlst(m_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, (AvahiPublishFlags) 0, m_raw_name, m_service_type.constData(), NULL, NULL, m_port, m_raw_txt)) < 0) { + + if (ret == AVAHI_ERR_COLLISION) + goto collision; + + qDebug("Failed to add '%s': %s", m_service_type.constData(), avahi_strerror(ret)); + release(); + return false; + } + if ((ret = avahi_entry_group_commit(m_group)) < 0) { + qDebug("Failed to commit entry group: %s", avahi_strerror(ret)); + release(); + return false; + } + } + + return true; + + collision: + qDebug("avahi name collision: %s, trying a new one", m_raw_name); + { + char *n = avahi_alternative_service_name(m_raw_name); + avahi_free(m_raw_name); + m_raw_name = n; + m_name = QString::fromUtf8(m_raw_name); + } + avahi_entry_group_reset(m_group); + emit m_parent->collision(); + goto again; + } + + bool start() { + if (m_running) return true; + if (!m_initialized) return false; + + m_running = true; + emit m_parent->started(); + + return true; + } + + void stop() { + release(); + } + }; + const AvahiPoll *AnnounceP::m_qtpoll = 0; + + static void announcep_client_callback(AvahiClient *c, AvahiClientState state, void * userdata) { + AnnounceP *p = static_cast(userdata); + p->client_callback(c, state); + } + static void announcep_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { + AnnounceP *p = static_cast(userdata); + p->entry_group_callback(g, state); + } + } + + /* public interfaces */ + + Browse::Browse(QString serviceType, QObject *parent) + : QObject(parent), m_priv(new priv::BrowseP(serviceType, this)) { + } + + Browse::~Browse() { + delete m_priv; + m_priv = 0; + } + + bool Browse::start() { + return m_priv->start(); + } + + void Browse::stop() { + m_priv->stop(); + } + + QString Browse::serviceType() const { + return m_priv->m_serviceType; + } + + QList Browse::currentList() const { + return m_priv->m_list.values(); + } + + + Announce::Announce(QString serviceType, QString name, quint16 port, QHash txt, QObject *parent) + : QObject(parent), m_priv(new priv::AnnounceP(serviceType, name, port, txt, this)) { + } + + Announce::~Announce() { + delete m_priv; + m_priv = 0; + } + + bool Announce::start() { + return m_priv->start(); + } + + void Announce::stop() { + m_priv->stop(); + } + + QString Announce::serviceType() const { + return m_priv->m_serviceType; + } + + QString Announce::name() const { + return m_priv->m_name; + } + quint16 Announce::port() const { + return m_priv->m_port; + } + QHash Announce::txt() const { + return m_priv->m_txt; + } + QString Announce::txt(QString key) const { + return m_priv->m_txt.value(key); + } + +} diff --git a/libqtavahi/qtavahi.h b/libqtavahi/qtavahi.h new file mode 100644 index 0000000..84baab9 --- /dev/null +++ b/libqtavahi/qtavahi.h @@ -0,0 +1,186 @@ +#ifndef QTAVAHI_H +#define QTAVAHI_H + +#include + +#include "libqtavahi_global.h" + +namespace Avahi { + class LIBQTAVAHISHARED_EXPORT Service; + class LIBQTAVAHISHARED_EXPORT ServiceKey; + class LIBQTAVAHISHARED_EXPORT Browse; + class LIBQTAVAHISHARED_EXPORT Announce; +} + +/* we need this before #include so it finds our qHash function */ +inline uint qHash(const Avahi::ServiceKey& k); + +#include +#include +#include +#include +#include + +#include "qtavahi_p.h" + +namespace Avahi { + + class LIBQTAVAHISHARED_EXPORT Service { + public: + inline Service() : m_priv(0) { } + inline Service(QString name, QString hostname, QString domain, QHostAddress address, int interface, int protocol, quint16 port, QHash txt) + : m_priv(new Avahi::priv::ServiceP(name, hostname, domain, address, interface, protocol, port, txt)) { } + + inline Service(const Service &s) : m_priv(0) { assign(s.m_priv); } + inline Service &operator=(const Service &s) { assign(s.m_priv); return *this; } + + ~Service() { assign(0); } + + inline QString name() const { return m_priv ? m_priv->m_name : QString(); } + inline QString hostname() const { return m_priv ? m_priv->m_hostname : QString(); } + inline QString domain() const { return m_priv ? m_priv->m_domain : QString(); } + inline QHostAddress address() const { return m_priv ? m_priv->m_address : QHostAddress(); } + inline quint16 port() const { return m_priv ? m_priv->m_port : 0; } + inline QHash txt() const { return m_priv ? m_priv->m_txt : QHash(); } + inline QString txt(QString key) const { return m_priv ? m_priv->m_txt.value(key) : QString(); } + inline bool valid() const { return 0 != m_priv; } + + /* save custom data for a service (the service is probably associated with a Browse instance, so all + code using the same Browse instance uses the same private data here */ + inline void* data() const { return m_priv ? m_priv->m_data : 0; } + inline void setData(void *data) const { if (m_priv) m_priv->m_data = data; } + + private: + friend class Avahi::ServiceKey; + + inline void assign(Avahi::priv::ServiceP *p) { + if (p == m_priv) return; + if (p) p->ref(); + if (m_priv) m_priv->deref(); + m_priv = p; + } + + Avahi::priv::ServiceP *m_priv; + }; + + class LIBQTAVAHISHARED_EXPORT ServiceKey { + public: + inline ServiceKey(int interface, int protocol, const char *name, const char *domain) + : interface(interface), protocol(protocol), name(QString::fromUtf8(name)), domain(QString::fromUtf8(domain)) { + } + inline ServiceKey(int interface, int protocol, QString name, QString domain) + : interface(interface), protocol(protocol), name(name), domain(domain) { + } + inline ServiceKey(const Service &s) + : interface(-1), protocol(-1), name(), domain() { + if (s.m_priv) { + interface = s.m_priv->m_interface; + protocol = s.m_priv->m_protocol; + name = s.m_priv->m_name; + domain = s.m_priv->m_domain; + } + } + + int interface; /* AvahiIfIndex */ + int protocol; /* AvahiProtocol */ + QString name; + QString domain; + }; + + inline bool operator==(const ServiceKey &a, const ServiceKey &b) { + return a.interface == b.interface && a.protocol == b.protocol && a.name == b.name && a.domain == b.domain; + } + inline bool operator<(const ServiceKey &a, const ServiceKey &b) { + if (a.interface < b.interface) return true; + if (a.interface > b.interface) return false; + if (a.protocol < b.protocol) return true; + if (a.protocol > b.protocol) return false; + if (a.name < b.name) return true; + if (a.name > b.name) return false; + return a.domain < b.domain; + } + inline bool operator>(const ServiceKey &a, const ServiceKey &b) { return b < a; } + inline bool operator<=(const ServiceKey &a, const ServiceKey &b) { return !(b < a); } + inline bool operator>=(const ServiceKey &a, const ServiceKey &b) { return !(a < b); } + + + + class LIBQTAVAHISHARED_EXPORT Browse : public QObject { + Q_OBJECT + private: + Q_DISABLE_COPY(Browse); + + public: + typedef Avahi::priv::Protocol Protocol; + inline Browse(QString servtype, Protocol prot, QObject *parent = 0) { + Browse(Avahi::priv::serv_prot2str(servtype, prot), parent); + } + Browse(QString serviceType, QObject *parent = 0); + virtual ~Browse(); + + bool start(); + void stop(); + + QString serviceType() const; + + QList currentList() const; + + private: + void clearList(); + + signals: + void found(Avahi::Service i); + void lost(Avahi::Service i); + + void started(); + void stopped(); + + private: + Avahi::priv::BrowseP *m_priv; + + friend class Avahi::priv::BrowseP; + }; + + class LIBQTAVAHISHARED_EXPORT Announce : public QObject { + Q_OBJECT + private: + Q_DISABLE_COPY(Announce); + + public: + typedef Avahi::priv::Protocol Protocol; + inline Announce(QString servtype, Protocol prot, QString name, quint16 port, QHash txt, QObject *parent = 0) { + Announce(Avahi::priv::serv_prot2str(servtype, prot), name, port, txt, parent); + } + Announce(QString serviceType, QString name, quint16 port, QHash txt, QObject *parent = 0); + virtual ~Announce(); + + QString serviceType() const; + QString name() const; + quint16 port() const; + QHash txt() const; + QString txt(QString key) const; + + bool start(); + void stop(); + + signals: + void established(); /* the name may have been changed after collisions */ + void collision(); + void started(); + void stopped(); + + private: + Avahi::priv::AnnounceP *m_priv; + + friend class Avahi::priv::AnnounceP; + }; + +} + +QT_BEGIN_NAMESPACE +inline uint qHash(const Avahi::ServiceKey& k) { + return qHash(k.interface ^ k.protocol) ^ qHash(k.name) ^ qHash(k.domain); +} +QT_END_NAMESPACE + +#endif // QTAVAHI_H diff --git a/libqtavahi/qtavahi_p.h b/libqtavahi/qtavahi_p.h new file mode 100644 index 0000000..f0cf808 --- /dev/null +++ b/libqtavahi/qtavahi_p.h @@ -0,0 +1,50 @@ +#ifndef QTAVAHI_P_H +#define QTAVAHI_P_H + +namespace Avahi { + + namespace priv { + class LIBQTAVAHISHARED_EXPORT ServiceP; + class BrowseP; + class AnnounceP; + }; +} + +namespace Avahi { + namespace priv { + class LIBQTAVAHISHARED_EXPORT ServiceP { + private: + friend class Avahi::Service; + friend class Avahi::ServiceKey; + friend class Avahi::Browse; + inline ServiceP(QString name, QString hostname, QString domain, QHostAddress address, int interface, int protocol, quint16 port, QHash txt) + : m_ref(1), m_name(name), m_hostname(hostname), m_domain(domain), m_address(address), m_interface(interface), m_protocol(protocol), m_port(port), m_txt(txt), m_data(0) { } + + Q_DISABLE_COPY(ServiceP); + + inline void ref() { m_ref.ref(); } + inline void deref() { if (!m_ref.deref()) { delete this; } } + + QAtomicInt m_ref; + + QString m_name, m_hostname, m_domain; + QHostAddress m_address; + int m_interface, m_protocol; + quint16 m_port; + QHash m_txt; + + void* m_data; + }; + + typedef enum { TCP, UDP } Protocol; + inline LIBQTAVAHISHARED_EXPORT QString serv_prot2str(QString service, Protocol prot) { + switch (prot) { + case TCP: return QString("_%1._tcp").arg(service); break; + case UDP: return QString("_%1._udp").arg(service); break; + default: return service; + } + } + } +} + +#endif // QTAVAHI_P_H diff --git a/libqtavahi/qtavahimodel.cpp b/libqtavahi/qtavahimodel.cpp new file mode 100644 index 0000000..e2ecb8a --- /dev/null +++ b/libqtavahi/qtavahimodel.cpp @@ -0,0 +1,91 @@ +#include "qtavahimodel.h" + +namespace Avahi { + namespace model { + Row::~Row() { } + + int Row::columnCount() { return 0; } /* columns of child rows */ + Row* Row::index(int /* row */) { return 0; } + int Row::rowCount() { return 0; } + + Row* RowList::index(int row) { + return list.value(row); + } + int RowList::rowCount() { + return list.length(); + } + + QVariant Root::data(int , int ) { return QVariant(); } + } + + + BrowseModel::BrowseModel(Avahi::model::Root *root, QObject *parent) + : QAbstractItemModel(parent), m_root(root) { } + + BrowseModel::Row *BrowseModel::getRow(const QModelIndex &index) const { + if (index.isValid()) { + return static_cast< Row* >(index.internalPointer()); + } else { + return m_root; + } + } + + int BrowseModel::columnCount(const QModelIndex & parent) const { + Row *row = getRow(parent); + return row ? row->columnCount() : 0; + } + QVariant BrowseModel::data(const QModelIndex & index, int role) const { + Row *row = getRow(index); + return row ? row->data(index.column(), role) : QVariant(); + } + QModelIndex BrowseModel::index(int row, int column, const QModelIndex & parent) const { + Row *r = getRow(parent); + if (!r) return QModelIndex(); /* invalid */ + Row *child = r->index(row); + if (!child) return QModelIndex(); /* invalid */ + return createIndex(row, column, (void*) child); + } + QModelIndex BrowseModel::parent(const QModelIndex & index) const { + Row *row = getRow(index); + if (!row) return QModelIndex(); /* invalid */ + Row *p = row->parent(); + if (!p || p == m_root) return QModelIndex(); + return createIndex(0, 0, (void*) p); + } + int BrowseModel::rowCount(const QModelIndex & parent) const { + Row *row = getRow(parent); + return row ? row->rowCount() : 0; + } + + Avahi::ServiceKey BrowseModel::key(const Avahi::Service &s) { + Avahi::ServiceKey k(s); + k.interface = k.protocol = -1; + return k; + } + + void BrowseModel::insert(const Avahi::Service &s, void *priv) { + Avahi::ServiceKey k = key(s); + if (m_services.contains(k)) { + EntryList *el = m_services.value(k); + beginInsertRows(createIndex(0, 0, (void*) el), 0, 0); + el->list.push_front(el->newEntry(s, priv)); + endInsertRows(); + } else { + EntryList *el = m_root->newList(k); + QMap tmp(m_services); + tmp.insert(k, el); + int row = tmp.keys().indexOf(k); + beginInsertRows(QModelIndex(), row, row); + m_services = tmp; + m_root->list.insert(row, el); + endInsertRows(); + beginInsertRows(createIndex(0, 0, (void*) el), 0, 0); + el->list.push_front(el->newEntry(s, priv)); + endInsertRows(); + } + } + + void BrowseModel::remove(const Avahi::Service &) { + } + +} diff --git a/libqtavahi/qtavahimodel.h b/libqtavahi/qtavahimodel.h new file mode 100644 index 0000000..815eacf --- /dev/null +++ b/libqtavahi/qtavahimodel.h @@ -0,0 +1,115 @@ +#ifndef QTAVAHIMODEL_H +#define QTAVAHIMODEL_H + +#include "qtavahi.h" + +#include + +namespace Avahi { + class BrowseModel; + + namespace model { + class Row { + public: + Row(Row *parent) : m_parent(parent) { } + Row() : m_parent(0) { } + virtual ~Row(); + + virtual int columnCount(); /* columns of child rows */ + virtual QVariant data(int col, int role) = 0; + virtual Row* index(int row); + virtual int rowCount(); + + Row *parent() { return m_parent; } + + protected: + Row *m_parent; + }; + + class RowList : public Row { + public: + typedef QList< Row* > List; + + RowList(Row *parent) : Row(parent) { } + RowList() : Row() { } + + virtual int columnCount() = 0; /* columns of child rows */ + virtual Row* index(int row); + virtual int rowCount(); + + protected: + friend class Avahi::BrowseModel; + + List list; + }; + + class Entry : public Row { + public: + Entry(Row *parent, const Avahi::Service &s) : Row(parent), service(s) { } + + protected: + friend class Avahi::BrowseModel; + + Avahi::Service service; + }; + + class EntryList : public RowList { + public: + EntryList(Row *parent, const Avahi::ServiceKey &k) : RowList(parent), key(k) { } + + protected: + friend class Avahi::BrowseModel; + + virtual Entry* newEntry(const Avahi::Service &s, void *priv) = 0; + + Avahi::ServiceKey key; + }; + + class Root : public RowList { + public: + virtual QVariant data(int , int ); + + protected: + friend class Avahi::BrowseModel; + + virtual EntryList* newList(const Avahi::ServiceKey &k) = 0; + }; + } + + + class BrowseModel : public QAbstractItemModel { + Q_OBJECT + + protected: + typedef Avahi::model::Row Row; + typedef Avahi::model::RowList RowList; + typedef Avahi::model::Entry Entry; + typedef Avahi::model::EntryList EntryList; + + Avahi::model::Root *m_root; + + BrowseModel(Avahi::model::Root *root, QObject *parent = 0); + + Row *getRow(const QModelIndex &index) const; + + public: + int columnCount(const QModelIndex & parent) const; + QVariant data(const QModelIndex & index, int role) const; + QModelIndex index(int row, int column, const QModelIndex & parent) const; + QModelIndex parent(const QModelIndex & index) const; + int rowCount(const QModelIndex & parent) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const = 0; + + protected: + virtual Avahi::ServiceKey key(const Avahi::Service &s); + + void insert(const Avahi::Service &s, void *priv); + void remove(const Avahi::Service &s); + + private: + Avahi::Browse *m_browse; + QMap m_services; + }; +} + +#endif // QCPMODEL_H diff --git a/qcp.pro b/qcp.pro new file mode 100644 index 0000000..7f405c7 --- /dev/null +++ b/qcp.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS = libqtavahi libqcp qcp diff --git a/qcp/libqcp.so.1 b/qcp/libqcp.so.1 new file mode 120000 index 0000000..76db813 --- /dev/null +++ b/qcp/libqcp.so.1 @@ -0,0 +1 @@ +../libqcp/libqcp.so.1 \ No newline at end of file diff --git a/qcp/libqtavahi.so.1 b/qcp/libqtavahi.so.1 new file mode 120000 index 0000000..44cb5f7 --- /dev/null +++ b/qcp/libqtavahi.so.1 @@ -0,0 +1 @@ +../libqtavahi/libqtavahi.so.1 \ No newline at end of file diff --git a/qcp/main.cpp b/qcp/main.cpp new file mode 100644 index 0000000..7007341 --- /dev/null +++ b/qcp/main.cpp @@ -0,0 +1,9 @@ +#include +#include "mainwindow.h" + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/qcp/mainwindow.cpp b/qcp/mainwindow.cpp new file mode 100644 index 0000000..90b2606 --- /dev/null +++ b/qcp/mainwindow.cpp @@ -0,0 +1,26 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) { + ui->setupUi(this); + + m_browsemodel = new QCP::BrowseModel(this); + ui->viewShares->setModel(m_browsemodel); +} + +MainWindow::~MainWindow() { + delete ui; +} + +void MainWindow::changeEvent(QEvent *e) { + QMainWindow::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} diff --git a/qcp/mainwindow.h b/qcp/mainwindow.h new file mode 100644 index 0000000..47b8331 --- /dev/null +++ b/qcp/mainwindow.h @@ -0,0 +1,27 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +#include "../libqcp/qcp.h" +#include "../libqcp/qcpmodel.h" + +namespace Ui { + class MainWindow; +} + +class MainWindow : public QMainWindow { + Q_OBJECT +public: + MainWindow(QWidget *parent = 0); + ~MainWindow(); + +protected: + void changeEvent(QEvent *e); + +private: + Ui::MainWindow *ui; + QCP::BrowseModel *m_browsemodel; +}; + +#endif // MAINWINDOW_H diff --git a/qcp/mainwindow.ui b/qcp/mainwindow.ui new file mode 100644 index 0000000..90338ad --- /dev/null +++ b/qcp/mainwindow.ui @@ -0,0 +1,55 @@ + + + MainWindow + + + + 0 + 0 + 600 + 400 + + + + MainWindow + + + + + + + Shares + + + + + + + + + + + + + + 0 + 0 + 600 + 23 + + + + + + TopToolBarArea + + + false + + + + + + + + diff --git a/qcp/qcp.pro b/qcp/qcp.pro new file mode 100644 index 0000000..48e8189 --- /dev/null +++ b/qcp/qcp.pro @@ -0,0 +1,21 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2010-07-24T12:24:31 +# +#------------------------------------------------- + +QT += network + +TARGET = qcp +TEMPLATE = app + + +SOURCES += main.cpp\ + mainwindow.cpp + +HEADERS += mainwindow.h + +FORMS += mainwindow.ui + +TARGETDEPS += ../libqcp/libqcp.so ../libqtavahi/libqtavahi.so +LIBS += ../libqcp/libqcp.so ../libqtavahi/libqtavahi.so