File indexing completed on 2025-01-19 04:51:59

0001 /*
0002     Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net>
0003     Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
0004 
0005     This library is free software; you can redistribute it and/or modify it
0006     under the terms of the GNU Library General Public License as published by
0007     the Free Software Foundation; either version 2 of the License, or (at your
0008     option) any later version.
0009 
0010     This library is distributed in the hope that it will be useful, but WITHOUT
0011     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0012     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
0013     License for more details.
0014 
0015     You should have received a copy of the GNU Library General Public License
0016     along with this library; see the file COPYING.LIB.  If not, write to the
0017     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0018     02110-1301, USA.
0019 */
0020 
0021 #include "maillistmodel.h"
0022 
0023 #include <sink/standardqueries.h>
0024 
0025 MailListModel::MailListModel(QObject *parent)
0026     : QSortFilterProxyModel(parent)
0027 {
0028     setDynamicSortFilter(true);
0029     sort(0, Qt::DescendingOrder);
0030     setFilterCaseSensitivity(Qt::CaseInsensitive);
0031 }
0032 
0033 MailListModel::~MailListModel()
0034 {
0035 
0036 }
0037 
0038 
0039 static void requestHeaders(Sink::Query &query)
0040 {
0041     using namespace Sink::ApplicationDomain;
0042     query.request<Mail::Subject>();
0043     query.request<Mail::Sender>();
0044     query.request<Mail::To>();
0045     query.request<Mail::Cc>();
0046     query.request<Mail::Bcc>();
0047     query.request<Mail::Date>();
0048     query.request<Mail::Unread>();
0049     query.request<Mail::Important>();
0050     query.request<Mail::Draft>();
0051     query.request<Mail::Folder>();
0052     query.request<Mail::Sent>();
0053     query.request<Mail::Trash>();
0054 }
0055 
0056 static void requestFullMail(Sink::Query &query)
0057 {
0058     using namespace Sink::ApplicationDomain;
0059     requestHeaders(query);
0060     query.request<Mail::MimeMessage>();
0061     query.request<Mail::FullPayloadAvailable>();
0062 }
0063 
0064 QHash< int, QByteArray > MailListModel::roleNames() const
0065 {
0066     QHash<int, QByteArray> roles;
0067 
0068     roles[Subject] = "subject";
0069     roles[Sender] = "sender";
0070     roles[SenderName] = "senderName";
0071     roles[To] = "to";
0072     roles[Cc] = "cc";
0073     roles[Bcc] = "bcc";
0074     roles[Date] = "date";
0075     roles[Unread] = "unread";
0076     roles[Important] = "important";
0077     roles[Draft] = "draft";
0078     roles[Sent] = "sent";
0079     roles[Trash] = "trash";
0080     roles[Id] = "id";
0081     roles[MimeMessage] = "mimeMessage";
0082     roles[DomainObject] = "domainObject";
0083     roles[ThreadSize] = "threadSize";
0084     roles[Mail] = "mail";
0085     roles[Incomplete] = "incomplete";
0086     roles[Status] = "status";
0087 
0088     return roles;
0089 }
0090 
0091 static QString join(const QList<Sink::ApplicationDomain::Mail::Contact> &contacts)
0092 {
0093     QStringList list;
0094     for (const auto &contact : contacts) {
0095         if (!contact.name.isEmpty()) {
0096             if (contact.name.contains(",")) {
0097                 list << QString("\"%1\" <%2>").arg(contact.name).arg(contact.emailAddress);
0098             } else {
0099                 list << QString("%1 <%2>").arg(contact.name).arg(contact.emailAddress);
0100             }
0101         } else {
0102             list << contact.emailAddress;
0103         }
0104     }
0105     return list.join(", ");
0106 }
0107 
0108 void MailListModel::fetchMail(Sink::ApplicationDomain::Mail::Ptr mail)
0109 {
0110     if (mail && !mail->getFullPayloadAvailable() && !mFetchedMails.contains(mail->identifier())) {
0111         qDebug() << "Fetching mail: " << mail->identifier() << mail->getSubject();
0112         mFetchedMails.insert(mail->identifier());
0113         Sink::Store::synchronize(Sink::SyncScope{*mail}).exec();
0114     }
0115 }
0116 
0117 QVariant MailListModel::data(const QModelIndex &idx, int role) const
0118 {
0119     auto srcIdx = mapToSource(idx);
0120     auto mail = srcIdx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>();
0121     switch (role) {
0122         case Subject:
0123             if (mail->isAggregate()) {
0124                 return mail->getProperty(QByteArray{Sink::ApplicationDomain::Mail::Subject::name} + QByteArray{"Selected"});
0125             } else {
0126                 return mail->getSubject();
0127             }
0128         case Sender:
0129             return mail->getSender().emailAddress;
0130         case SenderName:
0131             return mail->getSender().name;
0132         case To:
0133             return join(mail->getTo());
0134         case Cc:
0135             return join(mail->getCc());
0136         case Bcc:
0137             return join(mail->getBcc());
0138         case Date:
0139             return mail->getDate();
0140         case Unread:
0141             if (mail->isAggregate()) {
0142                 return mail->getCollectedProperty<Sink::ApplicationDomain::Mail::Unread>().contains(true);
0143             } else {
0144                 return mail->getUnread();
0145             }
0146         case Important:
0147             if (mail->isAggregate()) {
0148                 return mail->getCollectedProperty<Sink::ApplicationDomain::Mail::Important>().contains(true);
0149             } else {
0150                 return mail->getImportant();
0151             }
0152         case Draft:
0153             return mail->getDraft();
0154         case Sent:
0155             return mail->getSent();
0156         case Trash:
0157             return mail->getTrash();
0158         case Id:
0159             return mail->identifier();
0160         case DomainObject:
0161             return QVariant::fromValue(mail);
0162         case MimeMessage:
0163             if (mFetchMails) {
0164                 const_cast<MailListModel*>(this)->fetchMail(mail);
0165             }
0166             return mail->getMimeMessage();
0167         case ThreadSize:
0168             return mail->count();
0169         case Mail:
0170             return QVariant::fromValue(mail);
0171         case Incomplete:
0172             return !mail->getFullPayloadAvailable();
0173         case Status:
0174             const auto status = srcIdx.data(Sink::Store::StatusRole).toInt();
0175             if (status == Sink::ApplicationDomain::SyncStatus::SyncInProgress) {
0176                 return InProgressStatus;
0177             }
0178             if (status == Sink::ApplicationDomain::SyncStatus::SyncError) {
0179                 return ErrorStatus;
0180             }
0181             return NoStatus;
0182     }
0183     return QSortFilterProxyModel::data(idx, role);
0184 }
0185 
0186 bool MailListModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
0187 {
0188     const auto leftDate = left.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>()->getDate();
0189     const auto rightDate = right.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>()->getDate();
0190     if (leftDate == rightDate) {
0191         return left.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>()->identifier() <
0192                 right.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>()->identifier();
0193     }
0194     return leftDate < rightDate;
0195 }
0196 
0197 bool MailListModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
0198 {
0199     auto idx = sourceModel()->index(sourceRow, 0, sourceParent);
0200     auto regExp = filterRegExp();
0201     if (regExp.isEmpty()) {
0202         return true;
0203     }
0204     auto mail = idx.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Mail::Ptr>();
0205     return mail->getSubject().contains(regExp) ||
0206         mail->getSender().name.contains(regExp);
0207 }
0208 
0209 void MailListModel::runQuery(const Sink::Query &query)
0210 {
0211     if (mQuery == query) {
0212         return;
0213     }
0214     if (query.getBaseFilters().isEmpty() && query.ids().isEmpty()) {
0215         mQuery = {};
0216         m_model.clear();
0217         setSourceModel(nullptr);
0218     } else {
0219         mQuery = query;
0220         m_model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query);
0221         QObject::connect(m_model.data(), &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, const QVector<int> &roles) {
0222             if (roles.contains(Sink::Store::ChildrenFetchedRole)) {
0223                 emit initialItemsLoaded();
0224             }
0225         });
0226         setSourceModel(m_model.data());
0227     }
0228 }
0229 
0230 void MailListModel::setFilter(const QVariantMap &filter)
0231 {
0232     qDebug() << "MailListModel::setFilter " << filter;
0233     emit filterChanged();
0234     using namespace Sink;
0235     using namespace Sink::ApplicationDomain;
0236     bool validQuery = false;
0237     Sink::Query query;
0238 
0239     //Base queries
0240     if (filter.value("folder").value<Folder::Ptr>()) {
0241         auto folder = filter.value("folder").value<Folder::Ptr>();
0242         const auto specialPurpose = folder->getSpecialPurpose();
0243         mIsThreaded = !(specialPurpose.contains(SpecialPurpose::Mail::drafts) ||
0244                                 specialPurpose.contains(SpecialPurpose::Mail::sent));
0245 
0246         query = [&] {
0247             if (mIsThreaded) {
0248                 return Sink::StandardQueries::threadLeaders(*folder);
0249             } else {
0250                 Sink::Query query;
0251                 query.setId("threadleaders-unthreaded");
0252                 if (!folder->resourceInstanceIdentifier().isEmpty()) {
0253                     query.resourceFilter(folder->resourceInstanceIdentifier());
0254                 }
0255                 query.filter<Sink::ApplicationDomain::Mail::Folder>(*folder);
0256                 query.sort<Sink::ApplicationDomain::Mail::Date>();
0257                 return query;
0258             }
0259         }();
0260         if (!folder->getSpecialPurpose().contains(Sink::ApplicationDomain::SpecialPurpose::Mail::trash)) {
0261             //Filter trash if this is not a trash folder
0262             query.filter<Sink::ApplicationDomain::Mail::Trash>(false);
0263         }
0264 
0265         query.setFlags(Sink::Query::LiveQuery);
0266         query.limit(100);
0267 
0268         qDebug() << "Running folder query: " << folder->resourceInstanceIdentifier() << folder->identifier();
0269         //Latest mail on top
0270         sort(0, Qt::DescendingOrder);
0271         validQuery = true;
0272     } else if (!filter.value("account").toByteArray().isEmpty()) {
0273         query.resourceFilter<SinkResource::Account>(filter.value("account").toByteArray());
0274     }
0275 
0276     if (filter.value("important").toBool()) {
0277         query.setId("threadLeadersImportant");
0278         query.setFlags(Sink::Query::LiveQuery);
0279         query.filter<Sink::ApplicationDomain::Mail::Important>(true);
0280         query.sort<ApplicationDomain::Mail::Date>();
0281         query.reduce<ApplicationDomain::Mail::ThreadId>(Query::Reduce::Selector::max<ApplicationDomain::Mail::Date>())
0282             .count()
0283             .select<ApplicationDomain::Mail::Subject>(Query::Reduce::Selector::Min)
0284             .collect<ApplicationDomain::Mail::Unread>()
0285             .collect<ApplicationDomain::Mail::Important>();
0286         //Latest mail at the top
0287         sort(0, Qt::DescendingOrder);
0288         validQuery = true;
0289     }
0290 
0291     if (filter.value("drafts").toBool()) {
0292         query.setFlags(Sink::Query::LiveQuery);
0293         query.filter<Mail::Draft>(true);
0294         query.filter<Mail::Trash>(false);
0295         qDebug() << "Running mail query for drafts: ";
0296         //Latest mail at the top
0297         sort(0, Qt::DescendingOrder);
0298         validQuery = true;
0299     }
0300     if (filter.value("inbox").toBool()) {
0301         Sink::Query folderQuery{};
0302         folderQuery.containsFilter<Sink::ApplicationDomain::Folder::SpecialPurpose>(Sink::ApplicationDomain::SpecialPurpose::Mail::inbox);
0303         folderQuery.request<Sink::ApplicationDomain::Folder::SpecialPurpose>();
0304         folderQuery.request<Sink::ApplicationDomain::Folder::Name>();
0305 
0306         query.setFlags(Sink::Query::LiveQuery);
0307         query.filter<Sink::ApplicationDomain::Mail::Folder>(folderQuery);
0308         query.sort<Mail::Date>();
0309         //Latest mail at the top
0310         sort(0, Qt::DescendingOrder);
0311         validQuery = true;
0312     }
0313 
0314     if (filter.contains("singleMail") && filter.value("singleMail").value<Sink::ApplicationDomain::Mail::Ptr>()) {
0315         auto mail = filter.value("singleMail").value<Sink::ApplicationDomain::Mail::Ptr>();
0316         query = Sink::Query{*mail};
0317         query.setFlags(Sink::Query::LiveQuery | Sink::Query::UpdateStatus);
0318         //Latest mail at the bottom
0319         sort(0, Qt::AscendingOrder);
0320         validQuery = true;
0321     }
0322     if (filter.contains("mail") && filter.value("mail").value<Sink::ApplicationDomain::Mail::Ptr>()) {
0323         auto mail = filter.value("mail").value<Sink::ApplicationDomain::Mail::Ptr>();
0324         query = Sink::StandardQueries::completeThread(*mail);
0325         query.setFlags(Sink::Query::LiveQuery | Sink::Query::UpdateStatus);
0326         //Latest mail at the bottom
0327         sort(0, Qt::AscendingOrder);
0328         validQuery = true;
0329     }
0330 
0331     if (filter.contains("entityId") && !filter.value("entityId").value<QByteArray>().isEmpty()) {
0332         query.setFlags(Sink::Query::LiveQuery);
0333         query.filter(filter.value("entityId").value<QByteArray>());
0334         sort(0, Qt::DescendingOrder);
0335         validQuery = true;
0336     }
0337 
0338     //Additional filtering
0339     if (filter.contains("string") && filter.value("string").isValid()) {
0340         const auto filterString = filter.value("string").toString();
0341         if (filterString.length() < 3 && !filterString.isEmpty()) {
0342             return;
0343         }
0344         if (!filterString.isEmpty()) {
0345             query.filter({}, Sink::QueryBase::Comparator(filterString, Sink::QueryBase::Comparator::Fulltext));
0346             query.limit(0);
0347         }
0348         validQuery = true;
0349     }
0350 
0351     if (filter.value("hideTrash").toBool()) {
0352         query.filter<Sink::ApplicationDomain::Mail::Trash>(false);
0353     }
0354 
0355     if (filter.value("hideNonTrash").toBool()) {
0356         query.filter<Sink::ApplicationDomain::Mail::Trash>(true);
0357     }
0358 
0359     if (filter.value("headersOnly").toBool()) {
0360         requestHeaders(query);
0361     } else {
0362         requestFullMail(query);
0363     }
0364 
0365     mFetchMails = filter.value("fetchMails").toBool();
0366     //TODO don't reset on string filter update?
0367     mFetchedMails.clear();
0368 
0369     if (validQuery) {
0370         runQuery(query);
0371     } else {
0372         setSourceModel(nullptr);
0373     }
0374 }
0375 
0376 QVariantMap MailListModel::filter() const
0377 {
0378     return {};
0379 }
0380