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 }