File indexing completed on 2024-12-22 04:56:51
0001 /* 0002 SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "sendjob.h" 0008 0009 #include "storeresultjob.h" 0010 0011 #include "maildispatcher_debug.h" 0012 #include <Akonadi/AddressAttribute> 0013 #include <Akonadi/AgentInstance> 0014 #include <Akonadi/AgentManager> 0015 #include <Akonadi/Collection> 0016 #include <Akonadi/CollectionFetchJob> 0017 #include <Akonadi/ItemDeleteJob> 0018 #include <Akonadi/ItemModifyJob> 0019 #include <Akonadi/ItemMoveJob> 0020 #include <Akonadi/MessageParts> 0021 #include <Akonadi/SentBehaviourAttribute> 0022 #include <Akonadi/ServerManager> 0023 #include <Akonadi/SpecialMailCollections> 0024 #include <Akonadi/TransportAttribute> 0025 #include <Akonadi/TransportResourceBase> 0026 #include <KLocalizedString> 0027 #include <MailTransport/Transport> 0028 #include <MailTransport/TransportJob> 0029 #include <MailTransport/TransportManager> 0030 0031 #include <KMime/Message> 0032 0033 #include <QDBusConnection> 0034 #include <QDBusInterface> 0035 #include <QDBusReply> 0036 #include <QTimer> 0037 0038 using namespace Akonadi; 0039 using namespace KMime; 0040 using namespace MailTransport; 0041 0042 void SendJob::doTransport() 0043 { 0044 qCDebug(MAILDISPATCHER_LOG) << "Transporting message."; 0045 0046 if (mAborting) { 0047 qCDebug(MAILDISPATCHER_LOG) << "Marking message as aborted."; 0048 setError(UserDefinedError); 0049 setErrorText(i18n("Message sending aborted.")); 0050 storeResult(false, i18n("Message sending aborted.")); 0051 return; 0052 } 0053 0054 // Is it an Akonadi transport or a traditional one? 0055 const TransportAttribute *transportAttribute = mItem.attribute<TransportAttribute>(); 0056 Q_ASSERT(transportAttribute); 0057 const auto transport = TransportManager::self()->transportById(transportAttribute->transportId(), false); 0058 if (!transport) { 0059 storeResult(false, i18n("Could not initiate message transport. Possibly invalid transport.")); 0060 return; 0061 } 0062 0063 const TransportType type = transport->transportType(); 0064 if (!type.isValid()) { 0065 storeResult(false, i18n("Could not send message. Invalid transport.")); 0066 return; 0067 } 0068 0069 if (!filterItem(8)) { // BeforeOutbound 0070 return; 0071 } 0072 0073 if (type.isAkonadiResource()) { 0074 // Send the item directly to the resource that will send it. 0075 mResourceId = transport->host(); 0076 doAkonadiTransport(); 0077 } else { 0078 // Use a traditional transport job. 0079 doTraditionalTransport(); 0080 } 0081 } 0082 0083 void SendJob::doAkonadiTransport() 0084 { 0085 Q_ASSERT(!mResourceId.isEmpty()); 0086 Q_ASSERT(mInterface == nullptr); 0087 0088 const auto service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Resource, mResourceId); 0089 0090 mInterface = new QDBusInterface(service, 0091 QStringLiteral("/Transport"), 0092 QStringLiteral("org.freedesktop.Akonadi.Resource.Transport"), 0093 QDBusConnection::sessionBus(), 0094 this); 0095 0096 if (!mInterface->isValid()) { 0097 storeResult(false, i18n("Failed to get D-Bus interface of resource %1.", mResourceId)); 0098 delete mInterface; 0099 mInterface = nullptr; 0100 return; 0101 } 0102 0103 // Signals. 0104 QObject::connect(AgentManager::self(), &AgentManager::instanceProgressChanged, this, &SendJob::resourceProgress); 0105 QObject::connect(mInterface, SIGNAL(transportResult(qlonglong, int, QString)), this, SLOT(resourceResult(qlonglong, int, QString))); 0106 0107 // Start sending. 0108 const QDBusReply<void> reply = mInterface->call(QStringLiteral("send"), mItem.id()); 0109 if (!reply.isValid()) { 0110 storeResult(false, i18n("Invalid D-Bus reply from resource %1.", mResourceId)); 0111 return; 0112 } 0113 } 0114 0115 void SendJob::doTraditionalTransport() 0116 { 0117 const TransportAttribute *transportAttribute = mItem.attribute<TransportAttribute>(); 0118 Q_ASSERT(transportAttribute); 0119 TransportJob *job = TransportManager::self()->createTransportJob(transportAttribute->transportId()); 0120 0121 Q_ASSERT(job); 0122 Q_ASSERT(mCurrentJob == nullptr); 0123 0124 mCurrentJob = job; 0125 0126 // Message. 0127 Q_ASSERT(mItem.hasPayload<Message::Ptr>()); 0128 const auto message = mItem.payload<Message::Ptr>(); 0129 bool needAssemble = false; 0130 if (message->removeHeader("Bcc")) { 0131 needAssemble = true; 0132 } 0133 if (message->removeHeader("X-KMail-Identity")) { 0134 needAssemble = true; 0135 } 0136 if (message->removeHeader("X-KMail-Dictionary")) { 0137 needAssemble = true; 0138 } 0139 if (message->removeHeader("X-KMail-Transport")) { 0140 needAssemble = true; 0141 } 0142 if (message->removeHeader("X-KMail-Fcc")) { 0143 needAssemble = true; 0144 } 0145 if (message->removeHeader("X-KMail-Identity-Name")) { 0146 needAssemble = true; 0147 } 0148 if (message->removeHeader("X-KMail-Transport-Name")) { 0149 needAssemble = true; 0150 } 0151 0152 if (needAssemble) { 0153 message->assemble(); 0154 } 0155 const QByteArray content = message->encodedContent(true) + "\r\n"; 0156 Q_ASSERT(!content.isEmpty()); 0157 0158 // Addresses. 0159 const AddressAttribute *addressAttribute = mItem.attribute<AddressAttribute>(); 0160 Q_ASSERT(addressAttribute); 0161 0162 job->setData(content); 0163 job->setSender(addressAttribute->from()); 0164 job->setTo(addressAttribute->to()); 0165 job->setCc(addressAttribute->cc()); 0166 job->setBcc(addressAttribute->bcc()); 0167 job->setDeliveryStatusNotification(addressAttribute->deliveryStatusNotification()); 0168 0169 // Signals. 0170 connect(job, &TransportJob::result, this, &SendJob::transportResult); 0171 connect(job, &TransportJob::percentChanged, this, [this](KJob *job, ulong val) { 0172 transportPercent(job, val); 0173 }); 0174 job->start(); 0175 } 0176 0177 void SendJob::transportPercent(KJob *job, unsigned long) 0178 { 0179 Q_ASSERT(mCurrentJob == job); 0180 qCDebug(MAILDISPATCHER_LOG) << "Processed amount" << job->processedAmount(KJob::Bytes) << "total amount" << job->totalAmount(KJob::Bytes); 0181 0182 setTotalAmount(KJob::Bytes, job->totalAmount(KJob::Bytes)); // Is not set at the time of start(). 0183 setProcessedAmount(KJob::Bytes, job->processedAmount(KJob::Bytes)); 0184 } 0185 0186 void SendJob::transportResult(KJob *job) 0187 { 0188 Q_ASSERT(mCurrentJob == job); 0189 mCurrentJob = nullptr; 0190 doPostJob(!job->error(), job->errorString()); 0191 } 0192 0193 void SendJob::resourceProgress(const AgentInstance &instance) 0194 { 0195 if (!mInterface) { 0196 // We might have gotten a very late signal. 0197 qCWarning(MAILDISPATCHER_LOG) << "called but no resource job running!"; 0198 return; 0199 } 0200 0201 if (instance.identifier() == mResourceId) { 0202 // This relies on the resource's progress representing the progress of 0203 // sending this item. 0204 setPercent(instance.progress()); 0205 } 0206 } 0207 0208 void SendJob::resourceResult(qlonglong itemId, int result, const QString &message) 0209 { 0210 Q_UNUSED(itemId) 0211 Q_ASSERT(mInterface); 0212 delete mInterface; // So that abort() knows the transport job is over. 0213 mInterface = nullptr; 0214 0215 const auto transportResult = static_cast<TransportResourceBase::TransportResult>(result); 0216 0217 const bool success = (transportResult == TransportResourceBase::TransportSucceeded); 0218 0219 Q_ASSERT(itemId == mItem.id()); 0220 doPostJob(success, message); 0221 } 0222 0223 void SendJob::abortPostJob() 0224 { 0225 // We were unlucky and LocalFolders is recreating its stuff right now. 0226 // We will not wait for it. 0227 qCWarning(MAILDISPATCHER_LOG) << "Default sent mail collection unavailable, not moving the mail after sending."; 0228 setError(UserDefinedError); 0229 setErrorText(i18n("Default sent-mail folder unavailable. Keeping message in outbox.")); 0230 storeResult(false, errorString()); 0231 } 0232 0233 void SendJob::doPostJob(bool transportSuccess, const QString &transportMessage) 0234 { 0235 qCDebug(MAILDISPATCHER_LOG) << "success" << transportSuccess << "message" << transportMessage; 0236 0237 if (!transportSuccess) { 0238 qCDebug(MAILDISPATCHER_LOG) << "Error transporting."; 0239 setError(UserDefinedError); 0240 0241 const QString error = mAborting ? i18n("Message transport aborted.") : i18n("Failed to transport message."); 0242 0243 setErrorText(error + QLatin1Char(' ') + transportMessage); 0244 storeResult(false, errorString()); 0245 } else { 0246 qCDebug(MAILDISPATCHER_LOG) << "Success transporting."; 0247 0248 // Delete or move to sent-mail. 0249 const SentBehaviourAttribute *attribute = mItem.attribute<SentBehaviourAttribute>(); 0250 Q_ASSERT(attribute); 0251 0252 if (attribute->sentBehaviour() == SentBehaviourAttribute::Delete) { 0253 qCDebug(MAILDISPATCHER_LOG) << "Deleting item from outbox."; 0254 mCurrentJob = new ItemDeleteJob(mItem); 0255 QObject::connect(mCurrentJob, &ItemDeleteJob::result, this, &SendJob::postJobResult); 0256 } else { 0257 if (attribute->sentBehaviour() == SentBehaviourAttribute::MoveToDefaultSentCollection) { 0258 if (SpecialMailCollections::self()->hasDefaultCollection(SpecialMailCollections::SentMail)) { 0259 mCurrentJob = new ItemMoveJob(mItem, SpecialMailCollections::self()->defaultCollection(SpecialMailCollections::SentMail), this); 0260 QObject::connect(mCurrentJob, &ItemMoveJob::result, this, &SendJob::postJobResult); 0261 } else { 0262 abortPostJob(); 0263 } 0264 } else { 0265 qCDebug(MAILDISPATCHER_LOG) << "sentBehaviour=" << attribute->sentBehaviour() << "using collection from attribute"; 0266 mCurrentJob = new CollectionFetchJob(attribute->moveToCollection(), Akonadi::CollectionFetchJob::Base); 0267 QObject::connect(mCurrentJob, &CollectionFetchJob::result, this, &SendJob::slotSentMailCollectionFetched); 0268 } 0269 } 0270 } 0271 } 0272 0273 bool SendJob::filterItem(int filterset) 0274 { 0275 const auto service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Agent, QStringLiteral("akonadi_mailfilter_agent")); 0276 0277 QDBusInterface iface(service, 0278 QStringLiteral("/MailFilterAgent"), 0279 QStringLiteral("org.freedesktop.Akonadi.MailFilterAgent"), 0280 QDBusConnection::sessionBus(), 0281 this); 0282 if (!iface.isValid()) { 0283 storeResult(false, i18n("Failed to get D-Bus interface of mailfilteragent.")); 0284 return false; 0285 } 0286 0287 // Outbound = 0x2 0288 const QDBusReply<void> reply = iface.call(QStringLiteral("filterItem"), mItem.id(), filterset, QString()); 0289 if (!reply.isValid()) { 0290 storeResult(false, i18n("Invalid D-Bus reply from mailfilteragent")); 0291 return false; 0292 } 0293 0294 return true; 0295 } 0296 0297 void SendJob::slotSentMailCollectionFetched(KJob *job) 0298 { 0299 Akonadi::Collection fetchCol; 0300 bool ok = false; 0301 if (!job->error()) { 0302 const CollectionFetchJob *const fetchJob = qobject_cast<CollectionFetchJob *>(job); 0303 if (!fetchJob->collections().isEmpty()) { 0304 fetchCol = fetchJob->collections().at(0); 0305 ok = true; 0306 } 0307 } 0308 if (!ok) { 0309 if (!SpecialMailCollections::self()->hasDefaultCollection(SpecialMailCollections::SentMail)) { 0310 abortPostJob(); 0311 return; 0312 } 0313 fetchCol = SpecialMailCollections::self()->defaultCollection(SpecialMailCollections::SentMail); 0314 } 0315 mCurrentJob = new ItemMoveJob(mItem, fetchCol, this); 0316 QObject::connect(mCurrentJob, &ItemMoveJob::result, this, &SendJob::postJobResult); 0317 } 0318 0319 void SendJob::postJobResult(KJob *job) 0320 { 0321 Q_ASSERT(mCurrentJob == job); 0322 mCurrentJob = nullptr; 0323 const SentBehaviourAttribute *attribute = mItem.attribute<SentBehaviourAttribute>(); 0324 Q_ASSERT(attribute); 0325 0326 if (job->error()) { 0327 qCDebug(MAILDISPATCHER_LOG) << "Error deleting or moving to sent-mail."; 0328 0329 QString errorStr; 0330 switch (attribute->sentBehaviour()) { 0331 case SentBehaviourAttribute::Delete: 0332 errorStr = i18n("Sending succeeded, but failed to remove the message from the outbox."); 0333 break; 0334 default: 0335 errorStr = i18n("Sending succeeded, but failed to move the message to the sent-mail folder."); 0336 break; 0337 } 0338 setError(UserDefinedError); 0339 setErrorText(errorStr + QLatin1Char(' ') + job->errorString()); 0340 storeResult(false, errorString()); 0341 } else { 0342 qCDebug(MAILDISPATCHER_LOG) << "Success deleting or moving to sent-mail."; 0343 if (!filterItem(2)) { // Outbound 0344 return; 0345 } 0346 if (attribute->sentBehaviour() == SentBehaviourAttribute::Delete) { 0347 emitResult(); 0348 } else { 0349 storeResult(true); 0350 } 0351 } 0352 } 0353 0354 void SendJob::storeResult(bool success, const QString &message) 0355 { 0356 qCDebug(MAILDISPATCHER_LOG) << "success" << success << "message" << message; 0357 0358 Q_ASSERT(mCurrentJob == nullptr); 0359 mCurrentJob = new StoreResultJob(mItem, success, message); 0360 connect(mCurrentJob, &StoreResultJob::result, this, &SendJob::doEmitResult); 0361 } 0362 0363 void SendJob::doEmitResult(KJob *job) 0364 { 0365 Q_ASSERT(mCurrentJob == job); 0366 mCurrentJob = nullptr; 0367 0368 if (job->error()) { 0369 qCWarning(MAILDISPATCHER_LOG) << "Error storing result."; 0370 setError(UserDefinedError); 0371 setErrorText(errorString() + QLatin1Char(' ') + i18n("Failed to store result in item.") + QLatin1Char(' ') + job->errorString()); 0372 } else { 0373 qCDebug(MAILDISPATCHER_LOG) << "Success storing result."; 0374 // It is still possible that the transport failed. 0375 auto srJob = static_cast<StoreResultJob *>(job); 0376 if (!srJob->success()) { 0377 setError(UserDefinedError); 0378 setErrorText(srJob->message()); 0379 } 0380 } 0381 0382 emitResult(); 0383 } 0384 0385 SendJob::SendJob(const Item &item, QObject *parent) 0386 : KJob(parent) 0387 , mItem(item) 0388 { 0389 } 0390 0391 SendJob::~SendJob() = default; 0392 0393 void SendJob::start() 0394 { 0395 QTimer::singleShot(0, this, &SendJob::doTransport); 0396 } 0397 0398 void SendJob::setMarkAborted() 0399 { 0400 Q_ASSERT(!mAborting); 0401 mAborting = true; 0402 } 0403 0404 void SendJob::abort() 0405 { 0406 setMarkAborted(); 0407 0408 if (dynamic_cast<TransportJob *>(mCurrentJob)) { 0409 qCDebug(MAILDISPATCHER_LOG) << "Abort called, active transport job."; 0410 // Abort transport. 0411 mCurrentJob->kill(KJob::EmitResult); 0412 } else if (mInterface != nullptr) { 0413 qCDebug(MAILDISPATCHER_LOG) << "Abort called, propagating to resource."; 0414 // Abort resource doing transport. 0415 AgentInstance instance = AgentManager::self()->instance(mResourceId); 0416 instance.abortCurrentTask(); 0417 } else { 0418 qCDebug(MAILDISPATCHER_LOG) << "Abort called, but no transport job is active."; 0419 // Either transport has not started, in which case doTransport will 0420 // mark the item as aborted, or the item has already been sent, in which 0421 // case there is nothing we can do. 0422 } 0423 } 0424 0425 #include "moc_sendjob.cpp"