/* * Copyright (C) 2006 Justin Karneges * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "netinterface.h" #include "irisnetplugin.h" #include "irisnetglobal_p.h" namespace XMPP { //---------------------------------------------------------------------------- // NetTracker //---------------------------------------------------------------------------- class NetTracker : public QObject { Q_OBJECT public: static NetTracker *self; NetInterfaceProvider *c; QList info; QMutex m; NetTracker() { self = this; QList list = irisNetProviders(); c = 0; for(int n = 0; n < list.count(); ++n) { IrisNetProvider *p = list[n]; c = p->createNetInterfaceProvider(); if(c) break; } Q_ASSERT(c); // we have built-in support, so this should never fail connect(c, SIGNAL(updated()), SLOT(c_updated())); } ~NetTracker() { delete c; self = 0; } static NetTracker *instance() { return self; } void start() { c->start(); info = filterList(c->interfaces()); } QList getInterfaces() { QMutexLocker locker(&m); return info; } signals: void updated(); public slots: void c_updated() { { QMutexLocker locker(&m); info = filterList(c->interfaces()); } emit updated(); } private: static QList filterList(const QList &in) { QList out; for(int n = 0; n < in.count(); ++n) { if(!in[n].isLoopback) out += in[n]; } return out; } }; NetTracker *NetTracker::self = 0; class SyncThread : public QThread { Q_OBJECT public: QMutex control_mutex; QWaitCondition control_wait; QEventLoop *loop; SyncThread(QObject *parent = 0) : QThread(parent) { loop = 0; } ~SyncThread() { stop(); } void start() { control_mutex.lock(); QThread::start(); control_wait.wait(&control_mutex); control_mutex.unlock(); } void stop() { { QMutexLocker locker(&control_mutex); if(loop) QMetaObject::invokeMethod(loop, "quit"); } wait(); } virtual void run() { control_mutex.lock(); loop = new QEventLoop; begin(); control_wait.wakeOne(); control_mutex.unlock(); loop->exec(); QMutexLocker locker(&control_mutex); end(); delete loop; loop = 0; } virtual void begin() { } virtual void end() { } }; class NetThread : public SyncThread { Q_OBJECT public: NetTracker *tracker; NetThread(QObject *parent = 0) : SyncThread(parent) { } ~NetThread() { stop(); } virtual void begin() { tracker = new NetTracker; tracker->start(); } virtual void end() { delete tracker; } }; //---------------------------------------------------------------------------- // NetInterface //---------------------------------------------------------------------------- class NetInterfacePrivate : public QObject { Q_OBJECT public: friend class NetInterfaceManagerPrivate; NetInterface *q; NetInterfaceManager *man; bool valid; QString id, name; QList addrs; QHostAddress gw; NetInterfacePrivate(NetInterface *_q) : QObject(_q), q(_q) { valid = false; } void doUnavailable() { man->unreg(q); valid = false; emit q->unavailable(); } }; NetInterface::NetInterface(const QString &id, NetInterfaceManager *manager) :QObject(manager) { d = new NetInterfacePrivate(this); d->man = manager; NetInterfaceProvider::Info *info = (NetInterfaceProvider::Info *)d->man->reg(id, this); if(info) { d->valid = true; d->id = info->id; d->name = info->name; d->addrs = info->addresses; d->gw = info->gateway; delete info; } } NetInterface::~NetInterface() { d->man->unreg(this); delete d; } bool NetInterface::isValid() const { return d->valid; } QString NetInterface::id() const { return d->id; } QString NetInterface::name() const { return d->name; } QList NetInterface::addresses() const { return d->addrs; } QHostAddress NetInterface::gateway() const { return d->gw; } //---------------------------------------------------------------------------- // NetInterfaceManager //---------------------------------------------------------------------------- class NetInterfaceManagerGlobal; Q_GLOBAL_STATIC(QMutex, nim_mutex) static NetInterfaceManagerGlobal *g_nim = 0; class NetInterfaceManagerGlobal { public: NetThread *thread; int refs; NetInterfaceManagerGlobal() { thread = 0; refs = 0; } ~NetInterfaceManagerGlobal() { } // global mutex must be locked while calling this void addRef() { if(refs == 0) { thread = new NetThread; thread->moveToThread(QCoreApplication::instance()->thread()); thread->start(); } ++refs; } // global mutex must be locked while calling this void removeRef() { Q_ASSERT(refs > 0); --refs; if(refs == 0) { delete thread; thread = 0; } } }; class NetInterfaceManagerPrivate : public QObject { Q_OBJECT public: NetInterfaceManager *q; QMutex m; QList info; QList listeners; bool pending; NetInterfaceManagerPrivate(NetInterfaceManager *_q) : q(_q) { pending = false; } static int lookup(const QList &list, const QString &id) { for(int n = 0; n < list.count(); ++n) { if(list[n].id == id) return n; } return -1; } static bool sameContent(const NetInterfaceProvider::Info &a, const NetInterfaceProvider::Info &b) { // assume ids are the same already if(a.name == b.name && a.isLoopback == b.isLoopback && a.addresses == b.addresses && a.gateway == b.gateway) return true; return false; } void do_update() { // grab the latest info QList newinfo = NetTracker::instance()->getInterfaces(); QStringList here_ids, gone_ids; // removed / changed for(int n = 0; n < info.count(); ++n) { int i = lookup(newinfo, info[n].id); // id is still here if(i != -1) { // content changed? if(!sameContent(info[n], newinfo[i])) { gone_ids += info[n].id; here_ids += info[n].id; } } // id is gone else gone_ids += info[n].id; } // added for(int n = 0; n < newinfo.count(); ++n) { int i = lookup(info, newinfo[n].id); if(i == -1) here_ids += newinfo[n].id; } info = newinfo; // announce gone for(int n = 0; n < gone_ids.count(); ++n) { // work on a copy, just in case the list changes. // it is important to make the copy here, and not // outside the outer loop, in case the items // get deleted QList list = listeners; for(int i = 0; i < list.count(); ++i) { if(list[i]->d->id == gone_ids[n]) list[i]->d->doUnavailable(); } } // announce here for(int n = 0; n < here_ids.count(); ++n) emit q->interfaceAvailable(here_ids[n]); } public slots: void tracker_updated() { QMutexLocker locker(&m); if(!pending) { QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); pending = true; } } void update() { m.lock(); pending = false; m.unlock(); do_update(); } }; NetInterfaceManager::NetInterfaceManager(QObject *parent) :QObject(parent) { QMutexLocker locker(nim_mutex()); if(!g_nim) g_nim = new NetInterfaceManagerGlobal; d = new NetInterfaceManagerPrivate(this); g_nim->addRef(); d->connect(NetTracker::instance(), SIGNAL(updated()), SLOT(tracker_updated()), Qt::DirectConnection); } NetInterfaceManager::~NetInterfaceManager() { QMutexLocker locker(nim_mutex()); g_nim->removeRef(); delete d; if(g_nim->refs == 0) { delete g_nim; g_nim = 0; } } QStringList NetInterfaceManager::interfaces() const { d->info = NetTracker::instance()->getInterfaces(); QStringList out; for(int n = 0; n < d->info.count(); ++n) out += d->info[n].id; return out; } QString NetInterfaceManager::interfaceForAddress(const QHostAddress &a) { NetInterfaceManager netman; QStringList list = netman.interfaces(); for(int n = 0; n < list.count(); ++n) { NetInterface iface(list[n], &netman); if(iface.addresses().contains(a)) return list[n]; } return QString(); } void *NetInterfaceManager::reg(const QString &id, NetInterface *i) { for(int n = 0; n < d->info.count(); ++n) { if(d->info[n].id == id) { d->listeners += i; return new NetInterfaceProvider::Info(d->info[n]); } } return 0; } void NetInterfaceManager::unreg(NetInterface *i) { d->listeners.removeAll(i); } } #include "netinterface.moc"