Use ImapAccess even in the desktop version
[trojita.git] / src / Imap / Model / ImapAccess.cpp
blob3fe62e73c2dd64f3f13ffae78e76180a93667d88
1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
3 This file is part of the Trojita Qt IMAP e-mail client,
4 http://trojita.flaska.net/
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License as
8 published by the Free Software Foundation; either version 2 of
9 the License or (at your option) version 3 or any later version
10 accepted by the membership of KDE e.V. (or its successor approved
11 by the membership of KDE e.V.), which shall act as a proxy
12 defined in Section 14 of version 3 of the license.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "ImapAccess.h"
24 #include <QDir>
25 #include <QFileInfo>
26 #include <QSslKey>
27 #include <QSettings>
28 #include "Common/MetaTypes.h"
29 #include "Common/Paths.h"
30 #include "Common/PortNumbers.h"
31 #include "Common/SettingsNames.h"
32 #include "Imap/Model/CombinedCache.h"
33 #include "Imap/Model/MemoryCache.h"
34 #include "Imap/Model/Utils.h"
35 #include "Imap/Network/MsgPartNetAccessManager.h"
36 #include "Streams/SocketFactory.h"
38 namespace Imap {
40 ImapAccess::ImapAccess(QObject *parent, QSettings *settings, const QString &accountName) :
41 QObject(parent), m_settings(settings), m_imapModel(0), m_mailboxModel(0), m_mailboxSubtreeModel(0), m_msgListModel(0),
42 m_visibleTasksModel(0), m_oneMessageModel(0), m_netWatcher(0), m_msgQNAM(0), m_port(0),
43 m_connectionMethod(Common::ConnectionMethod::Invalid),
44 m_sslInfoIcon(Imap::Mailbox::CertificateUtils::NoIcon),
45 m_accountName(accountName)
47 Imap::migrateSettings(m_settings);
48 m_server = m_settings->value(Common::SettingsNames::imapHostKey).toString();
49 m_username = m_settings->value(Common::SettingsNames::imapUserKey).toString();
50 if (m_settings->value(Common::SettingsNames::imapMethodKey).toString() == Common::SettingsNames::methodSSL) {
51 m_connectionMethod = Common::ConnectionMethod::NetDedicatedTls;
52 } else if (m_settings->value(Common::SettingsNames::imapMethodKey).toString() == Common::SettingsNames::methodTCP) {
53 m_connectionMethod = m_settings->value(Common::SettingsNames::imapStartTlsKey).toBool() ?
54 Common::ConnectionMethod::NetStartTls : Common::ConnectionMethod::NetCleartext;
55 } else if (m_settings->value(Common::SettingsNames::imapMethodKey).toString() == Common::SettingsNames::methodProcess) {
56 m_connectionMethod = Common::ConnectionMethod::Process;
58 m_port = m_settings->value(Common::SettingsNames::imapPortKey, QVariant(0)).toInt();
59 if (!m_port) {
60 switch (m_connectionMethod) {
61 case Common::ConnectionMethod::NetCleartext:
62 case Common::ConnectionMethod::NetStartTls:
63 m_port = Common::PORT_IMAP;
64 break;
65 case Common::ConnectionMethod::NetDedicatedTls:
66 m_port = Common::PORT_IMAPS;
67 break;
68 case Common::ConnectionMethod::Process:
69 case Common::ConnectionMethod::Invalid:
70 // do nothing
71 break;
75 m_cacheDir = Common::writablePath(Common::LOCATION_CACHE) + m_accountName + QLatin1Char('/');;
78 void ImapAccess::alertReceived(const QString &message)
80 qDebug() << "alertReceived" << message;
83 void ImapAccess::connectionError(const QString &message)
85 qDebug() << "connectionError" << message;
88 void ImapAccess::slotLogged(uint parserId, const Common::LogMessage &message)
90 if (message.kind != Common::LOG_IO_READ) {
91 qDebug() << "LOG" << parserId << message.timestamp << message.kind << message.source << message.message;
95 QString ImapAccess::server() const
97 return m_server;
100 void ImapAccess::setServer(const QString &server)
102 m_server = server;
103 m_settings->setValue(Common::SettingsNames::imapHostKey, m_server);
104 emit serverChanged();
107 QString ImapAccess::username() const
109 return m_username;
112 void ImapAccess::setUsername(const QString &username)
114 m_username = username;
115 m_settings->setValue(Common::SettingsNames::imapUserKey, m_username);
116 emit usernameChanged();;
119 QString ImapAccess::password() const
121 return m_password;
124 void ImapAccess::setPassword(const QString &password)
126 m_password = password;
129 int ImapAccess::port() const
131 return m_port;
134 void ImapAccess::setPort(const int port)
136 m_port = port;
137 m_settings->setValue(Common::SettingsNames::imapPortKey, m_port);
138 emit portChanged();
141 QString ImapAccess::sslMode() const
143 switch (m_connectionMethod) {
144 case Common::ConnectionMethod::NetCleartext:
145 return QLatin1String("No");
146 case Common::ConnectionMethod::NetStartTls:
147 return QLatin1String("StartTLS");
148 case Common::ConnectionMethod::NetDedicatedTls:
149 return QLatin1String("SSL");
150 case Common::ConnectionMethod::Invalid:
151 case Common::ConnectionMethod::Process:
152 return QString();
155 Q_ASSERT(false);
156 return QString();
159 void ImapAccess::setSslMode(const QString &sslMode)
161 if (sslMode == QLatin1String("No")) {
162 setConnectionMethod(Common::ConnectionMethod::NetCleartext);
163 } else if (sslMode == QLatin1String("SSL")) {
164 setConnectionMethod(Common::ConnectionMethod::NetDedicatedTls);
165 } else if (sslMode == QLatin1String("StartTLS")) {
166 setConnectionMethod(Common::ConnectionMethod::NetStartTls);
167 } else {
168 Q_ASSERT(false);
172 Common::ConnectionMethod ImapAccess::connectionMethod() const
174 return m_connectionMethod;
177 void ImapAccess::setConnectionMethod(const Common::ConnectionMethod mode)
179 m_connectionMethod = mode;
180 switch (m_connectionMethod) {
181 case Common::ConnectionMethod::Invalid:
182 break;
183 case Common::ConnectionMethod::NetCleartext:
184 case Common::ConnectionMethod::NetStartTls:
185 m_settings->setValue(Common::SettingsNames::imapMethodKey, Common::SettingsNames::methodTCP);
186 m_settings->setValue(Common::SettingsNames::imapStartTlsKey, m_connectionMethod == Common::ConnectionMethod::NetStartTls);
187 break;
188 case Common::ConnectionMethod::NetDedicatedTls:
189 m_settings->setValue(Common::SettingsNames::imapMethodKey, Common::SettingsNames::methodSSL);
190 // Trying to communicate the fact that this is going to be an encrypted connection, even though
191 // that settings bit is not actually used
192 m_settings->setValue(Common::SettingsNames::imapStartTlsKey, true);
193 break;
194 case Common::ConnectionMethod::Process:
195 m_settings->setValue(Common::SettingsNames::imapMethodKey, Common::SettingsNames::methodProcess);
196 break;
198 emit connMethodChanged();
201 void ImapAccess::doConnect()
203 Q_ASSERT(!m_imapModel);
205 Imap::Mailbox::SocketFactoryPtr factory;
206 Imap::Mailbox::TaskFactoryPtr taskFactory(new Imap::Mailbox::TaskFactory());
208 switch (m_connectionMethod) {
209 case Common::ConnectionMethod::Invalid:
210 factory.reset(new Streams::FakeSocketFactory(Imap::CONN_STATE_LOGOUT));
211 break;
212 case Common::ConnectionMethod::NetCleartext:
213 case Common::ConnectionMethod::NetStartTls:
214 factory.reset(new Streams::TlsAbleSocketFactory(server(), port()));
215 factory->setStartTlsRequired(m_connectionMethod == Common::ConnectionMethod::NetStartTls);
216 break;
217 case Common::ConnectionMethod::NetDedicatedTls:
218 factory.reset(new Streams::SslSocketFactory(server(), port()));
219 break;
220 case Common::ConnectionMethod::Process:
221 QStringList args = m_settings->value(Common::SettingsNames::imapProcessKey).toString().split(QLatin1Char(' '));
222 if (args.isEmpty()) {
223 // it's going to fail anyway
224 args << QLatin1String("");
226 QString appName = args.takeFirst();
227 factory.reset(new Streams::ProcessSocketFactory(appName, args));
228 break;
231 bool shouldUsePersistentCache =
232 m_settings->value(Common::SettingsNames::cacheOfflineKey).toString() != Common::SettingsNames::cacheOfflineNone;
234 if (shouldUsePersistentCache && !QDir().mkpath(m_cacheDir)) {
235 onCacheError(tr("Failed to create directory %1").arg(m_cacheDir));
236 shouldUsePersistentCache = false;
239 if (shouldUsePersistentCache) {
240 QFile::Permissions expectedPerms = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner;
241 if (QFileInfo(m_cacheDir).permissions() != expectedPerms) {
242 if (!QFile::setPermissions(m_cacheDir, expectedPerms)) {
243 #ifndef Q_OS_WIN32
244 onCacheError(tr("Failed to set safe permissions on cache directory %1").arg(m_cacheDir));
245 shouldUsePersistentCache = false;
246 #endif
251 Imap::Mailbox::AbstractCache *cache = 0;
253 if (!shouldUsePersistentCache) {
254 cache = new Imap::Mailbox::MemoryCache(this);
255 } else {
256 cache = new Imap::Mailbox::CombinedCache(this, QLatin1String("trojita-imap-cache"), m_cacheDir);
257 connect(cache, SIGNAL(error(QString)), this, SLOT(onCacheError(QString)));
258 if (! static_cast<Imap::Mailbox::CombinedCache *>(cache)->open()) {
259 // Error message was already shown by the cacheError() slot
260 cache->deleteLater();
261 cache = new Imap::Mailbox::MemoryCache(this);
262 } else {
263 if (m_settings->value(Common::SettingsNames::cacheOfflineKey).toString() == Common::SettingsNames::cacheOfflineAll) {
264 cache->setRenewalThreshold(0);
265 } else {
266 const int defaultCacheLifetime = 30;
267 bool ok;
268 int num = m_settings->value(Common::SettingsNames::cacheOfflineNumberDaysKey, defaultCacheLifetime).toInt(&ok);
269 if (!ok)
270 num = defaultCacheLifetime;
271 cache->setRenewalThreshold(num);
276 m_imapModel = new Imap::Mailbox::Model(this, cache, std::move(factory), std::move(taskFactory));
277 m_imapModel->setObjectName(QString::fromUtf8("imapModel-%1").arg(m_accountName));
278 m_imapModel->setCapabilitiesBlacklist(m_settings->value(Common::SettingsNames::imapBlacklistedCapabilities).toStringList());
279 m_imapModel->setProperty("trojita-imap-enable-id", m_settings->value(Common::SettingsNames::imapEnableId, true).toBool());
280 connect(m_imapModel, SIGNAL(alertReceived(QString)), this, SLOT(alertReceived(QString)));
281 connect(m_imapModel, SIGNAL(connectionError(QString)), this, SLOT(connectionError(QString)));
282 //connect(m_imapModel, SIGNAL(logged(uint,Common::LogMessage)), this, SLOT(slotLogged(uint,Common::LogMessage)));
283 connect(m_imapModel, SIGNAL(needsSslDecision(QList<QSslCertificate>,QList<QSslError>)),
284 this, SLOT(slotSslErrors(QList<QSslCertificate>,QList<QSslError>)));
286 m_netWatcher = new Imap::Mailbox::NetworkWatcher(this, m_imapModel);
287 QMetaObject::invokeMethod(m_netWatcher,
288 m_settings->value(Common::SettingsNames::imapStartOffline).toBool() ?
289 "setNetworkOffline" : "setNetworkOnline",
290 Qt::QueuedConnection);
292 m_imapModel->setImapUser(username());
293 if (!m_password.isNull()) {
294 // Really; the idea is to wait before it has been set for the first time
295 m_imapModel->setImapPassword(password());
298 m_mailboxModel = new Imap::Mailbox::MailboxModel(this, m_imapModel);
299 m_mailboxModel->setObjectName(QString::fromUtf8("mailboxModel-%1").arg(m_accountName));
300 m_mailboxSubtreeModel = new Imap::Mailbox::SubtreeModelOfMailboxModel(this);
301 m_mailboxSubtreeModel->setObjectName(QString::fromUtf8("mailboxSubtreeModel-%1").arg(m_accountName));
302 m_mailboxSubtreeModel->setSourceModel(m_mailboxModel);
303 m_mailboxSubtreeModel->setOriginalRoot();
304 m_msgListModel = new Imap::Mailbox::MsgListModel(this, m_imapModel);
305 m_msgListModel->setObjectName(QString::fromUtf8("msgListModel-%1").arg(m_accountName));
306 m_visibleTasksModel = new Imap::Mailbox::VisibleTasksModel(this, m_imapModel->taskModel());
307 m_visibleTasksModel->setObjectName(QString::fromUtf8("visibleTasksModel-%1").arg(m_accountName));
308 m_oneMessageModel = new Imap::Mailbox::OneMessageModel(m_imapModel);
309 m_oneMessageModel->setObjectName(QString::fromUtf8("oneMessageModel-%1").arg(m_accountName));
310 m_msgQNAM = new Imap::Network::MsgPartNetAccessManager(this);
311 m_msgQNAM->setObjectName(QString::fromUtf8("m_msgQNAM-%1").arg(m_accountName));
312 emit modelsChanged();
315 void ImapAccess::onCacheError(const QString &message)
317 if (m_imapModel) {
318 m_imapModel->setCache(new Imap::Mailbox::MemoryCache(m_imapModel));
320 emit cacheError(message);
323 QObject *ImapAccess::imapModel() const
325 return m_imapModel;
328 QObject *ImapAccess::mailboxModel() const
330 return m_mailboxSubtreeModel;
333 QObject *ImapAccess::msgListModel() const
335 return m_msgListModel;
338 QObject *ImapAccess::visibleTasksModel() const
340 return m_visibleTasksModel;
343 QObject *ImapAccess::oneMessageModel() const
345 return m_oneMessageModel;
348 QObject *ImapAccess::networkWatcher() const
350 return m_netWatcher;
353 QNetworkAccessManager *ImapAccess::msgQNAM() const
355 return m_msgQNAM;
358 void ImapAccess::openMessage(const QString &mailboxName, const uint uid)
360 QModelIndex msgIndex = m_imapModel->messageIndexByUid(mailboxName, uid);
361 m_oneMessageModel->setMessage(msgIndex);
362 static_cast<Imap::Network::MsgPartNetAccessManager*>(m_msgQNAM)->setModelMessage(msgIndex);
365 QString ImapAccess::prettySize(const uint bytes) const
367 return Imap::Mailbox::PrettySize::prettySize(bytes, Imap::Mailbox::PrettySize::WITH_BYTES_SUFFIX);
370 void ImapAccess::slotSslErrors(const QList<QSslCertificate> &sslCertificateChain, const QList<QSslError> &sslErrors)
372 m_sslChain = sslCertificateChain;
373 m_sslErrors = sslErrors;
375 QByteArray lastKnownPubKey = m_settings->value(Common::SettingsNames::imapSslPemPubKey).toByteArray();
376 if (!m_sslChain.isEmpty() && !lastKnownPubKey.isEmpty() && lastKnownPubKey == m_sslChain[0].publicKey().toPem()) {
377 // This certificate chain contains the same public keys as the last time; we should accept that
378 m_imapModel->setSslPolicy(m_sslChain, m_sslErrors, true);
379 } else {
380 Imap::Mailbox::CertificateUtils::formatSslState(
381 m_sslChain, lastKnownPubKey, m_sslErrors, &m_sslInfoTitle, &m_sslInfoMessage, &m_sslInfoIcon);
382 emit checkSslPolicy();
386 void ImapAccess::setSslPolicy(bool accept)
388 if (accept && !m_sslChain.isEmpty()) {
389 m_settings->setValue(Common::SettingsNames::imapSslPemPubKey, m_sslChain[0].publicKey().toPem());
391 m_imapModel->setSslPolicy(m_sslChain, m_sslErrors, accept);
394 void ImapAccess::forgetSslCertificate()
396 m_settings->remove(Common::SettingsNames::imapSslPemPubKey);
399 QString ImapAccess::sslInfoTitle() const
401 return m_sslInfoTitle;
404 QString ImapAccess::sslInfoMessage() const
406 return m_sslInfoMessage;
409 Imap::Mailbox::CertificateUtils::IconType ImapAccess::sslInfoIcon() const
411 return m_sslInfoIcon;
414 QString ImapAccess::mailboxListMailboxName() const
416 return m_mailboxSubtreeModel->rootIndex().data(Imap::Mailbox::RoleMailboxName).toString();
419 QString ImapAccess::mailboxListShortMailboxName() const
421 return m_mailboxSubtreeModel->rootIndex().data(Imap::Mailbox::RoleShortMailboxName).toString();
424 /** @short Persistently remove the local cache of IMAP data
426 This method should be called by the UI when the user changes its connection details, i.e. when there's a big chance that we are
427 connecting to a completely different server since the last time.
429 void ImapAccess::nukeCache()
431 QFile::remove(m_cacheDir);