| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtNetwork module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qnetworkconfigmanager_p.h" |
| #include "qbearerplugin_p.h" |
| |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qtimer.h> |
| #include <QtCore/qstringlist.h> |
| #include <QtCore/qthread.h> |
| #include <QtCore/private/qcoreapplication_p.h> |
| #include <QtCore/private/qlocking_p.h> |
| #include <QtCore/private/qthread_p.h> |
| |
| #include <QtCore/qbytearray.h> |
| #include <QtCore/qglobal.h> |
| |
| #include <utility> |
| |
| |
| #ifndef QT_NO_BEARERMANAGEMENT |
| |
| QT_BEGIN_NAMESPACE |
| |
| QNetworkConfigurationManagerPrivate::QNetworkConfigurationManagerPrivate() |
| : QObject(), pollTimer(nullptr), |
| loader(QBearerEngineFactoryInterface_iid, QLatin1String("/bearer")), |
| forcedPolling(0), firstUpdate(true) |
| { |
| qRegisterMetaType<QNetworkConfiguration>(); |
| qRegisterMetaType<QNetworkConfigurationPrivatePointer>(); |
| } |
| |
| void QNetworkConfigurationManagerPrivate::initialize() |
| { |
| //Two stage construction, because we only want to do this heavyweight work for the winner of the Q_GLOBAL_STATIC race. |
| bearerThread = new QDaemonThread(); |
| bearerThread->setObjectName(QStringLiteral("Qt bearer thread")); |
| |
| bearerThread->moveToThread(QCoreApplicationPrivate::mainThread()); // because cleanup() is called in main thread context. |
| moveToThread(bearerThread); |
| bearerThread->start(); |
| updateConfigurations(); |
| } |
| |
| QNetworkConfigurationManagerPrivate::~QNetworkConfigurationManagerPrivate() |
| { |
| QMutexLocker locker(&mutex); |
| |
| qDeleteAll(sessionEngines); |
| sessionEngines.clear(); |
| if (bearerThread) |
| bearerThread->quit(); |
| } |
| |
| void QNetworkConfigurationManagerPrivate::cleanup() |
| { |
| QThread* thread = bearerThread; |
| deleteLater(); |
| if (thread->wait(QDeadlineTimer(5000))) |
| delete thread; |
| } |
| |
| QNetworkConfiguration QNetworkConfigurationManagerPrivate::defaultConfiguration() const |
| { |
| QMutexLocker locker(&mutex); |
| |
| for (QBearerEngine *engine : sessionEngines) { |
| QNetworkConfigurationPrivatePointer ptr = engine->defaultConfiguration(); |
| if (ptr) { |
| QNetworkConfiguration config; |
| config.d = ptr; |
| return config; |
| } |
| } |
| |
| // Engines don't have a default configuration. |
| |
| // Return first active snap |
| QNetworkConfigurationPrivatePointer defaultConfiguration; |
| |
| for (QBearerEngine *engine : sessionEngines) { |
| const auto locker = qt_scoped_lock(engine->mutex); |
| |
| for (const auto &ptr : qAsConst(engine->snapConfigurations)) { |
| const auto locker = qt_scoped_lock(ptr->mutex); |
| |
| if ((ptr->state & QNetworkConfiguration::Active) == QNetworkConfiguration::Active) { |
| QNetworkConfiguration config; |
| config.d = ptr; |
| return config; |
| } else if (!defaultConfiguration) { |
| if ((ptr->state & QNetworkConfiguration::Discovered) == QNetworkConfiguration::Discovered) |
| defaultConfiguration = ptr; |
| } |
| } |
| } |
| |
| // No Active SNAPs return first Discovered SNAP. |
| if (defaultConfiguration) { |
| QNetworkConfiguration config; |
| config.d = defaultConfiguration; |
| return config; |
| } |
| |
| /* |
| No Active or Discovered SNAPs, find the perferred access point. |
| The following priority order is used: |
| |
| 1. Active Ethernet |
| 2. Active WLAN |
| 3. Active Other |
| 4. Discovered Ethernet |
| 5. Discovered WLAN |
| 6. Discovered Other |
| */ |
| |
| for (QBearerEngine *engine : sessionEngines) { |
| |
| QMutexLocker locker(&engine->mutex); |
| |
| for (const auto &ptr : qAsConst(engine->accessPointConfigurations)) { |
| |
| QMutexLocker configLocker(&ptr->mutex); |
| QNetworkConfiguration::BearerType bearerType = ptr->bearerType; |
| |
| if ((ptr->state & QNetworkConfiguration::Discovered) == QNetworkConfiguration::Discovered) { |
| if (!defaultConfiguration) { |
| defaultConfiguration = ptr; |
| } else { |
| QMutexLocker defaultConfigLocker(&defaultConfiguration->mutex); |
| |
| if (defaultConfiguration->state == ptr->state) { |
| switch (defaultConfiguration->bearerType) { |
| case QNetworkConfiguration::BearerEthernet: |
| // do nothing |
| break; |
| case QNetworkConfiguration::BearerWLAN: |
| // Ethernet beats WLAN |
| defaultConfiguration = ptr; |
| break; |
| default: |
| // Ethernet and WLAN beats other |
| if (bearerType == QNetworkConfiguration::BearerEthernet || |
| bearerType == QNetworkConfiguration::BearerWLAN) { |
| defaultConfiguration = ptr; |
| } |
| } |
| } else { |
| // active beats discovered |
| if ((defaultConfiguration->state & QNetworkConfiguration::Active) != |
| QNetworkConfiguration::Active) { |
| defaultConfiguration = ptr; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // No Active InternetAccessPoint return first Discovered InternetAccessPoint. |
| if (defaultConfiguration) { |
| QNetworkConfiguration config; |
| config.d = defaultConfiguration; |
| return config; |
| } |
| |
| return QNetworkConfiguration(); |
| } |
| |
| QList<QNetworkConfiguration> QNetworkConfigurationManagerPrivate::allConfigurations(QNetworkConfiguration::StateFlags filter) const |
| { |
| QList<QNetworkConfiguration> result; |
| |
| QMutexLocker locker(&mutex); |
| |
| for (QBearerEngine *engine : sessionEngines) { |
| |
| const auto locker = qt_scoped_lock(engine->mutex); |
| |
| //find all InternetAccessPoints |
| for (const auto &ptr : qAsConst(engine->accessPointConfigurations)) { |
| const auto locker = qt_scoped_lock(ptr->mutex); |
| |
| if ((ptr->state & filter) == filter) { |
| QNetworkConfiguration pt; |
| pt.d = ptr; |
| result << pt; |
| } |
| } |
| |
| //find all service networks |
| for (const auto &ptr : qAsConst(engine->snapConfigurations)) { |
| const auto locker = qt_scoped_lock(ptr->mutex); |
| |
| if ((ptr->state & filter) == filter) { |
| QNetworkConfiguration pt; |
| pt.d = ptr; |
| result << pt; |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| QNetworkConfiguration QNetworkConfigurationManagerPrivate::configurationFromIdentifier(const QString &identifier) const |
| { |
| QNetworkConfiguration item; |
| |
| const auto locker = qt_scoped_lock(mutex); |
| |
| for (QBearerEngine *engine : sessionEngines) { |
| const auto locker = qt_scoped_lock(engine->mutex); |
| if (auto ptr = engine->accessPointConfigurations.value(identifier)) { |
| item.d = std::move(ptr); |
| break; |
| } |
| if (auto ptr = engine->snapConfigurations.value(identifier)) { |
| item.d = std::move(ptr); |
| break; |
| } |
| if (auto ptr = engine->userChoiceConfigurations.value(identifier)) { |
| item.d = std::move(ptr); |
| break; |
| } |
| } |
| |
| return item; |
| } |
| |
| bool QNetworkConfigurationManagerPrivate::isOnline() const |
| { |
| const auto locker = qt_scoped_lock(mutex); |
| |
| // We need allConfigurations since onlineConfigurations is filled with queued connections |
| // and thus is not always (more importantly just after creation) up to date |
| return !allConfigurations(QNetworkConfiguration::Active).isEmpty(); |
| } |
| |
| QNetworkConfigurationManager::Capabilities QNetworkConfigurationManagerPrivate::capabilities() const |
| { |
| const auto locker = qt_scoped_lock(mutex); |
| |
| QNetworkConfigurationManager::Capabilities capFlags; |
| |
| for (QBearerEngine *engine : sessionEngines) |
| capFlags |= engine->capabilities(); |
| |
| return capFlags; |
| } |
| |
| void QNetworkConfigurationManagerPrivate::configurationAdded(QNetworkConfigurationPrivatePointer ptr) |
| { |
| const auto locker = qt_scoped_lock(mutex); |
| |
| if (!firstUpdate) { |
| QNetworkConfiguration item; |
| item.d = ptr; |
| emit configurationAdded(item); |
| } |
| |
| auto ptrLocker = qt_unique_lock(ptr->mutex); |
| if (ptr->state == QNetworkConfiguration::Active) { |
| const auto id = ptr->id; |
| ptrLocker.unlock(); |
| onlineConfigurations.insert(id); |
| if (!firstUpdate && onlineConfigurations.count() == 1) |
| emit onlineStateChanged(true); |
| } |
| } |
| |
| void QNetworkConfigurationManagerPrivate::configurationRemoved(QNetworkConfigurationPrivatePointer ptr) |
| { |
| const auto locker = qt_scoped_lock(mutex); |
| |
| { |
| const auto locker = qt_scoped_lock(ptr->mutex); |
| ptr->isValid = false; |
| } |
| |
| if (!firstUpdate) { |
| QNetworkConfiguration item; |
| item.d = ptr; |
| emit configurationRemoved(item); |
| } |
| |
| onlineConfigurations.remove(ptr->id); |
| if (!firstUpdate && onlineConfigurations.isEmpty()) |
| emit onlineStateChanged(false); |
| } |
| |
| void QNetworkConfigurationManagerPrivate::configurationChanged(QNetworkConfigurationPrivatePointer ptr) |
| { |
| const auto locker = qt_scoped_lock(mutex); |
| |
| if (!firstUpdate) { |
| QNetworkConfiguration item; |
| item.d = ptr; |
| emit configurationChanged(item); |
| } |
| |
| bool previous = !onlineConfigurations.isEmpty(); |
| |
| { |
| const auto locker = qt_scoped_lock(ptr->mutex); |
| if (ptr->state == QNetworkConfiguration::Active) |
| onlineConfigurations.insert(ptr->id); |
| else |
| onlineConfigurations.remove(ptr->id); |
| } |
| |
| bool online = !onlineConfigurations.isEmpty(); |
| |
| if (!firstUpdate && online != previous) |
| emit onlineStateChanged(online); |
| } |
| |
| void QNetworkConfigurationManagerPrivate::updateConfigurations() |
| { |
| typedef QMultiMap<int, QString> PluginKeyMap; |
| typedef PluginKeyMap::const_iterator PluginKeyMapConstIterator; |
| |
| auto locker = qt_unique_lock(mutex); |
| |
| if (firstUpdate) { |
| if (qobject_cast<QBearerEngine *>(sender())) |
| return; |
| |
| updating = false; |
| |
| bool envOK = false; |
| const int skipGeneric = qEnvironmentVariableIntValue("QT_EXCLUDE_GENERIC_BEARER", &envOK); |
| QBearerEngine *generic = nullptr; |
| QFactoryLoader *l = &loader; |
| const PluginKeyMap keyMap = l->keyMap(); |
| const PluginKeyMapConstIterator cend = keyMap.constEnd(); |
| QStringList addedEngines; |
| for (PluginKeyMapConstIterator it = keyMap.constBegin(); it != cend; ++it) { |
| const QString &key = it.value(); |
| if (addedEngines.contains(key)) |
| continue; |
| |
| addedEngines.append(key); |
| if (QBearerEngine *engine = qLoadPlugin<QBearerEngine, QBearerEnginePlugin>(l, key)) { |
| if (key == QLatin1String("generic")) |
| generic = engine; |
| else |
| sessionEngines.append(engine); |
| |
| engine->moveToThread(bearerThread); |
| |
| connect(engine, SIGNAL(updateCompleted()), |
| this, SLOT(updateConfigurations()), |
| Qt::QueuedConnection); |
| connect(engine, SIGNAL(configurationAdded(QNetworkConfigurationPrivatePointer)), |
| this, SLOT(configurationAdded(QNetworkConfigurationPrivatePointer)), |
| Qt::QueuedConnection); |
| connect(engine, SIGNAL(configurationRemoved(QNetworkConfigurationPrivatePointer)), |
| this, SLOT(configurationRemoved(QNetworkConfigurationPrivatePointer)), |
| Qt::QueuedConnection); |
| connect(engine, SIGNAL(configurationChanged(QNetworkConfigurationPrivatePointer)), |
| this, SLOT(configurationChanged(QNetworkConfigurationPrivatePointer)), |
| Qt::QueuedConnection); |
| } |
| } |
| |
| if (generic) { |
| if (!envOK || skipGeneric <= 0) |
| sessionEngines.append(generic); |
| else |
| delete generic; |
| } |
| } |
| |
| QBearerEngine *engine = qobject_cast<QBearerEngine *>(sender()); |
| if (engine && !updatingEngines.isEmpty()) |
| updatingEngines.remove(engine); |
| |
| if (updating && updatingEngines.isEmpty()) { |
| updating = false; |
| emit configurationUpdateComplete(); |
| } |
| |
| if (engine && !pollingEngines.isEmpty()) { |
| pollingEngines.remove(engine); |
| if (pollingEngines.isEmpty()) |
| startPolling(); |
| } |
| |
| if (firstUpdate) { |
| firstUpdate = false; |
| const QList<QBearerEngine*> enginesToInitialize = sessionEngines; //shallow copy the list in case it is modified when we unlock mutex |
| locker.unlock(); |
| for (QBearerEngine* engine : enginesToInitialize) |
| QMetaObject::invokeMethod(engine, "initialize", Qt::BlockingQueuedConnection); |
| } |
| } |
| |
| void QNetworkConfigurationManagerPrivate::performAsyncConfigurationUpdate() |
| { |
| const auto locker = qt_scoped_lock(mutex); |
| |
| if (sessionEngines.isEmpty()) { |
| emit configurationUpdateComplete(); |
| return; |
| } |
| |
| updating = true; |
| |
| for (QBearerEngine *engine : qAsConst(sessionEngines)) { |
| updatingEngines.insert(engine); |
| QMetaObject::invokeMethod(engine, "requestUpdate"); |
| } |
| } |
| |
| QList<QBearerEngine *> QNetworkConfigurationManagerPrivate::engines() const |
| { |
| const auto locker = qt_scoped_lock(mutex); |
| |
| return sessionEngines; |
| } |
| |
| void QNetworkConfigurationManagerPrivate::startPolling() |
| { |
| const auto locker = qt_scoped_lock(mutex); |
| if (!pollTimer) { |
| pollTimer = new QTimer(this); |
| bool ok; |
| int interval = qEnvironmentVariableIntValue("QT_BEARER_POLL_TIMEOUT", &ok); |
| if (!ok) |
| interval = 10000;//default 10 seconds |
| pollTimer->setInterval(interval); |
| pollTimer->setSingleShot(true); |
| connect(pollTimer, SIGNAL(timeout()), this, SLOT(pollEngines())); |
| } |
| |
| if (pollTimer->isActive()) |
| return; |
| |
| for (QBearerEngine *engine : qAsConst(sessionEngines)) { |
| if (engine->requiresPolling() && (forcedPolling || engine->configurationsInUse())) { |
| pollTimer->start(); |
| break; |
| } |
| } |
| performAsyncConfigurationUpdate(); |
| } |
| |
| void QNetworkConfigurationManagerPrivate::pollEngines() |
| { |
| const auto locker = qt_scoped_lock(mutex); |
| |
| for (QBearerEngine *engine : qAsConst(sessionEngines)) { |
| if (engine->requiresPolling() && (forcedPolling || engine->configurationsInUse())) { |
| pollingEngines.insert(engine); |
| QMetaObject::invokeMethod(engine, "requestUpdate"); |
| } |
| } |
| } |
| |
| void QNetworkConfigurationManagerPrivate::enablePolling() |
| { |
| const auto locker = qt_scoped_lock(mutex); |
| |
| ++forcedPolling; |
| |
| if (forcedPolling == 1) |
| QMetaObject::invokeMethod(this, "startPolling"); |
| } |
| |
| void QNetworkConfigurationManagerPrivate::disablePolling() |
| { |
| const auto locker = qt_scoped_lock(mutex); |
| |
| --forcedPolling; |
| } |
| |
| QT_END_NAMESPACE |
| |
| #endif // QT_NO_BEARERMANAGEMENT |