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;
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()
94 {
95 }
96};
97
98StaticTransportManager *sSelf = nullptr;
99
100static void destroyStaticTransportManager()
101{
102 delete sSelf;
103}
104
106 : QObject()
107 , d(new TransportManagerPrivate(this))
108{
109 qAddPostRoutine(destroyStaticTransportManager);
110 d->config = new KConfig(QStringLiteral("mailtransports"));
111
113
115 connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, [this]() {
116 d->dbusServiceUnregistered();
117 });
118
119 QDBusConnection::sessionBus().connect(QString(), QString(), DBUS_INTERFACE_NAME, DBUS_CHANGE_SIGNAL, this, SLOT(slotTransportsChanged()));
120
121 d->isMainInstance = QDBusConnection::sessionBus().registerService(DBUS_SERVICE_NAME);
122
123 d->fillTypes();
124}
125
127{
128 qRemovePostRoutine(destroyStaticTransportManager);
129}
130
132{
133 if (!sSelf) {
134 sSelf = new StaticTransportManager;
135 sSelf->d->readConfig();
136 }
137 return sSelf;
138}
139
140Transport *TransportManager::transportById(Transport::Id id, bool def) const
141{
142 for (Transport *t : std::as_const(d->transports)) {
143 if (t->id() == id) {
144 return t;
145 }
146 }
147
148 if (def || (id == 0 && d->defaultTransportId != id)) {
149 return transportById(d->defaultTransportId, false);
150 }
151 return nullptr;
152}
153
155{
156 for (Transport *t : std::as_const(d->transports)) {
157 if (t->name() == name) {
158 return t;
159 }
160 }
161 if (def) {
162 return transportById(0, false);
163 }
164 return nullptr;
165}
166
168{
169 return d->transports;
170}
171
173{
174 return d->types;
175}
176
178{
179 auto id = d->createId();
180 auto t = new Transport(QString::number(id));
181 t->setId(id);
182 return t;
183}
184
186{
187 if (d->transports.contains(transport)) {
188 qCDebug(MAILTRANSPORT_LOG) << "Already have this transport.";
189 return;
190 }
191
192 qCDebug(MAILTRANSPORT_LOG) << "Added transport" << transport;
193 d->transports.append(transport);
194 d->validateDefault();
195 d->writeConfig();
196 emitChangesCommitted();
197}
198
200{
201 connect(job, &TransportJob::result, this, [this](KJob *job) {
202 d->jobResult(job);
203 });
204
205 // check if the job is waiting for the wallet
206 if (!job->transport()->isComplete()) {
207 qCDebug(MAILTRANSPORT_LOG) << "job waits for wallet:" << job;
208 d->walletQueue << job;
210 return;
211 }
212
213 job->start();
214}
215
217{
218 if (showCondition == IfNoTransportExists) {
219 if (!isEmpty()) {
220 return true;
221 }
222
223 const int response = KMessageBox::warningContinueCancel(parent,
224 i18n("You must create an outgoing account before sending."),
225 i18nc("@title:window", "Create Account Now?"),
226 KGuiItem(i18nc("@action:button", "Create Account Now")),
228 if (response != KMessageBox::Continue) {
229 return false;
230 }
231 }
232
234 const bool accepted = (dialog->exec() == QDialog::Accepted);
235 delete dialog;
236 return accepted;
237}
238
239void TransportManager::initializeTransport(const QString &identifier, Transport *transport)
240{
241 TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(identifier);
242 if (plugin) {
243 plugin->initializeTransport(transport, identifier);
244 }
245}
246
247bool TransportManager::configureTransport(const QString &identifier, Transport *transport, QWidget *parent)
248{
249 TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(identifier);
250 if (plugin) {
251 return plugin->configureTransport(identifier, transport, parent);
252 }
253 return false;
254}
255
257{
258 Transport *t = transportById(transportId, false);
259 if (!t) {
260 return nullptr;
261 }
262 t = t->clone(); // Jobs delete their transports.
264 TransportAbstractPlugin *plugin = TransportPluginManager::self()->plugin(t->identifier());
265 if (plugin) {
266 return plugin->createTransportJob(t, t->identifier());
267 }
268 Q_ASSERT(false);
269 return nullptr;
270}
271
273{
274 bool ok = false;
275 Transport *t = nullptr;
276
277 int transportId = transport.toInt(&ok);
278 if (ok) {
279 t = transportById(transportId);
280 }
281
282 if (!t) {
283 t = transportByName(transport, false);
284 }
285
286 if (t) {
287 return createTransportJob(t->id());
288 }
289
290 return nullptr;
291}
292
294{
295 return d->transports.isEmpty();
296}
297
299{
300 QList<int> rv;
301 rv.reserve(d->transports.count());
302 for (Transport *t : std::as_const(d->transports)) {
303 rv << t->id();
304 }
305 return rv;
306}
307
309{
310 QStringList rv;
311 rv.reserve(d->transports.count());
312 for (Transport *t : std::as_const(d->transports)) {
313 rv << t->name();
314 }
315 return rv;
316}
317
319{
320 Transport *t = transportById(d->defaultTransportId, false);
321 if (t) {
322 return t->name();
323 }
324 return {};
325}
326
328{
329 return d->defaultTransportId;
330}
331
333{
334 if (id == d->defaultTransportId || !transportById(id, false)) {
335 return;
336 }
337 d->defaultTransportId = id;
338 d->writeConfig();
339}
340
341void TransportManager::removePasswordFromWallet(Transport::Id id)
342{
343 auto deleteJob = new DeletePasswordJob(WALLET_FOLDER);
344 deleteJob->setKey(QString::number(id));
345 deleteJob->start();
346}
347
349{
350 Transport *t = transportById(id, false);
351 if (!t) {
352 return;
353 }
354 auto plugin = MailTransport::TransportPluginManager::self()->plugin(t->identifier());
355 if (plugin) {
356 plugin->cleanUp(t);
357 }
358 Q_EMIT transportRemoved(t->id(), t->name());
359
360 d->transports.removeAll(t);
361 d->validateDefault();
362 const QString group = t->currentGroup();
363 if (t->storePassword()) {
364 auto deleteJob = new DeletePasswordJob(WALLET_FOLDER);
365 deleteJob->setKey(QString::number(t->id()));
366 deleteJob->start();
367 }
368 delete t;
369 d->config->deleteGroup(group);
370 d->writeConfig();
371}
372
373void TransportManagerPrivate::readConfig()
374{
375 QList<Transport *> oldTransports = transports;
376 transports.clear();
377
378 static QRegularExpression re(QStringLiteral("^Transport (.+)$"));
379 const QStringList groups = config->groupList().filter(re);
380 for (const QString &s : groups) {
381 const QRegularExpressionMatch match = re.match(s);
382 if (!match.hasMatch()) {
383 continue;
384 }
385 Transport *t = nullptr;
386 // see if we happen to have that one already
387 const QString capturedString = match.captured(1);
388 const QString checkString = "Transport "_L1 + capturedString;
389 for (Transport *old : oldTransports) {
390 if (old->currentGroup() == checkString) {
391 qCDebug(MAILTRANSPORT_LOG) << "reloading existing transport:" << s;
392 t = old;
393 t->load();
394 oldTransports.removeAll(old);
395 break;
396 }
397 }
398
399 if (!t) {
400 t = new Transport(capturedString);
401 }
402 if (t->id() <= 0) {
403 t->setId(createId());
404 t->save();
405 }
406 transports.append(t);
407 }
408
409 qDeleteAll(oldTransports);
410 oldTransports.clear();
411 // read default transport
412 KConfigGroup group(config, QStringLiteral("General"));
413 defaultTransportId = group.readEntry("default-transport", 0);
414 if (defaultTransportId == 0) {
415 // migrated default transport contains the name instead
416 QString name = group.readEntry("default-transport", QString());
417 if (!name.isEmpty()) {
418 Transport *t = q->transportByName(name, false);
419 if (t) {
420 defaultTransportId = t->id();
421 writeConfig();
422 }
423 }
424 }
425 validateDefault();
426 migrateToWallet();
428}
429
430void TransportManagerPrivate::writeConfig()
431{
432 KConfigGroup group(config, QStringLiteral("General"));
433 group.writeEntry("default-transport", defaultTransportId);
434 config->sync();
435 q->emitChangesCommitted();
436}
437
438void TransportManagerPrivate::fillTypes()
439{
440 Q_ASSERT(types.isEmpty());
441
442 updatePluginList();
443 QObject::connect(MailTransport::TransportPluginManager::self(), &TransportPluginManager::updatePluginList, q, &TransportManager::updatePluginList);
444}
445
446void TransportManagerPrivate::updatePluginList()
447{
448 types.clear();
449 const QList<MailTransport::TransportAbstractPlugin *> lstPlugins = MailTransport::TransportPluginManager::self()->pluginsList();
450 for (MailTransport::TransportAbstractPlugin *plugin : lstPlugins) {
451 if (plugin->names().isEmpty()) {
452 qCDebug(MAILTRANSPORT_LOG) << "Plugin " << plugin << " doesn't provide plugin";
453 }
454 const QList<TransportAbstractPluginInfo> lstInfos = plugin->names();
455 for (const MailTransport::TransportAbstractPluginInfo &info : lstInfos) {
457 type.d->mName = info.name;
458 type.d->mDescription = info.description;
459 type.d->mIdentifier = info.identifier;
460 type.d->mIsAkonadiResource = info.isAkonadi;
461 types << type;
462 }
463 }
464}
465
466void TransportManager::updatePluginList()
467{
468 d->updatePluginList();
469}
470
471void TransportManager::emitChangesCommitted()
472{
473 d->myOwnChange = true; // prevent us from reading our changes again
474 d->appliedChange = false; // but we have to read them at least once
477}
478
479void TransportManagerPrivate::slotTransportsChanged()
480{
481 if (myOwnChange && appliedChange) {
482 myOwnChange = false;
483 appliedChange = false;
484 return;
485 }
486
487 qCDebug(MAILTRANSPORT_LOG);
488 config->reparseConfiguration();
489 // FIXME: this deletes existing transport objects!
490 readConfig();
491 appliedChange = true; // to prevent recursion
492 Q_EMIT q->transportsChanged();
493}
494
495Transport::Id TransportManagerPrivate::createId() const
496{
497 QList<int> usedIds;
498 usedIds.reserve(transports.size());
499 for (Transport *t : std::as_const(transports)) {
500 usedIds << t->id();
501 }
502 int newId;
503 do {
504 // 0 is default for unknown, so use 1 as lower value accepted
505 newId = QRandomGenerator::global()->bounded(1, RAND_MAX);
506 } while (usedIds.contains(newId));
507 return newId;
508}
509
511{
512 QEventLoop loop;
513 for (Transport *t : std::as_const(d->transports)) {
514 if (d->passwordConnections.contains(t)) {
515 continue;
516 }
517 auto conn = connect(t, &Transport::passwordLoaded, this, [&]() {
518 disconnect(d->passwordConnections[t]);
519 d->passwordConnections.remove(t);
520 if (d->passwordConnections.count() == 0) {
521 loop.exit();
522 }
523 });
524 d->passwordConnections[t] = conn;
525 t->readPassword();
526 }
527 loop.exec();
528
529 d->startQueuedJobs();
531}
532
534{
535 for (Transport *t : std::as_const(d->transports)) {
536 if (!t->isComplete()) {
537 if (d->passwordConnections.contains(t)) {
538 continue;
539 }
540 auto conn = connect(t, &Transport::passwordLoaded, this, [&]() {
541 disconnect(d->passwordConnections[t]);
542 d->passwordConnections.remove(t);
543 if (d->passwordConnections.count() == 0) {
544 d->startQueuedJobs();
546 }
547 });
548 d->passwordConnections[t] = conn;
549 t->readPassword();
550 }
551 }
552}
553
554void TransportManagerPrivate::startQueuedJobs()
555{
556 QList<TransportJob *> jobsToDel;
557 for (auto job : walletQueue) {
558 if (job->transport()->isComplete()) {
559 job->start();
560 jobsToDel << job;
561 }
562 }
563
564 for (auto job : jobsToDel) {
565 walletQueue.removeAll(job);
566 }
567}
568
569void TransportManagerPrivate::validateDefault()
570{
571 if (!q->transportById(defaultTransportId, false)) {
572 if (q->isEmpty()) {
573 defaultTransportId = -1;
574 } else {
575 defaultTransportId = transports.constFirst()->id();
576 writeConfig();
577 }
578 }
579}
580
581void TransportManagerPrivate::migrateToWallet()
582{
583 // check if we tried this already
584 static bool firstRun = true;
585 if (!firstRun) {
586 return;
587 }
588 firstRun = false;
589
590 // check if we are the main instance
591 if (!isMainInstance) {
592 return;
593 }
594
595 // check if migration is needed
596 QStringList names;
597 for (Transport *t : std::as_const(transports)) {
598 if (t->needsWalletMigration()) {
599 names << t->name();
600 }
601 }
602 if (names.isEmpty()) {
603 return;
604 }
605
606 // ask user if he wants to migrate
607 int result = KMessageBox::questionTwoActionsList(nullptr,
608 i18n("The following mail transports store their passwords in an "
609 "unencrypted configuration file.\nFor security reasons, "
610 "please consider migrating these passwords to KWallet, the "
611 "KDE Wallet management tool,\nwhich stores sensitive data "
612 "for you in a strongly encrypted file.\n"
613 "Do you want to migrate your passwords to KWallet?"),
614 names,
615 i18nc("@title:window", "Question"),
616 KGuiItem(i18nc("@action:button", "Migrate")),
617 KGuiItem(i18nc("@action:button", "Keep")),
618 QStringLiteral("WalletMigrate"));
619 if (result != KMessageBox::ButtonCode::PrimaryAction) {
620 return;
621 }
622
623 // perform migration
624 for (Transport *t : std::as_const(transports)) {
625 if (t->needsWalletMigration()) {
626 t->migrateToWallet();
627 }
628 }
629}
630
631void TransportManagerPrivate::dbusServiceUnregistered()
632{
633 QDBusConnection::sessionBus().registerService(DBUS_SERVICE_NAME);
634}
635
636void TransportManagerPrivate::jobResult(KJob *job)
637{
638 walletQueue.removeAll(static_cast<TransportJob *>(job));
639}
640
641#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.
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()
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 Mon Nov 18 2024 12:07:17 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.