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