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"
9#include "plugins/transportabstractplugin.h"
10#include "plugins/transportpluginmanager.h"
11#include "transport.h"
12#include "transport_p.h"
13#include "transportjob.h"
14#include "transporttype_p.h"
15#include "widgets/addtransportdialogng.h"
16#include <MailTransport/TransportAbstractPlugin>
17
18#include <QApplication>
19#include <QDBusConnection>
20#include <QDBusConnectionInterface>
21#include <QDBusServiceWatcher>
22#include <QPointer>
23#include <QRandomGenerator>
24#include <QRegularExpression>
25#include <QStringList>
26
27#include "mailtransport_debug.h"
28#include <KConfig>
29#include <KConfigGroup>
30#include <KLocalizedString>
31#include <KMessageBox>
32#include <qt6keychain/keychain.h>
33
34using namespace QKeychain;
35
36using namespace MailTransport;
37
38namespace MailTransport
39{
40/**
41 * Private class that helps to provide binary compatibility between releases.
42 * @internal
43 */
44class TransportManagerPrivate
45{
46public:
47 TransportManagerPrivate(TransportManager *parent)
48 : q(parent)
49 {
50 }
51
52 ~TransportManagerPrivate()
53 {
54 delete config;
55 qDeleteAll(transports);
56 }
57
58 KConfig *config = nullptr;
59 QList<Transport *> transports;
61 bool myOwnChange = false;
62 bool appliedChange = false;
63 bool walletAsyncOpen = false;
64 int defaultTransportId = -1;
65 bool isMainInstance = false;
66 QList<TransportJob *> walletQueue;
68 TransportManager *const q;
69
70 void readConfig();
71 void writeConfig();
72 void fillTypes();
73 [[nodiscard]] Transport::Id createId() const;
74 void prepareWallet();
75 void validateDefault();
76 void migrateToWallet();
77 void updatePluginList();
78
79 // Slots
80 void slotTransportsChanged();
81 void dbusServiceUnregistered();
82 void startQueuedJobs();
83 void jobResult(KJob *job);
84};
85}
86
87class StaticTransportManager : public TransportManager
88{
89public:
90 StaticTransportManager()
92 {
93 }
94};
95
96StaticTransportManager *sSelf = nullptr;
97
98static void destroyStaticTransportManager()
99{
100 delete sSelf;
101}
102
104 : QObject()
105 , d(new TransportManagerPrivate(this))
106{
107 qAddPostRoutine(destroyStaticTransportManager);
108 d->config = new KConfig(QStringLiteral("mailtransports"));
109
111
114 d->dbusServiceUnregistered();
115 });
116
117 QDBusConnection::sessionBus().connect(QString(), QString(), DBUS_INTERFACE_NAME, DBUS_CHANGE_SIGNAL, this, SLOT(slotTransportsChanged()));
118
119 d->isMainInstance = QDBusConnection::sessionBus().registerService(DBUS_SERVICE_NAME);
120
121 d->fillTypes();
122}
123
125{
126 qRemovePostRoutine(destroyStaticTransportManager);
127}
128
130{
131 if (!sSelf) {
132 sSelf = new StaticTransportManager;
133 sSelf->d->readConfig();
134 }
135 return sSelf;
136}
137
138Transport *TransportManager::transportById(Transport::Id id, bool def) const
139{
140 for (Transport *t : std::as_const(d->transports)) {
141 if (t->id() == id) {
142 return t;
143 }
144 }
145
146 if (def || (id == 0 && d->defaultTransportId != id)) {
147 return transportById(d->defaultTransportId, false);
148 }
149 return nullptr;
150}
151
153{
154 for (Transport *t : std::as_const(d->transports)) {
155 if (t->name() == name) {
156 return t;
157 }
158 }
159 if (def) {
160 return transportById(0, false);
161 }
162 return nullptr;
163}
164
166{
167 return d->transports;
168}
169
171{
172 return d->types;
173}
174
176{
177 auto id = d->createId();
178 auto t = new Transport(QString::number(id));
179 t->setId(id);
180 return t;
181}
182
184{
185 if (d->transports.contains(transport)) {
186 qCDebug(MAILTRANSPORT_LOG) << "Already have this transport.";
187 return;
188 }
189
190 qCDebug(MAILTRANSPORT_LOG) << "Added transport" << transport;
191 d->transports.append(transport);
192 d->validateDefault();
193 d->writeConfig();
194 emitChangesCommitted();
195}
196
198{
199 connect(job, &TransportJob::result, this, [this](KJob *job) {
200 d->jobResult(job);
201 });
202
203 // check if the job is waiting for the wallet
204 if (!job->transport()->isComplete()) {
205 qCDebug(MAILTRANSPORT_LOG) << "job waits for wallet:" << job;
206 d->walletQueue << job;
208 return;
209 }
210
211 job->start();
212}
213
215{
217 if (!isEmpty()) {
218 return true;
219 }
220
222 i18n("You must create an outgoing account before sending."),
223 i18n("Create Account Now?"),
224 KGuiItem(i18n("Create Account Now")),
227 return false;
228 }
229 }
230
232 const bool accepted = (dialog->exec() == QDialog::Accepted);
233 delete dialog;
234 return accepted;
235}
236
237void TransportManager::initializeTransport(const QString &identifier, Transport *transport)
238{
239 TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(identifier);
240 if (plugin) {
241 plugin->initializeTransport(transport, identifier);
242 }
243}
244
245bool TransportManager::configureTransport(const QString &identifier, Transport *transport, QWidget *parent)
246{
247 TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(identifier);
248 if (plugin) {
249 return plugin->configureTransport(identifier, transport, parent);
250 }
251 return false;
252}
253
255{
256 Transport *t = transportById(transportId, false);
257 if (!t) {
258 return nullptr;
259 }
260 t = t->clone(); // Jobs delete their transports.
261 t->updatePasswordState();
262 TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(t->identifier());
263 if (plugin) {
264 return plugin->createTransportJob(t, t->identifier());
265 }
266 Q_ASSERT(false);
267 return nullptr;
268}
269
271{
272 bool ok = false;
273 Transport *t = nullptr;
274
275 int transportId = transport.toInt(&ok);
276 if (ok) {
277 t = transportById(transportId);
278 }
279
280 if (!t) {
281 t = transportByName(transport, false);
282 }
283
284 if (t) {
285 return createTransportJob(t->id());
286 }
287
288 return nullptr;
289}
290
292{
293 return d->transports.isEmpty();
294}
295
297{
299 rv.reserve(d->transports.count());
300 for (Transport *t : std::as_const(d->transports)) {
301 rv << t->id();
302 }
303 return rv;
304}
305
307{
309 rv.reserve(d->transports.count());
310 for (Transport *t : std::as_const(d->transports)) {
311 rv << t->name();
312 }
313 return rv;
314}
315
317{
318 Transport *t = transportById(d->defaultTransportId, false);
319 if (t) {
320 return t->name();
321 }
322 return {};
323}
324
326{
327 return d->defaultTransportId;
328}
329
331{
332 if (id == d->defaultTransportId || !transportById(id, false)) {
333 return;
334 }
335 d->defaultTransportId = id;
336 d->writeConfig();
337}
338
339void TransportManager::removePasswordFromWallet(Transport::Id id)
340{
341 auto deleteJob = new DeletePasswordJob(WALLET_FOLDER);
342 deleteJob->setKey(QString::number(id));
343 deleteJob->start();
344}
345
347{
348 Transport *t = transportById(id, false);
349 if (!t) {
350 return;
351 }
352 auto plugin = MailTransport::TransportPluginManager::self()->plugin(t->identifier());
353 if (plugin) {
354 plugin->cleanUp(t);
355 }
356 Q_EMIT transportRemoved(t->id(), t->name());
357
358 d->transports.removeAll(t);
359 d->validateDefault();
360 const QString group = t->currentGroup();
361 if (t->storePassword()) {
362 auto deleteJob = new DeletePasswordJob(WALLET_FOLDER);
363 deleteJob->setKey(QString::number(t->id()));
364 deleteJob->start();
365 }
366 delete t;
367 d->config->deleteGroup(group);
368 d->writeConfig();
369}
370
371void TransportManagerPrivate::readConfig()
372{
373 QList<Transport *> oldTransports = transports;
374 transports.clear();
375
376 static QRegularExpression re(QStringLiteral("^Transport (.+)$"));
377 const QStringList groups = config->groupList().filter(re);
378 for (const QString &s : groups) {
379 const QRegularExpressionMatch match = re.match(s);
380 if (!match.hasMatch()) {
381 continue;
382 }
383 Transport *t = nullptr;
384 // see if we happen to have that one already
385 const QString capturedString = match.captured(1);
386 const QString checkString = QLatin1StringView("Transport ") + capturedString;
387 for (Transport *old : oldTransports) {
388 if (old->currentGroup() == checkString) {
389 qCDebug(MAILTRANSPORT_LOG) << "reloading existing transport:" << s;
390 t = old;
391 t->load();
392 oldTransports.removeAll(old);
393 break;
394 }
395 }
396
397 if (!t) {
398 t = new Transport(capturedString);
399 }
400 if (t->id() <= 0) {
401 t->setId(createId());
402 t->save();
403 }
404 transports.append(t);
405 }
406
407 qDeleteAll(oldTransports);
408 oldTransports.clear();
409 // read default transport
410 KConfigGroup group(config, QStringLiteral("General"));
411 defaultTransportId = group.readEntry("default-transport", 0);
412 if (defaultTransportId == 0) {
413 // migrated default transport contains the name instead
414 QString name = group.readEntry("default-transport", QString());
415 if (!name.isEmpty()) {
416 Transport *t = q->transportByName(name, false);
417 if (t) {
418 defaultTransportId = t->id();
419 writeConfig();
420 }
421 }
422 }
423 validateDefault();
424 migrateToWallet();
426}
427
428void TransportManagerPrivate::writeConfig()
429{
430 KConfigGroup group(config, QStringLiteral("General"));
431 group.writeEntry("default-transport", defaultTransportId);
432 config->sync();
433 q->emitChangesCommitted();
434}
435
436void TransportManagerPrivate::fillTypes()
437{
438 Q_ASSERT(types.isEmpty());
439
440 updatePluginList();
441 QObject::connect(MailTransport::TransportPluginManager::self(), &TransportPluginManager::updatePluginList, q, &TransportManager::updatePluginList);
442}
443
444void TransportManagerPrivate::updatePluginList()
445{
446 types.clear();
447 const QList<MailTransport::TransportAbstractPlugin *> lstPlugins = MailTransport::TransportPluginManager::self()->pluginsList();
448 for (MailTransport::TransportAbstractPlugin *plugin : lstPlugins) {
449 if (plugin->names().isEmpty()) {
450 qCDebug(MAILTRANSPORT_LOG) << "Plugin " << plugin << " doesn't provide plugin";
451 }
452 const QList<TransportAbstractPluginInfo> lstInfos = plugin->names();
453 for (const MailTransport::TransportAbstractPluginInfo &info : lstInfos) {
455 type.d->mName = info.name;
456 type.d->mDescription = info.description;
457 type.d->mIdentifier = info.identifier;
458 type.d->mIsAkonadiResource = info.isAkonadi;
459 types << type;
460 }
461 }
462}
463
464void TransportManager::updatePluginList()
465{
466 d->updatePluginList();
467}
468
469void TransportManager::emitChangesCommitted()
470{
471 d->myOwnChange = true; // prevent us from reading our changes again
472 d->appliedChange = false; // but we have to read them at least once
475}
476
477void TransportManagerPrivate::slotTransportsChanged()
478{
479 if (myOwnChange && appliedChange) {
480 myOwnChange = false;
481 appliedChange = false;
482 return;
483 }
484
485 qCDebug(MAILTRANSPORT_LOG);
486 config->reparseConfiguration();
487 // FIXME: this deletes existing transport objects!
488 readConfig();
489 appliedChange = true; // to prevent recursion
490 Q_EMIT q->transportsChanged();
491}
492
493Transport::Id TransportManagerPrivate::createId() const
494{
495 QList<int> usedIds;
496 usedIds.reserve(transports.size());
497 for (Transport *t : std::as_const(transports)) {
498 usedIds << t->id();
499 }
500 int newId;
501 do {
502 // 0 is default for unknown, so use 1 as lower value accepted
503 newId = QRandomGenerator::global()->bounded(1, RAND_MAX);
504 } while (usedIds.contains(newId));
505 return newId;
506}
507
509{
510 QEventLoop loop;
511 for (Transport *t : std::as_const(d->transports)) {
512 if (d->passwordConnections.contains(t)) {
513 continue;
514 }
515 auto conn = connect(t, &Transport::passwordLoaded, this, [&]() {
516 disconnect(d->passwordConnections[t]);
517 d->passwordConnections.remove(t);
518 if (d->passwordConnections.count() == 0) {
519 loop.exit();
520 }
521 });
522 d->passwordConnections[t] = conn;
523 t->readPassword();
524 }
525 loop.exec();
526
527 d->startQueuedJobs();
529}
530
532{
533 for (Transport *t : std::as_const(d->transports)) {
534 if (!t->isComplete()) {
535 if (d->passwordConnections.contains(t)) {
536 continue;
537 }
538 auto conn = connect(t, &Transport::passwordLoaded, this, [&]() {
539 disconnect(d->passwordConnections[t]);
540 d->passwordConnections.remove(t);
541 if (d->passwordConnections.count() == 0) {
542 d->startQueuedJobs();
544 }
545 });
546 d->passwordConnections[t] = conn;
547 t->readPassword();
548 }
549 }
550}
551
552void TransportManagerPrivate::startQueuedJobs()
553{
554 QList<TransportJob *> jobsToDel;
555 for (auto job : walletQueue) {
556 if (job->transport()->isComplete()) {
557 job->start();
558 jobsToDel << job;
559 }
560 }
561
562 for (auto job : jobsToDel) {
563 walletQueue.removeAll(job);
564 }
565}
566
567void TransportManagerPrivate::validateDefault()
568{
569 if (!q->transportById(defaultTransportId, false)) {
570 if (q->isEmpty()) {
571 defaultTransportId = -1;
572 } else {
573 defaultTransportId = transports.constFirst()->id();
574 writeConfig();
575 }
576 }
577}
578
579void TransportManagerPrivate::migrateToWallet()
580{
581 // check if we tried this already
582 static bool firstRun = true;
583 if (!firstRun) {
584 return;
585 }
586 firstRun = false;
587
588 // check if we are the main instance
589 if (!isMainInstance) {
590 return;
591 }
592
593 // check if migration is needed
594 QStringList names;
595 for (Transport *t : std::as_const(transports)) {
596 if (t->needsWalletMigration()) {
597 names << t->name();
598 }
599 }
600 if (names.isEmpty()) {
601 return;
602 }
603
604 // ask user if he wants to migrate
605 int result = KMessageBox::questionTwoActionsList(nullptr,
606 i18n("The following mail transports store their passwords in an "
607 "unencrypted configuration file.\nFor security reasons, "
608 "please consider migrating these passwords to KWallet, the "
609 "KDE Wallet management tool,\nwhich stores sensitive data "
610 "for you in a strongly encrypted file.\n"
611 "Do you want to migrate your passwords to KWallet?"),
612 names,
613 i18n("Question"),
614 KGuiItem(i18n("Migrate")),
615 KGuiItem(i18n("Keep")),
616 QStringLiteral("WalletMigrate"));
617 if (result != KMessageBox::ButtonCode::PrimaryAction) {
618 return;
619 }
620
621 // perform migration
622 for (Transport *t : std::as_const(transports)) {
623 if (t->needsWalletMigration()) {
624 t->migrateToWallet();
625 }
626 }
627}
628
629void TransportManagerPrivate::dbusServiceUnregistered()
630{
631 QDBusConnection::sessionBus().registerService(DBUS_SERVICE_NAME);
632}
633
634void TransportManagerPrivate::jobResult(KJob *job)
635{
636 walletQueue.removeAll(static_cast<TransportJob *>(job));
637}
638
639#include "moc_transportmanager.cpp"
void reparseConfiguration()
bool sync() override
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.
A representation of a transport type.
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.
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)
KGuiItem cancel()
QString name(StandardShortcut id)
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
qsizetype removeAll(const AT &t)
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
T qobject_cast(QObject *object)
double bounded(double highest)
QRandomGenerator * global()
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
The TransportAbstractPluginInfo struct.
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:12:37 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.