29
32
struct mailbox_sync_context *sync_ctx;
35
struct mailbox_status status;
36
struct mailbox_sync_status sync_status;
32
38
struct mailbox_sync_rec sync_rec;
33
39
ARRAY_TYPE(keywords) tmp_keywords;
34
40
ARRAY_TYPE(seq_range) expunges;
37
43
ARRAY_TYPE(seq_range) search_adds, search_removes;
44
unsigned int search_update_idx;
39
46
unsigned int messages_count;
41
48
unsigned int failed:1;
49
unsigned int finished:1;
42
50
unsigned int no_newmail:1;
51
unsigned int have_new_mails:1;
52
unsigned int search_update_notifying:1;
45
55
static void uids_to_seqs(struct mailbox *box, ARRAY_TYPE(seq_range) *uids)
80
static int search_update_fetch_more(const struct imap_search_update *update)
84
if ((ret = imap_fetch_more_no_lock_update(update->fetch_ctx)) <= 0)
86
/* finished the FETCH */
87
if (imap_fetch_end(update->fetch_ctx) < 0)
93
imap_sync_send_fetch_to_search_update(struct imap_sync_context *ctx,
94
const struct imap_search_update *update)
96
struct mail_search_args *search_args;
97
struct mail_search_arg *arg;
98
ARRAY_TYPE(seq_range) seqs;
100
if (ctx->search_update_notifying)
101
return search_update_fetch_more(update);
103
i_assert(!update->fetch_ctx->state.fetching);
105
if (array_count(&ctx->search_adds) == 0 || !ctx->have_new_mails)
108
search_args = mail_search_build_init();
109
arg = mail_search_build_add(search_args, SEARCH_UIDSET);
110
p_array_init(&arg->value.seqset, search_args->pool, 1);
112
/* find the newly appended messages: ctx->messages_count is the message
113
count before new messages found by sync, client->messages_count is
114
the number of messages after. */
115
t_array_init(&seqs, 1);
116
seq_range_array_add_range(&seqs, ctx->messages_count+1,
117
ctx->client->messages_count);
118
mailbox_get_uid_range(ctx->client->mailbox, &seqs, &arg->value.seqset);
119
/* remove messages not in the search_adds list */
120
seq_range_array_intersect(&arg->value.seqset, &ctx->search_adds);
122
imap_fetch_begin(update->fetch_ctx, ctx->client->mailbox, search_args);
123
mail_search_args_unref(&search_args);
124
return search_update_fetch_more(update);
71
128
imap_sync_send_search_update(struct imap_sync_context *ctx,
72
const struct imap_search_update *update)
129
const struct imap_search_update *update,
76
mailbox_search_result_sync(update->result, &ctx->search_removes,
135
if (!ctx->search_update_notifying) {
136
mailbox_search_result_sync(update->result, &ctx->search_removes,
78
139
if (array_count(&ctx->search_adds) == 0 &&
79
140
array_count(&ctx->search_removes) == 0)
143
i_assert(array_count(&ctx->search_adds) == 0 || !removes_only);
144
if (update->fetch_ctx != NULL) {
145
ret = imap_sync_send_fetch_to_search_update(ctx, update);
147
ctx->search_update_notifying = TRUE;
151
ctx->search_update_notifying = FALSE;
82
153
cmd = t_str_new(256);
83
154
str_append(cmd, "* ESEARCH (TAG ");
84
imap_quote_append_string(cmd, update->tag, FALSE);
155
imap_append_string(cmd, update->tag);
85
156
str_append_c(cmd, ')');
86
157
if (update->return_uids)
87
158
str_append(cmd, " UID");
102
173
str_append_c(cmd, ')');
104
175
str_append(cmd, "\r\n");
105
o_stream_send(ctx->client->output, str_data(cmd), str_len(cmd));
176
o_stream_nsend(ctx->client->output, str_data(cmd), str_len(cmd));
108
static void imap_sync_send_search_updates(struct imap_sync_context *ctx)
181
imap_sync_send_search_updates(struct imap_sync_context *ctx, bool removes_only)
110
const struct imap_search_update *update;
183
const struct imap_search_update *updates;
184
unsigned int i, count;
112
187
if (!array_is_created(&ctx->client->search_updates))
115
190
if (!array_is_created(&ctx->search_removes)) {
116
191
i_array_init(&ctx->search_removes, 64);
117
192
i_array_init(&ctx->search_adds, 128);
120
array_foreach(&ctx->client->search_updates, update) T_BEGIN {
121
imap_sync_send_search_update(ctx, update);
195
updates = array_get(&ctx->client->search_updates, &count);
196
for (i = ctx->search_update_idx; i < count; i++) {
198
ret = imap_sync_send_search_update(ctx, &updates[i],
204
ctx->search_update_idx = i;
125
208
struct imap_sync_context *
155
243
/* send search updates the first time after sync is initialized.
156
244
it now contains expunged messages that must be sent before
157
245
EXPUNGE replies. */
158
imap_sync_send_search_updates(ctx);
246
if (imap_sync_send_search_updates(ctx, TRUE) == 0)
248
ctx->search_update_idx = 0;
163
253
imap_sync_send_highestmodseq(struct imap_sync_context *ctx,
164
const struct mailbox_status *status,
165
const struct mailbox_sync_status *sync_status,
166
254
struct client_command_context *sync_cmd)
168
256
struct client *client = ctx->client;
169
257
uint64_t send_modseq = 0;
171
if (sync_status->sync_delayed_expunges &&
259
if (ctx->sync_status.sync_delayed_expunges &&
172
260
client->highest_fetch_modseq > client->sync_last_full_modseq) {
173
261
/* if client updates highest-modseq using returned MODSEQs
174
262
it loses expunges. try to avoid this by sending it a lower
175
263
pre-expunge HIGHESTMODSEQ reply. */
176
264
send_modseq = client->sync_last_full_modseq;
177
} else if (!sync_status->sync_delayed_expunges &&
178
status->highest_modseq > client->sync_last_full_modseq &&
179
status->highest_modseq > client->highest_fetch_modseq) {
265
} else if (!ctx->sync_status.sync_delayed_expunges &&
266
ctx->status.highest_modseq > client->sync_last_full_modseq &&
267
ctx->status.highest_modseq > client->highest_fetch_modseq) {
180
268
/* we've probably sent some VANISHED or EXISTS replies which
181
269
increased the highest-modseq. notify the client about
183
send_modseq = status->highest_modseq;
271
send_modseq = ctx->status.highest_modseq;
186
274
if (send_modseq == 0) {
188
276
} else if (sync_cmd->sync != NULL && /* IDLE doesn't have ->sync */
277
sync_cmd->sync->tagline != NULL && /* NOTIFY doesn't have tagline */
189
278
strncmp(sync_cmd->sync->tagline, "OK ", 3) == 0 &&
190
279
sync_cmd->sync->tagline[3] != '[') {
191
280
/* modify the tagged reply directly */
200
289
(unsigned long long)send_modseq));
203
if (!sync_status->sync_delayed_expunges) {
292
if (!ctx->sync_status.sync_delayed_expunges) {
204
293
/* no delayed expunges, remember this for future */
205
client->sync_last_full_modseq = status->highest_modseq;
294
client->sync_last_full_modseq = ctx->status.highest_modseq;
207
296
client->highest_fetch_modseq = 0;
210
int imap_sync_deinit(struct imap_sync_context *ctx,
211
struct client_command_context *sync_cmd)
299
static int imap_sync_finish(struct imap_sync_context *ctx, bool aborting)
213
301
struct client *client = ctx->client;
214
struct mailbox_status status;
215
struct mailbox_sync_status sync_status;
302
int ret = ctx->failed ? -1 : 0;
306
ctx->finished = TRUE;
218
308
mail_free(&ctx->mail);
309
/* the transaction is used only for fetching modseqs/flags.
310
it can't really fail.. */
311
(void)mailbox_transaction_commit(&ctx->t);
219
313
if (array_is_created(&ctx->expunges))
220
314
array_free(&ctx->expunges);
222
if (mailbox_sync_deinit(&ctx->sync_ctx, &sync_status) < 0 ||
316
if (mailbox_sync_deinit(&ctx->sync_ctx, &ctx->sync_status) < 0 ||
224
mailbox_transaction_rollback(&ctx->t);
225
array_free(&ctx->tmp_keywords);
229
321
mailbox_get_open_status(ctx->box, STATUS_UIDVALIDITY |
230
322
STATUS_MESSAGES | STATUS_RECENT |
231
STATUS_HIGHESTMODSEQ, &status);
233
ret = mailbox_transaction_commit(&ctx->t);
235
if (status.uidvalidity != client->uidvalidity) {
323
STATUS_HIGHESTMODSEQ, &ctx->status);
325
if (ctx->status.uidvalidity != client->uidvalidity) {
236
326
/* most clients would get confused by this. disconnect them. */
237
327
client_disconnect_with_error(client,
238
328
"Mailbox UIDVALIDITY changed");
240
if (!ctx->no_newmail) {
241
if (status.messages < ctx->messages_count)
330
if (!ctx->no_newmail && !aborting) {
331
if (ctx->status.messages < ctx->messages_count)
242
332
i_panic("Message count decreased");
243
client->messages_count = status.messages;
244
if (status.messages != ctx->messages_count) {
245
client_send_line(client,
246
t_strdup_printf("* %u EXISTS", status.messages));
248
if (status.recent != client->recent_count &&
250
client->recent_count = status.recent;
251
client_send_line(client,
252
t_strdup_printf("* %u RECENT", status.recent));
333
if (ctx->status.messages != ctx->messages_count &&
334
client->notify_count_changes) {
335
client_send_line(client,
336
t_strdup_printf("* %u EXISTS", ctx->status.messages));
337
ctx->have_new_mails = TRUE;
339
if (ctx->status.recent != client->recent_count &&
340
client->notify_count_changes) {
341
client_send_line(client,
342
t_strdup_printf("* %u RECENT", ctx->status.recent));
344
client->messages_count = ctx->status.messages;
345
client->recent_count = ctx->status.recent;
350
static int imap_sync_notify_more(struct imap_sync_context *ctx)
354
if (ctx->have_new_mails && ctx->client->notify_ctx != NULL) {
355
/* send FETCH replies for the new mails */
356
if ((ret = imap_client_notify_newmails(ctx->client)) == 0)
255
362
/* send search updates the second time after syncing in done.
256
363
now it contains added/removed messages. */
257
imap_sync_send_search_updates(ctx);
259
if ((client->enabled_features & MAILBOX_FEATURE_QRESYNC) != 0) {
260
imap_sync_send_highestmodseq(ctx, &status, &sync_status,
364
if ((ret = imap_sync_send_search_updates(ctx, FALSE)) < 0)
369
int imap_sync_deinit(struct imap_sync_context *ctx,
370
struct client_command_context *sync_cmd)
374
ret = imap_sync_finish(ctx, TRUE);
375
imap_client_notify_finished(ctx->client);
377
if ((ctx->client->enabled_features & MAILBOX_FEATURE_QRESYNC) != 0)
378
imap_sync_send_highestmodseq(ctx, sync_cmd);
264
380
if (array_is_created(&ctx->search_removes)) {
265
381
array_free(&ctx->search_removes);
366
482
str_printfa(line, ":%u", prev_uid);
368
484
str_append(line, "\r\n");
369
o_stream_send(ctx->client->output, str_data(line), str_len(line));
485
o_stream_nsend(ctx->client->output, str_data(line), str_len(line));
488
static int imap_sync_send_expunges(struct imap_sync_context *ctx, string_t *str)
492
if (!ctx->client->notify_count_changes) {
493
/* NOTIFY: MessageEvent not specified for selected mailbox */
497
if (array_is_created(&ctx->expunges)) {
498
/* Use a single VANISHED line */
499
seq_range_array_add_range(&ctx->expunges,
505
ctx->seq = ctx->sync_rec.seq2;
506
for (; ctx->seq >= ctx->sync_rec.seq1; ctx->seq--) {
508
/* buffer full, continue later */
512
str_truncate(str, 0);
513
str_printfa(str, "* %u EXPUNGE", ctx->seq);
514
ret = client_send_line_next(ctx->client, str_c(str));
372
519
int imap_sync_more(struct imap_sync_context *ctx)
419
574
case MAILBOX_SYNC_TYPE_EXPUNGE:
420
ctx->client->sync_seen_expunges = TRUE;
421
if (array_is_created(&ctx->expunges)) {
422
/* Use a single VANISHED line */
423
seq_range_array_add_range(&ctx->expunges,
426
ctx->messages_count -=
428
ctx->sync_rec.seq1 + 1;
432
ctx->seq = ctx->sync_rec.seq2;
434
for (; ctx->seq >= ctx->sync_rec.seq1; ctx->seq--) {
438
str_truncate(str, 0);
439
str_printfa(str, "* %u EXPUNGE", ctx->seq);
440
ret = client_send_line(ctx->client, str_c(str));
442
if (ctx->seq < ctx->sync_rec.seq1) {
575
ret = imap_sync_send_expunges(ctx, str);
443
577
/* update only after we're finished, so that
444
578
the seq2 > messages_count check above