File indexing completed on 2024-11-24 04:53:14

0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
0002 
0003    This file is part of the Trojita Qt IMAP e-mail client,
0004    http://trojita.flaska.net/
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public License as
0008    published by the Free Software Foundation; either version 2 of
0009    the License or (at your option) version 3 or any later version
0010    accepted by the membership of KDE e.V. (or its successor approved
0011    by the membership of KDE e.V.), which shall act as a proxy
0012    defined in Section 14 of version 3 of the license.
0013 
0014    This program is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017    GNU General Public License for more details.
0018 
0019    You should have received a copy of the GNU General Public License
0020    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include "SystemNetworkWatcher.h"
0024 #include <QNetworkConfigurationManager>
0025 #include <QNetworkSession>
0026 #include "Model.h"
0027 
0028 namespace Imap {
0029 namespace Mailbox {
0030 
0031 QString policyToString(const NetworkPolicy policy)
0032 {
0033     switch (policy) {
0034     case NETWORK_OFFLINE:
0035         return QStringLiteral("NETWORK_OFFLINE");
0036     case NETWORK_EXPENSIVE:
0037         return QStringLiteral("NETWORK_EXPENSIVE");
0038     case NETWORK_ONLINE:
0039         return QStringLiteral("NETWORK_ONLINE");
0040     }
0041     Q_ASSERT(false);
0042     return QString();
0043 }
0044 
0045 SystemNetworkWatcher::SystemNetworkWatcher(ImapAccess *parent, Model *model):
0046     NetworkWatcher(parent, model), m_netConfManager(0), m_session(0)
0047 {
0048     m_netConfManager = new QNetworkConfigurationManager(this);
0049     resetSession();
0050     connect(m_netConfManager, &QNetworkConfigurationManager::onlineStateChanged, this, &SystemNetworkWatcher::onGlobalOnlineStateChanged);
0051     connect(m_netConfManager, &QNetworkConfigurationManager::configurationChanged, this, &SystemNetworkWatcher::networkConfigurationChanged);
0052 }
0053 
0054 void SystemNetworkWatcher::onGlobalOnlineStateChanged(const bool online)
0055 {
0056     if (online) {
0057         m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"), QStringLiteral("System is back online"));
0058         auto currentConfig = sessionsActiveConfiguration();
0059         if (!currentConfig.isValid() && currentConfig != m_netConfManager->defaultConfiguration()) {
0060             m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"),
0061                               QStringLiteral("A new network configuration is available"));
0062             networkConfigurationChanged(m_netConfManager->defaultConfiguration());
0063         }
0064         setDesiredNetworkPolicy(m_desiredPolicy);
0065     } else {
0066         m_model->setNetworkPolicy(NETWORK_OFFLINE);
0067         // The session remains open, so that we indicate our intention to reconnect after the connectivity is restored
0068         // (or when a configured AP comes back to range, etc).
0069     }
0070 }
0071 
0072 void SystemNetworkWatcher::setDesiredNetworkPolicy(const NetworkPolicy policy)
0073 {
0074     if (policy != m_desiredPolicy) {
0075         m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"),
0076                           QStringLiteral("User's preference changed: %1").arg(policyToString(policy)));
0077         m_desiredPolicy = policy;
0078     }
0079     if (m_model->networkPolicy() == NETWORK_OFFLINE && policy != NETWORK_OFFLINE) {
0080         // We are asked to connect, the model is not connected yet
0081         if (isOnline()) {
0082             if (m_netConfManager->allConfigurations().isEmpty()) {
0083                 m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"),
0084                                   // Yes, this is quite deliberate call to tr(). We absolutely want users to be able to read this
0085                                   // (but no so much as to bother them with a popup for now, I guess -- or not?)
0086                                   tr("Qt does not recognize any network session. Please be sure that qtbearer package "
0087                                      "(or similar) is installed. Assuming that network is actually already available."));
0088             }
0089 
0090             m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"), QStringLiteral("Network is online -> connecting"));
0091             reconnectModelNetwork();
0092         } else {
0093             // Chances are that our previously valid session is not valid anymore
0094             resetSession();
0095             // We aren't online yet, but we will become online at some point. When that happens, reconnectModelNetwork() will
0096             // be called, so there is nothing to do from this place.
0097             m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"), QStringLiteral("Opening network session"));
0098             m_session->open();
0099         }
0100     } else if (m_model->networkPolicy() != NETWORK_OFFLINE && policy == NETWORK_OFFLINE) {
0101         // This is pretty easy -- just disconnect the model
0102         m_model->setNetworkPolicy(NETWORK_OFFLINE);
0103         m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"), QStringLiteral("Closing network session"));
0104         m_session->close();
0105     } else {
0106         // No need to connect/disconnect/reconnect, yay!
0107         m_model->setNetworkPolicy(m_desiredPolicy);
0108     }
0109 }
0110 
0111 void SystemNetworkWatcher::reconnectModelNetwork()
0112 {
0113     m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"),
0114                       QStringLiteral("Session is %1 (configuration %2), online state: %3").arg(
0115                           QLatin1String(m_session->isOpen() ? "open" : "not open"),
0116                           m_session->configuration().name(),
0117                           QLatin1String(m_netConfManager->isOnline() ? "online" : "offline")
0118                           ));
0119     if (!isOnline()) {
0120         m_model->setNetworkPolicy(NETWORK_OFFLINE);
0121         return;
0122     }
0123 
0124     m_model->setNetworkPolicy(m_desiredPolicy);
0125 }
0126 
0127 bool SystemNetworkWatcher::isOnline() const
0128 {
0129     if (m_netConfManager->allConfigurations().isEmpty()) {
0130         // This means that Qt has no idea about the network. Let's pretend that we are connected.
0131         // This happens e.g. when users do not have the qt-bearer package installed.
0132         return true;
0133     }
0134 
0135     return m_netConfManager->isOnline() && m_session->isOpen();
0136 }
0137 
0138 /** @short Some of the configuration profiles have changed
0139 
0140 This can be some completely harmelss change, like user editting an inactive profile of some random WiFi network.
0141 Unfortunately, this is also the only method in which the Qt's NetworkManager plugin informs us about a switch
0142 from eth0 to wlan0.
0143 
0144 There's apparently no better signal, see http://lists.qt-project.org/pipermail/interest/2013-December/010374.html
0145 
0146 */
0147 void SystemNetworkWatcher::networkConfigurationChanged(const QNetworkConfiguration &conf)
0148 {
0149     bool reconnect = false;
0150 
0151     if (conf == sessionsActiveConfiguration() && !conf.state().testFlag(QNetworkConfiguration::Active) &&
0152             conf != m_netConfManager->defaultConfiguration() && m_netConfManager->defaultConfiguration().isValid()) {
0153         // Change of the "session's own configuration" which is not a default config of the system (anymore?), and the new default
0154         // is something valid.
0155         // I'm seeing (Qt 5.5-git, Linux, NetworkManager,...) quite a few of these as false positives on a random hotel WiFi.
0156         // Let's prevent a ton of useless reconnects here by only handling this if the system now believes that a default session
0157         // is something else.
0158         m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"),
0159                           QStringLiteral("Change of configuration of the current session (%1); current default session is %2")
0160                           .arg(conf.name(), m_netConfManager->defaultConfiguration().name()));
0161         reconnect = true;
0162     } else if (conf.state().testFlag(QNetworkConfiguration::Active) && conf.type() == QNetworkConfiguration::InternetAccessPoint &&
0163                conf != sessionsActiveConfiguration() && conf == m_netConfManager->defaultConfiguration()) {
0164         // We are going to interpret this as a subtle hint for switching to another session
0165 
0166         if (m_session->configuration().type() == QNetworkConfiguration::UserChoice && !sessionsActiveConfiguration().isValid()) {
0167             // No configuration has been assigned yet, just ignore this event. This happens on Harmattan when we get a change
0168             // of e.g. an office WiFi connection in reply to us trying to open a session with the system's default configuration.
0169             m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"),
0170                               QStringLiteral("No configuration has been assigned yet, let's wait for it"));
0171             return;
0172         }
0173 
0174         m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"),
0175                           m_session->configuration().type() == QNetworkConfiguration::InternetAccessPoint ?
0176                               QStringLiteral("Change of system's default configuration: %1. Currently using %2.")
0177                               .arg(conf.name(), m_session->configuration().name())
0178                             :
0179                               QStringLiteral("Change of system's default configuration: %1. Currently using %2 (active: %3).")
0180                               .arg(conf.name(), m_session->configuration().name(), sessionsActiveConfiguration().name()));
0181         reconnect = true;
0182     }
0183 
0184     if (reconnect) {
0185         m_model->setNetworkPolicy(NETWORK_OFFLINE);
0186         resetSession();
0187         if (m_session->configuration().isValid()) {
0188             m_session->open();
0189         } else {
0190             m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"),
0191                               QStringLiteral("Waiting for network to become available..."));
0192         }
0193     }
0194 }
0195 
0196 /** @short Make sure that we switch over to whatever session which is now the default one */
0197 void SystemNetworkWatcher::resetSession()
0198 {
0199     delete m_session;
0200     const auto conf = m_netConfManager->defaultConfiguration();
0201     m_session = new QNetworkSession(conf, this);
0202 
0203     QString buf;
0204     QTextStream ss(&buf);
0205     ss << "Switched to network configuration " << conf.name() << " (" << conf.bearerTypeName() << ", " << conf.identifier() << ")";
0206     m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"), buf);
0207     connect(m_session, &QNetworkSession::opened, this, &SystemNetworkWatcher::reconnectModelNetwork);
0208     connect(m_session, static_cast<void (QNetworkSession::*)(QNetworkSession::SessionError)>(&QNetworkSession::error), this, &SystemNetworkWatcher::networkSessionError);
0209 }
0210 
0211 QNetworkConfiguration SystemNetworkWatcher::sessionsActiveConfiguration() const
0212 {
0213     auto conf = m_session->configuration();
0214     if (m_session->configuration().type() == QNetworkConfiguration::InternetAccessPoint) {
0215         return conf;
0216     } else {
0217         QString activeConfId = m_session->sessionProperty(QStringLiteral("ActiveConfiguration")).toString();
0218         // yes, this can return an invalid session
0219         return m_netConfManager->configurationFromIdentifier(activeConfId);
0220     }
0221 }
0222 
0223 void SystemNetworkWatcher::networkSessionError()
0224 {
0225     m_model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Network Session"),
0226                       QStringLiteral("Session error: %1").arg(m_session->errorString()));
0227 
0228     if (!m_reconnectTimer->isActive()) {
0229         attemptReconnect();
0230     }
0231 }
0232 
0233 }
0234 }