MailTransport

transportmanager.cpp
1/*
2 SPDX-FileCopyrightText: 2006-2007 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "transportmanager.h"
8using namespace Qt::Literals::StringLiterals;
9
10#include "mailtransport_defs.h"
11#include "plugins/transportabstractplugin.h"
12#include "plugins/transportpluginmanager.h"
13#include "transport.h"
14#include "transport_p.h"
15#include "transportjob.h"
16#include "transporttype_p.h"
17#include "widgets/addtransportdialogng.h"
18#include <MailTransport/TransportAbstractPlugin>
19
20#include <QApplication>
21#include <QDBusConnection>
22#include <QDBusConnectionInterface>
23#include <QDBusServiceWatcher>
24#include <QPointer>
25#include <QRandomGenerator>
26#include <QRegularExpression>
27#include <QStringList>
28
29#include "mailtransport_debug.h"
30#include <KConfig>
31#include <KConfigGroup>
32#include <KLocalizedString>
33#include <KMessageBox>
34#include <qt6keychain/keychain.h>
35
36using namespace QKeychain;
37
38using namespace MailTransport;
39
40namespace MailTransport
41{
42/**
43 * Private class that helps to provide binary compatibility between releases.
44 * @internal
45 */
46class TransportManagerPrivate
47{
48public:
49 TransportManagerPrivate(TransportManager *parent)
50 : q(parent)
51 {
52 }
53
54 ~TransportManagerPrivate()
55 {
56 delete config;
57 qDeleteAll(transports);
58 }
59
60 KConfig *config = nullptr;
61 QList<Transport *> transports;
63 bool myOwnChange = false;
64 bool appliedChange = false;
65 bool walletAsyncOpen = false;
66 int defaultTransportId = -1;
67 bool isMainInstance = false;
68 QList<TransportJob *> walletQueue;
69 QMap<Transport *, QMetaObject::Connection> passwordConnections;
70 TransportManager *const q;
71
72 void readConfig();
73 void writeConfig();
74 void fillTypes();
75 [[nodiscard]] Transport::Id createId() const;
76 void prepareWallet();
77 void validateDefault();
78 void migrateToWallet();
79 void updatePluginList();
80
81 // Slots
82 void slotTransportsChanged();
83 void dbusServiceUnregistered();
84 void startQueuedJobs();
85 void jobResult(KJob *job);
86};
87}
88
89class StaticTransportManager : public TransportManager
90{
91public:
92 StaticTransportManager()
93
94 {
95 }
96};
97
98StaticTransportManager *sSelf = nullptr;
99
100static void destroyStaticTransportManager()
101{
102 delete sSelf;
103}
104
106 : d(new TransportManagerPrivate(this))
107{
108 qAddPostRoutine(destroyStaticTransportManager);
109 d->config = new KConfig(QStringLiteral("mailtransports"));
110
112
114 connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, [this]() {
115 d->dbusServiceUnregistered();
116 });
117
118 QDBusConnection::sessionBus().connect(QString(), QString(), DBUS_INTERFACE_NAME, DBUS_CHANGE_SIGNAL, this, SLOT(slotTransportsChanged()));
119
120 d->isMainInstance = QDBusConnection::sessionBus().registerService(DBUS_SERVICE_NAME);
121
122 d->fillTypes();
123}
124
126{
127 qRemovePostRoutine(destroyStaticTransportManager);
128}
129
131{
132 if (!sSelf) {
133 sSelf = new StaticTransportManager;
134 sSelf->d->readConfig();
135 }
136 return sSelf;
137}
138
139Transport *TransportManager::transportById(Transport::Id id, bool def) const
140{
141 for (Transport *t : std::as_const(d->transports)) {
142 if (t->id() == id) {
143 return t;
144 }
145 }
146
147 if (def || (id == 0 && d->defaultTransportId != id)) {
148 return transportById(d->defaultTransportId, false);
149 }
150 return nullptr;
151}
152
153Transport *TransportManager::transportByName(const QString &name, bool def) const
154{
155 for (Transport *t : std::as_const(d->transports)) {
156 if (t->name() == name) {
157 return t;
158 }
159 }
160 if (def) {
161 return transportById(0, false);
162 }
163 return nullptr;
164}
165
167{
168 return d->transports;
169}
170
172{
173 return d->types;
174}
175
177{
178 auto id = d->createId();
179 auto t = new Transport(QString::number(id));
180 t->setId(id);
181 return t;
182}
183
184void TransportManager::addTransport(Transport *transport)
185{
186 if (d->transports.contains(transport)) {
187 qCDebug(MAILTRANSPORT_LOG) << "Already have this transport.";
188 return;
189 }
190
191 qCDebug(MAILTRANSPORT_LOG) << "Added transport" << transport;
192 d->transports.append(transport);
193 d->validateDefault();
194 d->writeConfig();
195 emitChangesCommitted();
196}
197
199{
200 connect(job, &TransportJob::result, this, [this](KJob *job) {
201 d->jobResult(job);
202 });
203
204 // check if the job is waiting for the wallet
205 if (!job->transport()->isComplete()) {
206 qCDebug(MAILTRANSPORT_LOG) << "job waits for wallet:" << job;
207 d->walletQueue << job;
209 return;
210 }
211
212 job->start();
213}
214
216{
217 if (showCondition == IfNoTransportExists) {
218 if (!isEmpty()) {
219 return true;
220 }
221
222 const int response = KMessageBox::warningContinueCancel(parent,
223 i18n("You must create an outgoing account before sending."),
224 i18nc("@title:window", "Create Account Now?"),
225 KGuiItem(i18nc("@action:button", "Create Account Now")),
227 if (response != KMessageBox::Continue) {
228 return false;
229 }
230 }
231
233 const bool accepted = (dialog->exec() == QDialog::Accepted);
234 delete dialog;
235 return accepted;
236}
237
238void TransportManager::initializeTransport(const QString &identifier, Transport *transport)
239{
240 TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(identifier);
241 if (plugin) {
242 plugin->initializeTransport(transport, identifier);
243 }
244}
245
246bool TransportManager::configureTransport(const QString &identifier, Transport *transport, QWidget *parent)
247{
248 TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(identifier);
249 if (plugin) {
250 return plugin->configureTransport(identifier, transport, parent);
251 }
252 return false;
253}
254
256{
257 Transport *t = transportById(transportId, false);
258 if (!t) {
259 return nullptr;
260 }
261 t = t->clone(); // Jobs delete their transports.
263 TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(t->identifier());
264 if (plugin) {
265 return plugin->createTransportJob(t, t->identifier());
266 }
267 Q_ASSERT(false);
268 return nullptr;
269}
270
272{
273 bool ok = false;
274 Transport *t = nullptr;
275
276 int transportId = transport.toInt(&ok);
277 if (ok) {
278 t = transportById(transportId);
279 }
280
281 if (!t) {
282 t = transportByName(transport, false);
283 }
284
285 if (t) {
286 return createTransportJob(t->id());
287 }
288
289 return nullptr;
290}
291
293{
294 return d->transports.isEmpty();
295}
296
298{
299 QList<int> rv;
300 rv.reserve(d->transports.count());
301 for (Transport *t : std::as_const(d->transports)) {
302 rv << t->id();
303 }
304 return rv;
305}
306
308{
309 QStringList rv;
310 rv.reserve(d->transports.count());
311 for (Transport *t : std::as_const(d->transports)) {
312 rv << t->name();
313 }
314 return rv;
315}
316
318{
319 Transport *t = transportById(d->defaultTransportId, false);
320 if (t) {
321 return t->name();
322 }
323 return {};
324}
325
327{
328 return d->defaultTransportId;
329}
330
332{
333 if (id == d->defaultTransportId || !transportById(id, false)) {
334 return;
335 }
336 d->defaultTransportId = id;
337 d->writeConfig();
338}
339
340void TransportManager::removePasswordFromWallet(Transport::Id id)
341{
342 auto deleteJob = new DeletePasswordJob(WALLET_FOLDER);
343 deleteJob->setKey(QString::number(id));
344 deleteJob->start();
345}
346
348{
349 Transport *t = transportById(id, false);
350 if (!t) {
351 return;
352 }
353 auto plugin = MailTransport::TransportPluginManager::self()->plugin(t->identifier());
354 if (plugin) {
355 plugin->cleanUp(t);
356 }
357 Q_EMIT transportRemoved(t->id(), t->name());
358
359 d->transports.removeAll(t);
360 d->validateDefault();
361 const QString group = t->currentGroup();
362 if (t->storePassword()) {
363 auto deleteJob = new DeletePasswordJob(WALLET_FOLDER);
364 deleteJob->setKey(QString::number(t->id()));
365 deleteJob->start();
366 }
367 delete t;
368 d->config->deleteGroup(group);
369 d->writeConfig();
370}
371
372void TransportManagerPrivate::readConfig()
373{
374 QList<Transport *> oldTransports = transports;
375 transports.clear();
376
377 static QRegularExpression re(QStringLiteral("^Transport (.+)$"));
378 const QStringList groups = config->groupList().filter(re);
379 for (const QString &s : groups) {
380 const QRegularExpressionMatch match = re.match(s);
381 if (!match.hasMatch()) {
382 continue;
383 }
384 Transport *t = nullptr;
385 // see if we happen to have that one already
386 const QString capturedString = match.captured(1);
387 const QString checkString = "Transport "_L1 + capturedString;
388 for (Transport *old : oldTransports) {
389 if (old->currentGroup() == checkString) {
390 qCDebug(MAILTRANSPORT_LOG) << "reloading existing transport:" << s;
391 t = old;
392 t->load();
393 oldTransports.removeAll(old);
394 break;
395 }
396 }
397
398 if (!t) {
399 t = new Transport(capturedString);
400 }
401 if (t->id() <= 0) {
402 t->setId(createId());
403 t->save();
404 }
405 transports.append(t);
406 }
407
408 qDeleteAll(oldTransports);
409 oldTransports.clear();
410 // read default transport
411 KConfigGroup group(config, QStringLiteral("General"));
412 defaultTransportId = group.readEntry("default-transport", 0);
413 if (defaultTransportId == 0) {
414 // migrated default transport contains the name instead
415 QString name = group.readEntry("default-transport", QString());
416 if (!name.isEmpty()) {
417 Transport *t = q->transportByName(name, false);
418 if (t) {
419 defaultTransportId = t->id();
420 writeConfig();
421 }
422 }
423 }
424 validateDefault();
425 migrateToWallet();
426 q->loadPasswordsAsync();
427}
428
429void TransportManagerPrivate::writeConfig()
430{
431 KConfigGroup group(config, QStringLiteral("General"));
432 group.writeEntry("default-transport", defaultTransportId);
433 config->sync();
434 q->emitChangesCommitted();
435}
436
437void TransportManagerPrivate::fillTypes()
438{
439 Q_ASSERT(types.isEmpty());
440
441 updatePluginList();
442 QObject::connect(MailTransport::TransportPluginManager::self(), &TransportPluginManager::updatePluginList, q, &TransportManager::updatePluginList);
443}
444
445void TransportManagerPrivate::updatePluginList()
446{
447 types.clear();
448 const QList<MailTransport::TransportAbstractPlugin *> lstPlugins = MailTransport::TransportPluginManager::self()->pluginsList();
449 for (MailTransport::TransportAbstractPlugin *plugin : lstPlugins) {
450 if (plugin->names().isEmpty()) {
451 qCDebug(MAILTRANSPORT_LOG) << "Plugin " << plugin << " doesn't provide plugin";
452 }
453 const QList<TransportAbstractPluginInfo> lstInfos = plugin->names();
454 for (const MailTransport::TransportAbstractPluginInfo &info : lstInfos) {
455 TransportType type;
456 type.d->mName = info.name;
457 type.d->mDescription = info.description;
458 type.d->mIdentifier = info.identifier;
459 type.d->mIsAkonadiResource = info.isAkonadi;
460 types << type;
461 }
462 }
463}
464
465void TransportManager::updatePluginList()
466{
467 d->updatePluginList();
468}
469
470void TransportManager::emitChangesCommitted()
471{
472 d->myOwnChange = true; // prevent us from reading our changes again
473 d->appliedChange = false; // but we have to read them at least once
476}
477
478void TransportManagerPrivate::slotTransportsChanged()
479{
480 if (myOwnChange && appliedChange) {
481 myOwnChange = false;
482 appliedChange = false;
483 return;
484 }
485
486 qCDebug(MAILTRANSPORT_LOG);
487 config->reparseConfiguration();
488 // FIXME: this deletes existing transport objects!
489 readConfig();
490 appliedChange = true; // to prevent recursion
491 Q_EMIT q->transportsChanged();
492}
493
494Transport::Id TransportManagerPrivate::createId() const
495{
496 QList<int> usedIds;
497 usedIds.reserve(transports.size());
498 for (Transport *t : std::as_const(transports)) {
499 usedIds << t->id();
500 }
501 int newId;
502 do {
503 // 0 is default for unknown, so use 1 as lower value accepted
504 newId = QRandomGenerator::global()->bounded(1, RAND_MAX);
505 } while (usedIds.contains(newId));
506 return newId;
507}
508
510{
511 QEventLoop loop;
512 for (Transport *t : std::as_const(d->transports)) {
513 if (d->passwordConnections.contains(t)) {
514 continue;
515 }
516 auto conn = connect(t, &Transport::passwordLoaded, this, [&]() {
517 disconnect(d->passwordConnections[t]);
518 d->passwordConnections.remove(t);
519 if (d->passwordConnections.count() == 0) {
520 loop.exit();
521 }
522 });
523 d->passwordConnections[t] = conn;
524 t->readPassword();
525 }
526 loop.exec();
527
528 d->startQueuedJobs();
530}
531
533{
534 for (Transport *t : std::as_const(d->transports)) {
535 if (!t->isComplete()) {
536 if (d->passwordConnections.contains(t)) {
537 continue;
538 }
539 auto conn = connect(t, &Transport::passwordLoaded, this, [&]() {
540 disconnect(d->passwordConnections[t]);
541 d->passwordConnections.remove(t);
542 if (d->passwordConnections.count() == 0) {
543 d->startQueuedJobs();
545 }
546 });
547 d->passwordConnections[t] = conn;
548 t->readPassword();
549 }
550 }
551}
552
553void TransportManagerPrivate::startQueuedJobs()
554{
555 QList<TransportJob *> jobsToDel;
556 for (auto job : walletQueue) {
557 if (job->transport()->isComplete()) {
558 job->start();
559 jobsToDel << job;
560 }
561 }
562
563 for (auto job : jobsToDel) {
564 walletQueue.removeAll(job);
565 }
566}
567
568void TransportManagerPrivate::validateDefault()
569{
570 if (!q->transportById(defaultTransportId, false)) {
571 if (q->isEmpty()) {
572 defaultTransportId = -1;
573 } else {
574 defaultTransportId = transports.constFirst()->id();
575 writeConfig();
576 }
577 }
578}
579
580void TransportManagerPrivate::migrateToWallet()
581{
582 // check if we tried this already
583 static bool firstRun = true;
584 if (!firstRun) {
585 return;
586 }
587 firstRun = false;
588
589 // check if we are the main instance
590 if (!isMainInstance) {
591 return;
592 }
593
594 // check if migration is needed
595 QStringList names;
596 for (Transport *t : std::as_const(transports)) {
597 if (t->needsWalletMigration()) {
598 names << t->name();
599 }
600 }
601 if (names.isEmpty()) {
602 return;
603 }
604
605 // ask user if he wants to migrate
606 int result = KMessageBox::questionTwoActionsList(nullptr,
607 i18n("The following mail transports store their passwords in an "
608 "unencrypted configuration file.\nFor security reasons, "
609 "please consider migrating these passwords to KWallet, the "
610 "KDE Wallet management tool,\nwhich stores sensitive data "
611 "for you in a strongly encrypted file.\n"
612 "Do you want to migrate your passwords to KWallet?"),
613 names,
614 i18nc("@title:window", "Question"),
615 KGuiItem(i18nc("@action:button", "Migrate")),
616 KGuiItem(i18nc("@action:button", "Keep")),
617 QStringLiteral("WalletMigrate"));
618 if (result != KMessageBox::ButtonCode::PrimaryAction) {
619 return;
620 }
621
622 // perform migration
623 for (Transport *t : std::as_const(transports)) {
624 if (t->needsWalletMigration()) {
625 t->migrateToWallet();
626 }
627 }
628}
629
630void TransportManagerPrivate::dbusServiceUnregistered()
631{
632 QDBusConnection::sessionBus().registerService(DBUS_SERVICE_NAME);
633}
634
635void TransportManagerPrivate::jobResult(KJob *job)
636{
637 walletQueue.removeAll(static_cast<TransportJob *>(job));
638}
639
640#include "moc_transportmanager.cpp"
QStringList groupList() const override
void result(KJob *job)
The TransportAbstractPlugin class.
Abstract base class for all mail transport jobs.
Transport * transport() const
Returns the Transport object containing the mail transport settings.
void start() override
Starts this job.
Central transport management interface.
MAILTRANSPORT_DEPRECATED void schedule(TransportJob *job)
Executes the given transport job.
Q_SCRIPTABLE QStringList transportNames() const
Returns a list of transport names.
void transportRemoved(int id, const QString &name)
Emitted when a transport is deleted.
Q_SCRIPTABLE void removeTransport(int id)
Deletes the specified transport.
Q_SCRIPTABLE void changesCommitted()
Internal signal to synchronize all TransportManager instances.
void loadPasswordsAsync()
Tries to load passwords asynchronously from KWallet if needed.
void passwordsChanged()
Emitted when passwords have been loaded from the wallet.
TransportType::List types() const
Returns a list of all available transport types.
MAILTRANSPORT_DEPRECATED TransportJob * createTransportJob(Transport::Id transportId)
Creates a mail transport job for the given transport identifier.
Q_SCRIPTABLE void setDefaultTransport(int id)
Sets the default transport.
Transport * transportById(Transport::Id id, bool def=true) const
Returns the Transport object with the given id.
void loadPasswords()
Loads all passwords synchronously.
Q_SCRIPTABLE QString defaultTransportName() const
Returns the default transport name.
Q_SCRIPTABLE void transportsChanged()
Emitted when transport settings have changed (by this or any other TransportManager instance).
void addTransport(Transport *transport)
Adds the given transport.
static TransportManager * self()
Returns the TransportManager instance.
Transport * transportByName(const QString &name, bool def=true) const
Returns the transport object with the given name.
bool showTransportCreationDialog(QWidget *parent, ShowCondition showCondition=Always)
Shows a dialog for creating and configuring a new transport.
bool configureTransport(const QString &identifier, Transport *transport, QWidget *parent)
Open a configuration dialog for an existing transport.
ShowCondition
Describes when to show the transport creation dialog.
@ IfNoTransportExists
Only show the transport creation dialog if no transport currently.
Q_SCRIPTABLE bool isEmpty() const
Returns true if there are no mail transports at all.
~TransportManager() override
Destructor.
Q_SCRIPTABLE int defaultTransportId() const
Returns the default transport identifier.
QList< Transport * > transports() const
Returns a list of all available transports.
Q_SCRIPTABLE QList< int > transportIds() const
Returns a list of transport identifiers.
TransportManager()
Singleton class, the only instance resides in the static object sSelf.
Transport * createTransport() const
Creates a new, empty Transport object.
QList< TransportType > List
Describes a list of transport types.
Represents the settings of a specific mail transport.
Definition transport.h:33
void migrateToWallet()
Try to migrate the password from the config file to the wallet.
void passwordLoaded()
Emitted when passwords have been loaded from QKeyChain.
bool needsWalletMigration() const
Returns true if the password was not stored in the wallet.
bool isComplete() const
Returns true if all settings have been loaded.
Transport * clone() const
Returns a deep copy of this Transport object which will no longer be automatically updated.
void updatePasswordState()
This function synchronizes the password of this transport with the password of the transport with the...
Definition transport.cpp:88
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
Internal file containing constant definitions etc.
Type type(const QSqlDatabase &db)
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
ButtonCode questionTwoActionsList(QWidget *parent, const QString &text, const QStringList &strlist, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
QString name(StandardAction id)
KGuiItem cancel()
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
bool registerService(const QString &serviceName)
QDBusConnection sessionBus()
void serviceUnregistered(const QString &serviceName)
int exec(ProcessEventsFlags flags)
void exit(int returnCode)
void clear()
bool contains(const AT &value) const const
bool isEmpty() const const
void reserve(qsizetype size)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
double bounded(double highest)
QRandomGenerator * global()
QString & append(QChar ch)
bool isEmpty() const const
QString number(double n, char format, int precision)
int toInt(bool *ok, int base) const const
QStringList filter(QStringView str, Qt::CaseSensitivity cs) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:46:28 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.