File indexing completed on 2024-12-22 04:56:52
0001 /* 0002 SPDX-FileCopyrightText: 2013-2024 Laurent Montel <montel@kde.org> 0003 0004 SPDX-FileCopyrightText: 2010 Volker Krause <vkrause@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "newmailnotifieragent.h" 0010 0011 #include "newmailnotificationhistorydialog.h" 0012 #include "newmailnotifieradaptor.h" 0013 #include "newmailnotifieragentsettings.h" 0014 #include "specialnotifierjob.h" 0015 0016 #include <KIdentityManagementCore/IdentityManager> 0017 0018 #include <QDBusConnection> 0019 0020 #include "newmailnotifier_debug.h" 0021 #include <Akonadi/AgentManager> 0022 #include <Akonadi/AttributeFactory> 0023 #include <Akonadi/ChangeRecorder> 0024 #include <Akonadi/CollectionFetchScope> 0025 #include <Akonadi/EntityDisplayAttribute> 0026 #include <Akonadi/EntityHiddenAttribute> 0027 #include <Akonadi/ItemFetchScope> 0028 #include <Akonadi/MessageStatus> 0029 #include <Akonadi/NewMailNotifierAttribute> 0030 #include <Akonadi/ServerManager> 0031 #include <Akonadi/Session> 0032 #include <Akonadi/SpecialMailCollections> 0033 #include <KLocalizedString> 0034 #include <KMime/Message> 0035 #include <KNotification> 0036 #if HAVE_TEXT_TO_SPEECH_SUPPORT 0037 #include <QTextToSpeech> 0038 #endif 0039 #include <KWindowSystem> 0040 using namespace std::chrono_literals; 0041 #include <chrono> 0042 0043 using namespace Akonadi; 0044 0045 NewMailNotifierAgent::NewMailNotifierAgent(const QString &id) 0046 : AgentBase(id) 0047 { 0048 connect(this, &Akonadi::AgentBase::reloadConfiguration, this, &NewMailNotifierAgent::slotReloadConfiguration); 0049 KLocalizedString::setApplicationDomain(QByteArrayLiteral("akonadi_newmailnotifier_agent")); 0050 Akonadi::AttributeFactory::registerAttribute<Akonadi::NewMailNotifierAttribute>(); 0051 new NewMailNotifierAdaptor(this); 0052 0053 NewMailNotifierAgentSettings::instance(KSharedConfig::openConfig()); 0054 mIdentityManager = KIdentityManagementCore::IdentityManager::self(); 0055 connect(mIdentityManager, qOverload<>(&KIdentityManagementCore::IdentityManager::changed), this, &NewMailNotifierAgent::slotIdentitiesChanged); 0056 slotIdentitiesChanged(); 0057 mDefaultIconName = QStringLiteral("kmail"); 0058 0059 QDBusConnection::sessionBus().registerObject(QStringLiteral("/NewMailNotifierAgent"), this, QDBusConnection::ExportAdaptors); 0060 0061 QString service = QStringLiteral("org.freedesktop.Akonadi.NewMailNotifierAgent"); 0062 if (Akonadi::ServerManager::hasInstanceIdentifier()) { 0063 service += QLatin1Char('.') + Akonadi::ServerManager::instanceIdentifier(); 0064 } 0065 QDBusConnection::sessionBus().registerService(service); 0066 0067 connect(Akonadi::AgentManager::self(), &Akonadi::AgentManager::instanceStatusChanged, this, &NewMailNotifierAgent::slotInstanceStatusChanged); 0068 connect(Akonadi::AgentManager::self(), &Akonadi::AgentManager::instanceRemoved, this, &NewMailNotifierAgent::slotInstanceRemoved); 0069 connect(Akonadi::AgentManager::self(), &Akonadi::AgentManager::instanceAdded, this, &NewMailNotifierAgent::slotInstanceAdded); 0070 connect(Akonadi::AgentManager::self(), &Akonadi::AgentManager::instanceNameChanged, this, &NewMailNotifierAgent::slotInstanceNameChanged); 0071 0072 changeRecorder()->setMimeTypeMonitored(KMime::Message::mimeType()); 0073 changeRecorder()->itemFetchScope().setCacheOnly(true); 0074 changeRecorder()->itemFetchScope().setFetchModificationTime(false); 0075 changeRecorder()->fetchCollection(true); 0076 changeRecorder()->setChangeRecordingEnabled(false); 0077 changeRecorder()->ignoreSession(Akonadi::Session::defaultSession()); 0078 changeRecorder()->collectionFetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); 0079 changeRecorder()->setCollectionMonitored(Collection::root(), true); 0080 mTimer.setInterval(5s); // 5secondes 0081 connect(&mTimer, &QTimer::timeout, this, &NewMailNotifierAgent::slotShowNotifications); 0082 0083 if (isActive()) { 0084 mTimer.setSingleShot(true); 0085 } 0086 } 0087 0088 NewMailNotifierAgent::~NewMailNotifierAgent() 0089 { 0090 delete mHistoryNotificationDialog; 0091 } 0092 0093 void NewMailNotifierAgent::slotReloadConfiguration() 0094 { 0095 NewMailNotifierAgentSettings::self()->load(); 0096 } 0097 0098 void NewMailNotifierAgent::slotIdentitiesChanged() 0099 { 0100 mListEmails = mIdentityManager->allEmails(); 0101 } 0102 0103 void NewMailNotifierAgent::doSetOnline(bool online) 0104 { 0105 if (!online) { 0106 clearAll(); 0107 } 0108 } 0109 0110 void NewMailNotifierAgent::setEnableAgent(bool enabled) 0111 { 0112 NewMailNotifierAgentSettings::setEnabled(enabled); 0113 NewMailNotifierAgentSettings::self()->save(); 0114 if (!enabled) { 0115 clearAll(); 0116 } 0117 } 0118 0119 bool NewMailNotifierAgent::enabledAgent() const 0120 { 0121 return NewMailNotifierAgentSettings::enabled(); 0122 } 0123 0124 void NewMailNotifierAgent::clearAll() 0125 { 0126 mNewMails.clear(); 0127 mInstanceNameInProgress.clear(); 0128 } 0129 0130 bool NewMailNotifierAgent::excludeSpecialCollection(const Akonadi::Collection &collection) const 0131 { 0132 if (collection.hasAttribute<Akonadi::EntityHiddenAttribute>()) { 0133 return true; 0134 } 0135 0136 if (collection.hasAttribute<Akonadi::NewMailNotifierAttribute>()) { 0137 if (collection.attribute<Akonadi::NewMailNotifierAttribute>()->ignoreNewMail()) { 0138 return true; 0139 } 0140 } 0141 0142 if (!collection.contentMimeTypes().contains(KMime::Message::mimeType())) { 0143 return true; 0144 } 0145 0146 SpecialMailCollections::Type type = SpecialMailCollections::self()->specialCollectionType(collection); 0147 switch (type) { 0148 case SpecialMailCollections::Invalid: // Not a special collection 0149 case SpecialMailCollections::Inbox: 0150 return false; 0151 default: 0152 return true; 0153 } 0154 } 0155 0156 void NewMailNotifierAgent::itemsRemoved(const Item::List &items) 0157 { 0158 if (!isActive()) { 0159 return; 0160 } 0161 0162 const QHash<Akonadi::Collection, QList<Akonadi::Item::Id>>::iterator end(mNewMails.end()); 0163 for (QHash<Akonadi::Collection, QList<Akonadi::Item::Id>>::iterator it = mNewMails.begin(); it != end; ++it) { 0164 QList<Akonadi::Item::Id> idList = it.value(); 0165 bool itemFound = false; 0166 for (const Item &item : items) { 0167 const int numberOfItemsRemoved = idList.removeAll(item.id()); 0168 if (numberOfItemsRemoved > 0) { 0169 itemFound = true; 0170 } 0171 } 0172 if (itemFound) { 0173 if (mNewMails[it.key()].isEmpty()) { 0174 mNewMails.remove(it.key()); 0175 } else { 0176 mNewMails[it.key()] = idList; 0177 } 0178 } 0179 } 0180 } 0181 0182 void NewMailNotifierAgent::itemsFlagsChanged(const Akonadi::Item::List &items, const QSet<QByteArray> &addedFlags, const QSet<QByteArray> &removedFlags) 0183 { 0184 Q_UNUSED(removedFlags) 0185 0186 if (!isActive()) { 0187 return; 0188 } 0189 for (const Akonadi::Item &item : items) { 0190 const QHash<Akonadi::Collection, QList<Akonadi::Item::Id>>::iterator end(mNewMails.end()); 0191 for (QHash<Akonadi::Collection, QList<Akonadi::Item::Id>>::iterator it = mNewMails.begin(); it != end; ++it) { 0192 QList<Akonadi::Item::Id> idList = it.value(); 0193 if (idList.contains(item.id()) && addedFlags.contains("\\SEEN")) { 0194 idList.removeAll(item.id()); 0195 if (idList.isEmpty()) { 0196 mNewMails.remove(it.key()); 0197 break; 0198 } else { 0199 (*it) = idList; 0200 } 0201 } 0202 } 0203 } 0204 } 0205 0206 void NewMailNotifierAgent::itemsMoved(const Akonadi::Item::List &items, 0207 const Akonadi::Collection &collectionSource, 0208 const Akonadi::Collection &collectionDestination) 0209 { 0210 if (!isActive()) { 0211 return; 0212 } 0213 0214 for (const Akonadi::Item &item : items) { 0215 if (ignoreStatusMail(item)) { 0216 continue; 0217 } 0218 0219 if (excludeSpecialCollection(collectionSource)) { 0220 continue; // outbox, sent-mail, trash, drafts or templates. 0221 } 0222 0223 if (mNewMails.contains(collectionSource)) { 0224 QList<Akonadi::Item::Id> idListFrom = mNewMails[collectionSource]; 0225 const int removeItems = idListFrom.removeAll(item.id()); 0226 if (removeItems > 0) { 0227 if (idListFrom.isEmpty()) { 0228 mNewMails.remove(collectionSource); 0229 } else { 0230 mNewMails[collectionSource] = idListFrom; 0231 } 0232 if (!excludeSpecialCollection(collectionDestination)) { 0233 QList<Akonadi::Item::Id> idListTo = mNewMails[collectionDestination]; 0234 idListTo.append(item.id()); 0235 mNewMails[collectionDestination] = idListTo; 0236 } 0237 } 0238 } 0239 } 0240 } 0241 0242 bool NewMailNotifierAgent::ignoreStatusMail(const Akonadi::Item &item) 0243 { 0244 Akonadi::MessageStatus status; 0245 status.setStatusFromFlags(item.flags()); 0246 if (status.isRead() || status.isSpam() || status.isIgnored()) { 0247 return true; 0248 } 0249 return false; 0250 } 0251 0252 void NewMailNotifierAgent::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) 0253 { 0254 if (!isActive()) { 0255 return; 0256 } 0257 0258 if (excludeSpecialCollection(collection)) { 0259 return; // outbox, sent-mail, trash, drafts or templates. 0260 } 0261 0262 if (ignoreStatusMail(item)) { 0263 return; 0264 } 0265 0266 if (!mTimer.isActive()) { 0267 mTimer.start(); 0268 } 0269 mNewMails[collection].append(item.id()); 0270 } 0271 0272 void NewMailNotifierAgent::showNotNotificationHistoryDialog(qlonglong windowId) 0273 { 0274 if (!mHistoryNotificationDialog) { 0275 mHistoryNotificationDialog = new NewMailNotificationHistoryDialog(nullptr); 0276 mHistoryNotificationDialog->setAttribute(Qt::WA_NativeWindow, true); 0277 } 0278 KWindowSystem::setMainWindow(mHistoryNotificationDialog->windowHandle(), windowId); 0279 mHistoryNotificationDialog->show(); 0280 mHistoryNotificationDialog->raise(); 0281 mHistoryNotificationDialog->activateWindow(); 0282 mHistoryNotificationDialog->setModal(false); 0283 } 0284 0285 void NewMailNotifierAgent::setVerboseMailNotification(bool b) 0286 { 0287 NewMailNotifierAgentSettings::setVerboseNotification(b); 0288 NewMailNotifierAgentSettings::self()->save(); 0289 } 0290 0291 bool NewMailNotifierAgent::verboseMailNotification() const 0292 { 0293 return NewMailNotifierAgentSettings::verboseNotification(); 0294 } 0295 0296 void NewMailNotifierAgent::slotShowNotifications() 0297 { 0298 if (mNewMails.isEmpty()) { 0299 return; 0300 } 0301 0302 if (!isActive()) { 0303 return; 0304 } 0305 0306 if (!mInstanceNameInProgress.isEmpty()) { 0307 // Restart timer until all is done. 0308 mTimer.start(); 0309 return; 0310 } 0311 0312 QList<NewMailNotificationHistoryManager::HistoryFolderInfo> infos; 0313 QString message; 0314 if (NewMailNotifierAgentSettings::verboseNotification()) { 0315 bool hasUniqMessage = true; 0316 Akonadi::Item::Id itemId = -1; 0317 QString currentPath; 0318 QStringList texts; 0319 const int numberOfCollection(mNewMails.count()); 0320 if (numberOfCollection > 1) { 0321 hasUniqMessage = false; 0322 } 0323 0324 QString resourceName; 0325 const QHash<Akonadi::Collection, QList<Akonadi::Item::Id>>::const_iterator end(mNewMails.constEnd()); 0326 for (QHash<Akonadi::Collection, QList<Akonadi::Item::Id>>::const_iterator it = mNewMails.constBegin(); it != end; ++it) { 0327 const auto attr = it.key().attribute<Akonadi::EntityDisplayAttribute>(); 0328 QString displayName; 0329 if (attr && !attr->displayName().isEmpty()) { 0330 displayName = attr->displayName(); 0331 } else { 0332 displayName = it.key().name(); 0333 } 0334 0335 const QString resource = it.key().resource(); 0336 resourceName = mCacheResourceName.value(resource); 0337 if (resourceName.isEmpty()) { 0338 const Akonadi::AgentInstance::List lst = Akonadi::AgentManager::self()->instances(); 0339 for (const Akonadi::AgentInstance &instance : lst) { 0340 if (instance.identifier() == resource) { 0341 mCacheResourceName.insert(instance.identifier(), instance.name()); 0342 resourceName = instance.name(); 0343 break; 0344 } 0345 } 0346 } 0347 0348 if (hasUniqMessage) { 0349 const int numberOfValue(it.value().count()); 0350 if (numberOfValue == 0) { 0351 // You can have an unique folder with 0 message 0352 return; 0353 } else if (numberOfValue == 1) { 0354 itemId = it.value().at(0); 0355 currentPath = displayName; 0356 break; 0357 } else { 0358 hasUniqMessage = false; 0359 } 0360 } 0361 0362 const int numberOfEmails(it.value().count()); 0363 if (numberOfEmails > 0) { 0364 const QString text = i18ncp("%2 = name of mail folder; %3 = name of Akonadi POP3/IMAP/etc resource (as user named it)", 0365 "One new email in %2 from \"%3\"", 0366 "%1 new emails in %2 from \"%3\"", 0367 numberOfEmails, 0368 displayName, 0369 resourceName); 0370 texts.append(text); 0371 if (!hasUniqMessage) { 0372 NewMailNotificationHistoryManager::HistoryFolderInfo info; 0373 info.message = text; 0374 info.identifier = it.key().id(); 0375 infos.append(info); 0376 } 0377 } 0378 } 0379 if (hasUniqMessage) { 0380 SpecialNotifierJob::SpecialNotificationInfo info; 0381 info.itemId = itemId; 0382 info.path = currentPath; 0383 info.listEmails = mListEmails; 0384 info.defaultIconName = mDefaultIconName; 0385 info.resourceName = resourceName; 0386 auto job = new SpecialNotifierJob(std::move(info), this); 0387 connect(job, &SpecialNotifierJob::displayNotification, this, [this, itemId](const QPixmap &pixmap, const QString &message) { 0388 NewMailNotificationHistoryManager::HistoryMailInfo info; 0389 info.message = message; 0390 info.identifier = itemId; 0391 addEmailInfoNotificationHistory(pixmap, message, std::move(info)); 0392 }); 0393 #if HAVE_TEXT_TO_SPEECH_SUPPORT 0394 connect(job, &SpecialNotifierJob::say, this, &NewMailNotifierAgent::slotSay); 0395 #endif 0396 mNewMails.clear(); 0397 return; 0398 } else { 0399 message = texts.join(QLatin1StringView("<br>")); 0400 } 0401 } else { 0402 message = i18n("New mail arrived"); 0403 } 0404 0405 qCDebug(NEWMAILNOTIFIER_LOG) << message; 0406 0407 addFoldersInfoNotificationHistory(message, std::move(infos)); 0408 0409 mNewMails.clear(); 0410 } 0411 0412 void NewMailNotifierAgent::addEmailInfoNotificationHistory(const QPixmap &pixmap, 0413 const QString &message, 0414 const NewMailNotificationHistoryManager::HistoryMailInfo &info) 0415 { 0416 slotDisplayNotification(pixmap, message); 0417 if (NewMailNotifierAgentSettings::enableNotificationHistory()) { 0418 NewMailNotificationHistoryManager::self()->addEmailInfoNotificationHistory(info); 0419 } 0420 } 0421 0422 void NewMailNotifierAgent::addFoldersInfoNotificationHistory(const QString &message, const QList<NewMailNotificationHistoryManager::HistoryFolderInfo> &infos) 0423 { 0424 slotDisplayNotification(QPixmap(), message); 0425 if (NewMailNotifierAgentSettings::enableNotificationHistory()) { 0426 NewMailNotificationHistoryManager::self()->addFoldersInfoNotificationHistory(infos); 0427 } 0428 } 0429 0430 void NewMailNotifierAgent::slotDisplayNotification(const QPixmap &pixmap, const QString &message) 0431 { 0432 if (pixmap.isNull()) { 0433 KNotification::event(QStringLiteral("new-email"), 0434 QString(), 0435 message, 0436 mDefaultIconName, 0437 NewMailNotifierAgentSettings::keepPersistentNotification() ? KNotification::Persistent | KNotification::SkipGrouping 0438 : KNotification::CloseOnTimeout, 0439 QStringLiteral("akonadi_newmailnotifier_agent")); 0440 } else { 0441 KNotification::event(QStringLiteral("new-email"), 0442 message, 0443 pixmap, 0444 NewMailNotifierAgentSettings::keepPersistentNotification() ? KNotification::Persistent | KNotification::SkipGrouping 0445 : KNotification::CloseOnTimeout, 0446 QStringLiteral("akonadi_newmailnotifier_agent")); 0447 } 0448 } 0449 0450 void NewMailNotifierAgent::slotInstanceNameChanged(const Akonadi::AgentInstance &instance) 0451 { 0452 if (!isActive()) { 0453 return; 0454 } 0455 0456 const QString identifier(instance.identifier()); 0457 int resourceNameRemoved = mCacheResourceName.remove(identifier); 0458 if (resourceNameRemoved > 0) { 0459 mCacheResourceName.insert(identifier, instance.name()); 0460 } 0461 } 0462 0463 void NewMailNotifierAgent::slotInstanceStatusChanged(const Akonadi::AgentInstance &instance) 0464 { 0465 if (!isActive()) { 0466 return; 0467 } 0468 0469 const QString identifier(instance.identifier()); 0470 switch (instance.status()) { 0471 case Akonadi::AgentInstance::Broken: 0472 case Akonadi::AgentInstance::Idle: 0473 mInstanceNameInProgress.removeAll(identifier); 0474 break; 0475 case Akonadi::AgentInstance::Running: 0476 if (!excludeAgentType(instance)) { 0477 if (!mInstanceNameInProgress.contains(identifier)) { 0478 mInstanceNameInProgress.append(identifier); 0479 } 0480 } 0481 break; 0482 case Akonadi::AgentInstance::NotConfigured: 0483 // Nothing 0484 break; 0485 } 0486 } 0487 0488 bool NewMailNotifierAgent::excludeAgentType(const Akonadi::AgentInstance &instance) 0489 { 0490 if (instance.type().mimeTypes().contains(KMime::Message::mimeType())) { 0491 const QStringList capabilities(instance.type().capabilities()); 0492 if (capabilities.contains(QLatin1StringView("Resource")) && !capabilities.contains(QLatin1StringView("Virtual")) 0493 && !capabilities.contains(QLatin1StringView("MailTransport"))) { 0494 return false; 0495 } else { 0496 return true; 0497 } 0498 } 0499 return true; 0500 } 0501 0502 void NewMailNotifierAgent::slotInstanceRemoved(const Akonadi::AgentInstance &instance) 0503 { 0504 if (!isActive()) { 0505 return; 0506 } 0507 0508 const QString identifier(instance.identifier()); 0509 mInstanceNameInProgress.removeAll(identifier); 0510 } 0511 0512 void NewMailNotifierAgent::slotInstanceAdded(const Akonadi::AgentInstance &instance) 0513 { 0514 mCacheResourceName.insert(instance.identifier(), instance.name()); 0515 } 0516 0517 void NewMailNotifierAgent::printDebug() 0518 { 0519 qCDebug(NEWMAILNOTIFIER_LOG) << "instance in progress: " << mInstanceNameInProgress << "\n notifier enabled : " << NewMailNotifierAgentSettings::enabled() 0520 << "\n check in progress : " << !mInstanceNameInProgress.isEmpty(); 0521 } 0522 0523 bool NewMailNotifierAgent::isActive() const 0524 { 0525 return isOnline() && NewMailNotifierAgentSettings::enabled(); 0526 } 0527 0528 void NewMailNotifierAgent::slotSay(const QString &message) 0529 { 0530 #if HAVE_TEXT_TO_SPEECH_SUPPORT 0531 if (!mTextToSpeech) { 0532 mTextToSpeech = new QTextToSpeech(this); 0533 } 0534 if (mTextToSpeech->availableEngines().isEmpty()) { 0535 qCWarning(NEWMAILNOTIFIER_LOG) << "No texttospeech engine available"; 0536 } else { 0537 mTextToSpeech->say(message); 0538 } 0539 #endif 0540 } 0541 0542 AKONADI_AGENT_MAIN(NewMailNotifierAgent) 0543 0544 #include "moc_newmailnotifieragent.cpp"