KIMAP

selectjob.cpp
1/*
2 SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "selectjob.h"
8
9#include "kimap_debug.h"
10
11#include "imapset.h"
12#include "job_p.h"
13#include "response_p.h"
14#include "rfccodecs.h"
15#include "session_p.h"
16
17#include <KLocalizedString>
18
19#include <QMap>
20#include <QTimer>
21
22namespace KIMAP
23{
24class SelectJobPrivate : public JobPrivate
25{
26public:
27 SelectJobPrivate(SelectJob *q, Session *session, const QString &name)
28 : JobPrivate(session, name)
29 , q(q)
30 {
31 QObject::connect(&emitPendingsTimer, &QTimer::timeout, [this]() {
32 emitPendings();
33 });
34 }
35
36 void emitPendings()
37 {
38 if (pendingMessages.empty()) {
39 return;
40 }
41
42 Q_EMIT q->modified(pendingMessages);
43 pendingMessages.clear();
44 }
45
46 QString mailBox;
47 bool readOnly = false;
48
49 QMap<qint64, Message> pendingMessages;
50 QTimer emitPendingsTimer;
51
53 QList<QByteArray> permanentFlags;
54 int messageCount = -1;
55 int recentCount = -1;
56 int firstUnseenIndex = -1;
57 qint64 uidValidity = -1;
58 qint64 nextUid = -1;
59 quint64 highestmodseq = 0;
60 qint64 lastUidvalidity = -1;
61 quint64 lastModseq = 0;
62 ImapSet knownUids;
63
64 bool condstoreEnabled = false;
65
66 SelectJob *const q;
67};
68}
69
70using namespace KIMAP;
71
72SelectJob::SelectJob(Session *session)
73 : Job(*new SelectJobPrivate(this, session, i18nc("name of the select job", "Select")))
74{
75}
76
77SelectJob::~SelectJob()
78{
79}
80
81void SelectJob::setMailBox(const QString &mailBox)
82{
83 Q_D(SelectJob);
84 d->mailBox = mailBox;
85}
86
87QString SelectJob::mailBox() const
88{
89 Q_D(const SelectJob);
90 return d->mailBox;
91}
92
93void SelectJob::setOpenReadOnly(bool readOnly)
94{
95 Q_D(SelectJob);
96 d->readOnly = readOnly;
97}
98
99bool SelectJob::isOpenReadOnly() const
100{
101 Q_D(const SelectJob);
102 return d->readOnly;
103}
104
105QList<QByteArray> SelectJob::flags() const
106{
107 Q_D(const SelectJob);
108 return d->flags;
109}
110
111QList<QByteArray> SelectJob::permanentFlags() const
112{
113 Q_D(const SelectJob);
114 return d->permanentFlags;
115}
116
117int SelectJob::messageCount() const
118{
119 Q_D(const SelectJob);
120 return d->messageCount;
121}
122
123int SelectJob::recentCount() const
124{
125 Q_D(const SelectJob);
126 return d->recentCount;
127}
128
129int SelectJob::firstUnseenIndex() const
130{
131 Q_D(const SelectJob);
132 return d->firstUnseenIndex;
133}
134
135qint64 SelectJob::uidValidity() const
136{
137 Q_D(const SelectJob);
138 return d->uidValidity;
139}
140
141qint64 SelectJob::nextUid() const
142{
143 Q_D(const SelectJob);
144 return d->nextUid;
145}
146
147quint64 SelectJob::highestModSequence() const
148{
149 Q_D(const SelectJob);
150 return d->highestmodseq;
151}
152
153void SelectJob::setCondstoreEnabled(bool enable)
154{
155 Q_D(SelectJob);
156 d->condstoreEnabled = enable;
157}
158
159bool SelectJob::condstoreEnabled() const
160{
161 Q_D(const SelectJob);
162 return d->condstoreEnabled;
163}
164
165void SelectJob::setQResync(qint64 lastUidvalidity, quint64 lastModseq, const ImapSet &knownUids)
166{
167 Q_D(SelectJob);
168 d->lastUidvalidity = lastUidvalidity;
169 d->lastModseq = lastModseq;
170 d->knownUids = knownUids;
171 setCondstoreEnabled(true);
172}
173
174void SelectJob::doStart()
175{
176 Q_D(SelectJob);
177
178 QByteArray command = "SELECT";
179 if (d->readOnly) {
180 command = "EXAMINE";
181 }
182
183 QByteArray params = '\"' + KIMAP::encodeImapFolderName(d->mailBox.toUtf8()) + '\"';
184
185 if (d->condstoreEnabled) {
186 // Check whether we only do CONDSTORE, or QRESYNC
187 if (d->lastUidvalidity == -1 && d->lastModseq == 0) {
188 params += " (CONDSTORE)";
189 } else {
190 params += " (QRESYNC (" + QByteArray::number(d->lastUidvalidity) + " " + QByteArray::number(d->lastModseq);
191 if (!d->knownUids.isEmpty()) {
192 params += " " + d->knownUids.toImapSequenceSet();
193 }
194 params += "))";
195 }
196 }
197
198 d->emitPendingsTimer.start(100);
199 d->tags << d->sessionInternal()->sendCommand(command, params);
200}
201
202void SelectJob::handleResponse(const Response &response)
203{
204 Q_D(SelectJob);
205
206 // Check for [READ-ONLY] response in final tagged OK
207 // This must be checked before handleErrorReplies(), because that calls emitResult()
208 // right away
209 if (!response.content.isEmpty() && d->tags.contains(response.content.first().toString())) {
210 if (response.responseCode.size() >= 1 && response.responseCode[0].toString() == "READ-ONLY") {
211 d->readOnly = true;
212 }
213 }
214
215 // We can predict it'll be handled by handleErrorReplies() so stop
216 // the timer now so that result() will really be the last emitted signal.
217 if (!response.content.isEmpty() && d->tags.size() == 1 && d->tags.contains(response.content.first().toString())) {
218 d->emitPendingsTimer.stop();
219 d->emitPendings();
220 }
221
222 if (handleErrorReplies(response) == NotHandled) {
223 if (response.content.size() >= 2) {
224 QByteArray code = response.content[1].toString();
225
226 if (code == "OK") {
227 if (response.responseCode.size() < 2) {
228 return;
229 }
230
231 code = response.responseCode[0].toString();
232 if (code == "PERMANENTFLAGS") {
233 d->permanentFlags = response.responseCode[1].toList();
234 } else if (code == "HIGHESTMODSEQ") {
235 bool isInt;
236 quint64 value = response.responseCode[1].toString().toULongLong(&isInt);
237 if (!isInt) {
238 return;
239 }
240 d->highestmodseq = value;
241 } else {
242 bool isInt;
243 qint64 value = response.responseCode[1].toString().toLongLong(&isInt);
244 if (!isInt) {
245 return;
246 }
247 if (code == "UIDVALIDITY") {
248 d->uidValidity = value;
249 } else if (code == "UNSEEN") {
250 d->firstUnseenIndex = value;
251 } else if (code == "UIDNEXT") {
252 d->nextUid = value;
253 }
254 }
255 } else if (code == "FLAGS") {
256 d->flags = response.content[2].toList();
257 } else if (code == "VANISHED" && response.content.size() == 4) { // VANISHED response in SELECT is RFC5162 (QRESYNC) extension
258 const auto vanishedSet = ImapSet::fromImapSequenceSet(response.content[3].toString());
259 Q_EMIT vanished(vanishedSet);
260 } else {
261 bool isInt = false;
262 int value = response.content[1].toString().toInt(&isInt);
263 if (!isInt || response.content.size() < 3) {
264 return;
265 }
266
267 code = response.content[2].toString();
268 if (code == "FETCH") { // FETCH response in SELECT is RFC5162 (QRESYNC) extension
269 Message msg{};
270 const auto content = response.content[3].toList();
271 for (auto it = content.cbegin(), end = content.cend(); it != end; ++it) {
272 const auto name = *it;
273 ++it;
274
275 if (it == content.constEnd()) { // Uh oh, message was truncated?
276 qCWarning(KIMAP_LOG) << "SELECT reply got truncated, skipping.";
277 break;
278 }
279
280 if (name == "UID") {
281 msg.uid = it->toLongLong();
282 } else if (name == "FLAGS") {
283 if ((*it).startsWith('(') && (*it).endsWith(')')) {
284 QByteArray str = *it;
285 str.chop(1);
286 str.remove(0, 1);
287 const auto flags = str.split(' ');
288 msg.flags = flags;
289 } else {
290 msg.flags << *it;
291 }
292 } else if (name == "MODSEQ") {
293 QByteArray modseq = *it;
294 if (modseq.startsWith('(') && modseq.endsWith(')')) {
295 modseq.chop(1);
296 modseq.remove(0, 1);
297 }
298
299 msg.attributes.insert(name, modseq.toULongLong());
300 }
301 }
302
303 d->pendingMessages.insert(value, msg);
304 } else if (code == "EXISTS") {
305 d->messageCount = value;
306 } else if (code == "RECENT") {
307 d->recentCount = value;
308 }
309 }
310 } else {
311 qCDebug(KIMAP_LOG) << response.toString();
312 }
313 } else {
314 Q_ASSERT(error() || d->m_session->selectedMailBox() == d->mailBox);
315 }
316}
317
318#include "moc_selectjob.cpp"
Represents a set of natural numbers (1->∞) in a as compact as possible form.
Definition imapset.h:127
static ImapSet fromImapSequenceSet(const QByteArray &sequence)
Return the set corresponding to the given IMAP-compatible QByteArray representation.
Definition imapset.cpp:285
int error() const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
const QList< QKeySequence > & end()
QString name(StandardShortcut id)
void chop(qsizetype n)
bool endsWith(QByteArrayView bv) const const
QByteArray & insert(qsizetype i, QByteArrayView data)
QByteArray number(double n, char format, int precision)
QByteArray & remove(qsizetype pos, qsizetype len)
QList< QByteArray > split(char sep) const const
bool startsWith(QByteArrayView bv) const const
qulonglong toULongLong(bool *ok, int base) const const
void clear()
bool empty() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
qlonglong toLongLong(bool *ok, int base) const const
void timeout()
This file is part of the IMAP support library and defines the RfcCodecs class.
KIMAP_EXPORT QByteArray encodeImapFolderName(const QByteArray &src)
Converts an Unicode IMAP mailbox to a QByteArray which can be used in IMAP communication.
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:37 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.