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 }