1 /* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
6 #include "mail-storage.h"
11 struct client_sync_context {
12 /* if multiple commands are in progress, we may need to wait for them
13 to finish before syncing mailbox. */
15 enum mailbox_sync_flags flags;
16 enum imap_sync_flags imap_flags;
18 imap_sync_callback_t *callback;
21 struct imap_sync_context {
22 struct client *client;
24 enum imap_sync_flags imap_flags;
26 struct mailbox_transaction_context *t;
27 struct mailbox_sync_context *sync_ctx;
30 struct mailbox_sync_rec sync_rec;
31 ARRAY_TYPE(keywords) tmp_keywords;
34 unsigned int messages_count;
36 unsigned int failed:1;
37 unsigned int no_newmail:1;
40 struct imap_sync_context *
41 imap_sync_init(struct client *client, struct mailbox *box,
42 enum imap_sync_flags imap_flags, enum mailbox_sync_flags flags)
44 struct imap_sync_context *ctx;
46 i_assert(client->mailbox == box);
48 ctx = i_new(struct imap_sync_context, 1);
51 ctx->imap_flags = imap_flags;
53 ctx->sync_ctx = mailbox_sync_init(box, flags);
54 ctx->t = mailbox_transaction_begin(box, 0);
55 ctx->mail = mail_alloc(ctx->t, MAIL_FETCH_FLAGS, 0);
56 ctx->messages_count = client->messages_count;
57 i_array_init(&ctx->tmp_keywords, client->keywords.announce_count + 8);
59 client_send_mailbox_flags(client, FALSE);
63 int imap_sync_deinit(struct imap_sync_context *ctx)
65 struct mailbox_status status;
68 mail_free(&ctx->mail);
70 if (mailbox_sync_deinit(&ctx->sync_ctx, STATUS_UIDVALIDITY |
71 STATUS_MESSAGES | STATUS_RECENT, &status) < 0 ||
73 mailbox_transaction_rollback(&ctx->t);
78 ret = mailbox_transaction_commit(&ctx->t);
80 if (status.uidvalidity != ctx->client->uidvalidity) {
81 /* most clients would get confused by this. disconnect them. */
82 client_disconnect_with_error(ctx->client,
83 "Mailbox UIDVALIDITY changed");
85 if (!ctx->no_newmail) {
86 if (status.messages < ctx->messages_count)
87 i_panic("Message count decreased");
88 ctx->client->messages_count = status.messages;
89 if (status.messages != ctx->messages_count) {
90 client_send_line(ctx->client,
91 t_strdup_printf("* %u EXISTS", status.messages));
93 if (status.recent != ctx->client->recent_count &&
95 ctx->client->recent_count = status.recent;
96 client_send_line(ctx->client,
97 t_strdup_printf("* %u RECENT", status.recent));
101 array_free(&ctx->tmp_keywords);
106 static int imap_sync_send_flags(struct imap_sync_context *ctx, string_t *str)
108 enum mail_flags flags;
109 const char *const *keywords;
111 mail_set_seq(ctx->mail, ctx->seq);
112 flags = mail_get_flags(ctx->mail);
113 keywords = client_get_keyword_names(ctx->client, &ctx->tmp_keywords,
114 mail_get_keyword_indexes(ctx->mail));
116 if ((flags & MAIL_DELETED) != 0)
117 ctx->client->sync_seen_deletes = TRUE;
119 str_truncate(str, 0);
120 str_printfa(str, "* %u FETCH (", ctx->seq);
121 if (ctx->imap_flags & IMAP_SYNC_FLAG_SEND_UID)
122 str_printfa(str, "UID %u ", ctx->mail->uid);
124 str_append(str, "FLAGS (");
125 imap_write_flags(str, flags, keywords);
126 str_append(str, "))");
127 return client_send_line(ctx->client, str_c(str));
130 int imap_sync_more(struct imap_sync_context *ctx)
135 str = t_str_new(256);
139 if (!mailbox_sync_next(ctx->sync_ctx, &ctx->sync_rec)) {
146 if (ctx->sync_rec.seq2 > ctx->messages_count) {
147 /* don't send change notifications of messages we
148 haven't even announced to client yet */
149 if (ctx->sync_rec.seq1 > ctx->messages_count) {
153 ctx->sync_rec.seq2 = ctx->messages_count;
156 switch (ctx->sync_rec.type) {
157 case MAILBOX_SYNC_TYPE_FLAGS:
159 ctx->seq = ctx->sync_rec.seq1;
162 for (; ctx->seq <= ctx->sync_rec.seq2; ctx->seq++) {
166 ret = imap_sync_send_flags(ctx, str);
169 case MAILBOX_SYNC_TYPE_EXPUNGE:
171 ctx->seq = ctx->sync_rec.seq2;
173 for (; ctx->seq >= ctx->sync_rec.seq1; ctx->seq--) {
177 str_truncate(str, 0);
178 str_printfa(str, "* %u EXPUNGE", ctx->seq);
179 ret = client_send_line(ctx->client, str_c(str));
181 if (ctx->seq < ctx->sync_rec.seq1) {
182 /* update only after we're finished, so that
183 the seq2 > messages_count check above
185 ctx->messages_count -=
187 ctx->sync_rec.seq1 + 1;
192 /* failure / buffer full */
201 static bool cmd_finish_sync(struct client_command_context *cmd)
203 if (cmd->sync->callback != NULL)
204 return cmd->sync->callback(cmd);
206 client_send_tagline(cmd, cmd->sync->tagline);
211 static bool cmd_sync_continue(struct client_command_context *sync_cmd)
213 struct client_command_context *cmd, *prev;
214 struct client *client = sync_cmd->client;
215 struct imap_sync_context *ctx = sync_cmd->context;
218 i_assert(ctx->client == client);
220 if ((ret = imap_sync_more(ctx)) == 0)
225 client->syncing = FALSE;
226 if (imap_sync_deinit(ctx) < 0) {
227 client_send_untagged_storage_error(client,
228 mailbox_get_storage(client->mailbox));
230 sync_cmd->context = NULL;
232 /* Finish all commands that waited for this sync. Go through the queue
233 backwards, so that tagged replies are sent in the same order as
234 they were received. This fixes problems with clients that rely on
235 this (Apple Mail 3.2) */
236 for (cmd = client->command_queue; cmd->next != NULL; cmd = cmd->next) ;
237 for (; cmd != NULL; cmd = prev) {
240 if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC &&
242 cmd->sync->counter+1 == client->sync_counter) {
243 if (cmd_finish_sync(cmd))
244 client_command_free(&cmd);
247 return cmd_finish_sync(sync_cmd);
250 static void get_common_sync_flags(struct client *client,
251 enum mailbox_sync_flags *flags_r,
252 enum imap_sync_flags *imap_flags_r)
254 struct client_command_context *cmd;
255 unsigned int count = 0, fast_count = 0, noexpunges_count = 0;
260 for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
261 if (cmd->sync != NULL &&
262 cmd->sync->counter == client->sync_counter) {
263 if ((cmd->sync->flags & MAILBOX_SYNC_FLAG_FAST) != 0)
265 if (cmd->sync->flags & MAILBOX_SYNC_FLAG_NO_EXPUNGES)
267 *flags_r |= cmd->sync->flags;
268 *imap_flags_r |= cmd->sync->imap_flags;
272 i_assert(noexpunges_count == 0 || noexpunges_count == count);
273 if (fast_count != count)
274 *flags_r &= ~MAILBOX_SYNC_FLAG_FAST;
276 i_assert((*flags_r & (MAILBOX_SYNC_AUTO_STOP |
277 MAILBOX_SYNC_FLAG_FIX_INCONSISTENT)) == 0);
280 static bool cmd_sync_client(struct client_command_context *sync_cmd)
282 struct client *client = sync_cmd->client;
283 struct imap_sync_context *ctx;
284 enum mailbox_sync_flags flags;
285 enum imap_sync_flags imap_flags;
288 /* there may be multiple commands waiting. use their combined flags */
289 get_common_sync_flags(client, &flags, &imap_flags);
290 client->sync_counter++;
292 no_newmail = (client_workarounds & WORKAROUND_DELAY_NEWMAIL) != 0 &&
293 (imap_flags & IMAP_SYNC_FLAG_SAFE) == 0;
295 /* expunges might break the client just as badly as new mail
297 flags |= MAILBOX_SYNC_FLAG_NO_EXPUNGES;
300 client->syncing = TRUE;
302 ctx = imap_sync_init(client, client->mailbox, imap_flags, flags);
303 ctx->no_newmail = no_newmail;
305 /* handle the syncing using sync_cmd. it doesn't actually matter which
306 one of the pending commands it is. */
307 sync_cmd->func = cmd_sync_continue;
308 sync_cmd->context = ctx;
309 sync_cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
310 if (!cmd_sync_continue(sync_cmd)) {
311 o_stream_set_flush_pending(client->output, TRUE);
315 client_command_free(&sync_cmd);
316 (void)cmd_sync_delayed(client);
321 cmd_sync_full(struct client_command_context *cmd, enum mailbox_sync_flags flags,
322 enum imap_sync_flags imap_flags, const char *tagline,
323 imap_sync_callback_t *callback)
325 struct client *client = cmd->client;
327 i_assert(client->output_lock == cmd || client->output_lock == NULL);
332 if (client->mailbox == NULL) {
333 /* no mailbox selected, no point in delaying the sync */
334 i_assert(callback == NULL);
335 client_send_tagline(cmd, tagline);
339 cmd->sync = p_new(cmd->pool, struct client_sync_context, 1);
340 cmd->sync->counter = client->sync_counter;
341 cmd->sync->flags = flags;
342 cmd->sync->imap_flags = imap_flags;
343 cmd->sync->tagline = p_strdup(cmd->pool, tagline);
344 cmd->sync->callback = callback;
345 cmd->state = CLIENT_COMMAND_STATE_WAIT_SYNC;
350 client->output_lock = NULL;
351 if (client->input_lock == cmd)
352 client->input_lock = NULL;
356 bool cmd_sync(struct client_command_context *cmd, enum mailbox_sync_flags flags,
357 enum imap_sync_flags imap_flags, const char *tagline)
359 return cmd_sync_full(cmd, flags, imap_flags, tagline, NULL);
362 bool cmd_sync_callback(struct client_command_context *cmd,
363 enum mailbox_sync_flags flags,
364 enum imap_sync_flags imap_flags,
365 imap_sync_callback_t *callback)
367 return cmd_sync_full(cmd, flags, imap_flags, NULL, callback);
370 static bool cmd_sync_drop_fast(struct client *client)
372 struct client_command_context *cmd, *prev;
375 if (client->command_queue == NULL)
378 for (cmd = client->command_queue; cmd->next != NULL; cmd = cmd->next) ;
379 for (; cmd != NULL; cmd = prev) {
382 if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC &&
383 (cmd->sync->flags & MAILBOX_SYNC_FLAG_FAST) != 0) {
384 if (cmd_finish_sync(cmd)) {
385 client_command_free(&cmd);
393 bool cmd_sync_delayed(struct client *client)
395 struct client_command_context *cmd, *first_expunge, *first_nonexpunge;
397 if (client->output_lock != NULL) {
398 /* wait until we can send output to client */
402 if (client->syncing ||
403 (client->mailbox != NULL &&
404 mailbox_transaction_get_count(client->mailbox) > 0)) {
405 /* wait until mailbox can be synced */
406 return cmd_sync_drop_fast(client);
409 /* separate syncs that can send expunges from those that can't */
410 first_expunge = first_nonexpunge = NULL;
411 for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
412 if (cmd->sync != NULL &&
413 cmd->sync->counter == client->sync_counter) {
414 if (cmd->sync->flags & MAILBOX_SYNC_FLAG_NO_EXPUNGES) {
415 if (first_nonexpunge == NULL)
416 first_nonexpunge = cmd;
418 if (first_expunge == NULL)
423 if (first_expunge != NULL && first_nonexpunge != NULL) {
424 /* sync expunges after nonexpunges */
425 for (cmd = first_expunge; cmd != NULL; cmd = cmd->next) {
426 if (cmd->sync != NULL &&
427 cmd->sync->counter == client->sync_counter &&
429 MAILBOX_SYNC_FLAG_NO_EXPUNGES) == 0)
430 cmd->sync->counter++;
432 first_expunge = NULL;
434 cmd = first_nonexpunge != NULL ? first_nonexpunge : first_expunge;
437 return cmd_sync_drop_fast(client);
438 i_assert(client->mailbox != NULL);
439 return cmd_sync_client(cmd);