File indexing completed on 2024-11-24 04:50:44
0001 // SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org> 0002 // SPDX-License-Identifier: LGPL-2.0-or-later 0003 0004 #include "mailmanager.h" 0005 0006 // Akonadi 0007 #include "merkuro_mail_debug.h" 0008 0009 #include "mailkernel.h" 0010 #include "sortedcollectionproxymodel.h" 0011 #include <Akonadi/AgentManager> 0012 #include <Akonadi/ChangeRecorder> 0013 #include <Akonadi/CollectionCreateJob> 0014 #include <Akonadi/CollectionDeleteJob> 0015 #include <Akonadi/CollectionPropertiesDialog> 0016 #include <Akonadi/EntityMimeTypeFilterModel> 0017 #include <Akonadi/EntityTreeModel> 0018 #include <Akonadi/ItemFetchJob> 0019 #include <Akonadi/ItemFetchScope> 0020 #include <Akonadi/ItemMoveJob> 0021 #include <Akonadi/MessageModel> 0022 #include <Akonadi/Monitor> 0023 #include <Akonadi/SelectionProxyModel> 0024 #include <Akonadi/ServerManager> 0025 #include <Akonadi/Session> 0026 #include <KConfigGroup> 0027 #include <KDescendantsProxyModel> 0028 #include <KLocalizedString> 0029 #include <KMbox/MBox> 0030 #include <KMime/Message> 0031 #include <MailCommon/EntityCollectionOrderProxyModel> 0032 #include <MailCommon/FolderCollectionMonitor> 0033 #include <MailCommon/MailKernel> 0034 #include <QApplication> 0035 #include <QLoggingCategory> 0036 #include <QPointer> 0037 0038 MailManager::MailManager(QObject *parent) 0039 : QObject(parent) 0040 , m_loading(true) 0041 { 0042 using namespace Akonadi; 0043 0044 MailKernel::self(); 0045 0046 // mailModel 0047 // ^ 0048 // | 0049 // itemModel 0050 // | 0051 // flatModel 0052 // | 0053 // descendantsProxyModel ------> selectionModel 0054 // ^ ^ 0055 // | | 0056 // collectionFilter | 0057 // \__________________treemodel 0058 0059 m_session = new Session(QByteArrayLiteral("KMailManager Kernel ETM"), this); 0060 auto folderCollectionMonitor = new MailCommon::FolderCollectionMonitor(m_session, this); 0061 0062 // setup collection model 0063 auto treeModel = new Akonadi::EntityTreeModel(folderCollectionMonitor->monitor(), this); 0064 treeModel->setItemPopulationStrategy(Akonadi::EntityTreeModel::LazyPopulation); 0065 0066 auto foldersModel = new Akonadi::CollectionFilterProxyModel(this); 0067 foldersModel->setSourceModel(treeModel); 0068 foldersModel->addMimeTypeFilter(KMime::Message::mimeType()); 0069 0070 m_foldersModel = new MailCommon::EntityCollectionOrderProxyModel(this); 0071 m_foldersModel->setSourceModel(foldersModel); 0072 m_foldersModel->setFilterCaseSensitivity(Qt::CaseInsensitive); 0073 KConfigGroup grp(KernelIf->config(), QStringLiteral("CollectionTreeOrder")); 0074 m_foldersModel->setOrderConfig(grp); 0075 m_foldersModel->sort(0, Qt::AscendingOrder); 0076 0077 // Setup selection model 0078 m_collectionSelectionModel = new QItemSelectionModel(m_foldersModel); 0079 connect(m_collectionSelectionModel, &QItemSelectionModel::selectionChanged, this, [this](const QItemSelection &selected, const QItemSelection &deselected) { 0080 Q_UNUSED(deselected) 0081 const auto indexes = selected.indexes(); 0082 if (!indexes.isEmpty()) { 0083 QString name; 0084 QModelIndex index = indexes[0]; 0085 while (index.isValid()) { 0086 if (name.isEmpty()) { 0087 name = index.data(Qt::DisplayRole).toString(); 0088 } else { 0089 name = index.data(Qt::DisplayRole).toString() + QLatin1StringView(" / ") + name; 0090 } 0091 index = index.parent(); 0092 } 0093 m_selectedFolderName = name; 0094 Q_EMIT selectedFolderNameChanged(); 0095 } 0096 }); 0097 auto selectionModel = new SelectionProxyModel(m_collectionSelectionModel, this); 0098 selectionModel->setSourceModel(treeModel); 0099 selectionModel->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); 0100 0101 // Setup mail model 0102 auto folderFilterModel = new EntityMimeTypeFilterModel(this); 0103 folderFilterModel->setSourceModel(selectionModel); 0104 folderFilterModel->setHeaderGroup(EntityTreeModel::ItemListHeaders); 0105 folderFilterModel->addMimeTypeInclusionFilter(KMime::Message::mimeType()); 0106 folderFilterModel->addMimeTypeExclusionFilter(Collection::mimeType()); 0107 0108 // Proxy for QML roles 0109 m_folderModel = new MailModel(this); 0110 m_folderModel->setSourceModel(folderFilterModel); 0111 0112 if (Akonadi::ServerManager::isRunning()) { 0113 m_loading = false; 0114 } else { 0115 connect(Akonadi::ServerManager::self(), &Akonadi::ServerManager::stateChanged, this, [this](Akonadi::ServerManager::State state) { 0116 if (state == Akonadi::ServerManager::State::Broken) { 0117 qApp->exit(-1); 0118 return; 0119 } 0120 bool loading = state != Akonadi::ServerManager::State::Running; 0121 if (loading == m_loading) { 0122 return; 0123 } 0124 m_loading = loading; 0125 Q_EMIT loadingChanged(); 0126 disconnect(Akonadi::ServerManager::self(), &Akonadi::ServerManager::stateChanged, this, nullptr); 0127 }); 0128 } 0129 CommonKernel->initFolders(); 0130 0131 loadConfig(); 0132 } 0133 0134 void MailManager::loadConfig() 0135 { 0136 KConfigGroup readerConfig(KernelIf->config(), QStringLiteral("AccountOrder")); 0137 QStringList listOrder; 0138 if (readerConfig.readEntry("EnableAccountOrder", true)) { 0139 listOrder = readerConfig.readEntry("order", QStringList()); 0140 } 0141 m_foldersModel->setTopLevelOrder(listOrder); 0142 } 0143 0144 void MailManager::saveConfig() 0145 { 0146 } 0147 0148 MailModel *MailManager::folderModel() const 0149 { 0150 return m_folderModel; 0151 } 0152 0153 void MailManager::loadMailCollection(const QModelIndex &modelIndex) 0154 { 0155 if (!modelIndex.isValid()) { 0156 return; 0157 } 0158 0159 m_collectionSelectionModel->select(modelIndex, QItemSelectionModel::ClearAndSelect); 0160 } 0161 0162 bool MailManager::loading() const 0163 { 0164 return m_loading; 0165 } 0166 0167 MailCommon::EntityCollectionOrderProxyModel *MailManager::foldersModel() const 0168 { 0169 return m_foldersModel; 0170 } 0171 0172 Akonadi::Session *MailManager::session() const 0173 { 0174 return m_session; 0175 } 0176 0177 QString MailManager::selectedFolderName() const 0178 { 0179 return m_selectedFolderName; 0180 } 0181 0182 void MailManager::moveToTrash(Akonadi::Item item) 0183 { 0184 auto collection = qvariant_cast<Akonadi::Collection>( 0185 foldersModel()->data(m_collectionSelectionModel->selection().indexes()[0], Akonadi::EntityTreeModel::CollectionRole)); 0186 auto trash = CommonKernel->trashCollectionFromResource(collection); 0187 if (!trash.isValid()) { 0188 trash = CommonKernel->trashCollectionFolder(); 0189 } 0190 new Akonadi::ItemMoveJob(item, trash); 0191 } 0192 0193 void MailManager::updateCollection(const QModelIndex &index) 0194 { 0195 const auto collection = foldersModel()->data(index, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); 0196 Akonadi::AgentManager::self()->synchronizeCollection(collection, true); 0197 } 0198 0199 void MailManager::addCollection(const QModelIndex &index, const QVariant &name) 0200 { 0201 const auto parentCollection = foldersModel()->data(index, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); 0202 const auto collection = new Akonadi::Collection(); 0203 collection->setParentCollection(parentCollection); 0204 collection->setName(name.toString()); 0205 collection->setContentMimeTypes({QStringLiteral("message/rfc822")}); 0206 0207 const auto job = new Akonadi::CollectionCreateJob(*collection); 0208 connect(job, SIGNAL(result(KJob *)), job, SLOT(slotResult(KJob *))); 0209 } 0210 0211 void MailManager::deleteCollection(const QModelIndex &index) 0212 { 0213 const auto collection = foldersModel()->data(index, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); 0214 const bool isTopLevel = collection.parentCollection() == Akonadi::Collection::root(); 0215 0216 if (!isTopLevel) { 0217 // delete contents 0218 const auto job = new Akonadi::CollectionDeleteJob(collection); 0219 connect(job, &Akonadi::CollectionDeleteJob::result, this, [](KJob *job) { 0220 if (job->error()) { 0221 qCWarning(merkuro_MAIL_LOG) << "Error occured deleting collection: " << job->errorString(); 0222 } 0223 }); 0224 return; 0225 } 0226 0227 // deletes agent but not the contents 0228 const auto instance = Akonadi::AgentManager::self()->instance(collection.resource()); 0229 if (instance.isValid()) { 0230 Akonadi::AgentManager::self()->removeInstance(instance); 0231 } 0232 } 0233 0234 void MailManager::editCollection(const QModelIndex &index) 0235 { 0236 const auto collection = foldersModel()->data(index, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); 0237 QPointer<Akonadi::CollectionPropertiesDialog> dialog = new Akonadi::CollectionPropertiesDialog(collection); 0238 dialog->setWindowTitle(i18nc("@title:window", "Account Configuration: %1", collection.name())); 0239 dialog->show(); 0240 } 0241 0242 QString MailManager::resourceIdentifier(const QModelIndex &index) 0243 { 0244 const auto collection = foldersModel()->data(index, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); 0245 return collection.resource(); 0246 } 0247 0248 void MailManager::saveMail(const QUrl &fileUrl, const Akonadi::Item &item) 0249 { 0250 const auto filename = fileUrl.toLocalFile(); 0251 0252 auto job = new Akonadi::ItemFetchJob(item); 0253 job->fetchScope().fetchFullPayload(); 0254 connect(job, &Akonadi::ItemFetchJob::result, this, [this, filename](KJob *job) { 0255 const auto *fetchJob = qobject_cast<Akonadi::ItemFetchJob *>(job); 0256 const auto items = fetchJob->items(); 0257 if (items.isEmpty()) { 0258 qWarning() << "Error occurred: empty fetch job"; 0259 return; 0260 } 0261 const auto item = items.at(0); 0262 if (!item.hasPayload()) { 0263 qCCritical(merkuro_MAIL_LOG) << "Error occured: error parsing mail"; 0264 return; 0265 } 0266 0267 const auto message = item.payload<KMime::Message::Ptr>(); 0268 KMBox::MBox mbox; 0269 if (!mbox.load(filename)) { 0270 qCWarning(merkuro_MAIL_LOG) << "Error occured: error creating file"; 0271 } 0272 mbox.appendMessage(message); 0273 0274 if (!mbox.save()) { 0275 qCWarning(merkuro_MAIL_LOG) << "Error occured: error saving mail"; 0276 } 0277 }); 0278 }