imap-sync.c revision c96eb61168670cfdd7596baba18856d3f086a093
d3b29d4b61f1549244a7509b798be6f806cf7d4eTimo Sirainen/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen
5355df8b66da35698449a10c2c83633a64995298Timo Sirainen#include "common.h"
d99107ddf4d9bccb710994482daf65276a9d6321Timo Sirainen#include "str.h"
d99107ddf4d9bccb710994482daf65276a9d6321Timo Sirainen#include "ostream.h"
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen#include "mail-storage.h"
d3b29d4b61f1549244a7509b798be6f806cf7d4eTimo Sirainen#include "imap-util.h"
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen#include "imap-sync.h"
9137c55411aa39d41c1e705ddc34d5bd26c65021Timo Sirainen#include "commands.h"
fc71e94957d0c2959a609450a2f303640d681858Sascha Wilde
9137c55411aa39d41c1e705ddc34d5bd26c65021Timo Sirainenstruct imap_sync_context {
2a90d8a14b0e7cc1508814bc87d3dfa598ef46a8Timo Sirainen struct client *client;
bbe42963032af89fac1318844da08c742525cc87Timo Sirainen struct mailbox *box;
72b9d8039b714ea57c4e3b31bce32ef41cb414d4Timo Sirainen enum imap_sync_flags imap_flags;
1299f2c3723ca9ccf8f9e563ec23ee1a1721fe4cTimo Sirainen
9137c55411aa39d41c1e705ddc34d5bd26c65021Timo Sirainen struct mailbox_transaction_context *t;
9137c55411aa39d41c1e705ddc34d5bd26c65021Timo Sirainen struct mailbox_sync_context *sync_ctx;
fc71e94957d0c2959a609450a2f303640d681858Sascha Wilde struct mail *mail;
9137c55411aa39d41c1e705ddc34d5bd26c65021Timo Sirainen
bbe42963032af89fac1318844da08c742525cc87Timo Sirainen struct mailbox_sync_rec sync_rec;
53f37d41e1abb068131320f39f870fc92e81f560Timo Sirainen ARRAY_TYPE(keywords) tmp_keywords;
53f37d41e1abb068131320f39f870fc92e81f560Timo Sirainen uint32_t seq;
unsigned int messages_count;
unsigned int failed:1;
unsigned int no_newmail:1;
};
struct imap_sync_context *
imap_sync_init(struct client *client, struct mailbox *box,
enum imap_sync_flags imap_flags, enum mailbox_sync_flags flags)
{
struct imap_sync_context *ctx;
i_assert(client->mailbox == box);
ctx = i_new(struct imap_sync_context, 1);
ctx->client = client;
ctx->box = box;
ctx->imap_flags = imap_flags;
ctx->sync_ctx = mailbox_sync_init(box, flags);
ctx->t = mailbox_transaction_begin(box, 0);
ctx->mail = mail_alloc(ctx->t, MAIL_FETCH_FLAGS, 0);
ctx->messages_count = client->messages_count;
i_array_init(&ctx->tmp_keywords, client->keywords.announce_count + 8);
client_send_mailbox_flags(client, FALSE);
return ctx;
}
int imap_sync_deinit(struct imap_sync_context *ctx)
{
struct mailbox_status status;
int ret;
mail_free(&ctx->mail);
if (mailbox_sync_deinit(&ctx->sync_ctx, STATUS_UIDVALIDITY |
STATUS_MESSAGES | STATUS_RECENT, &status) < 0 ||
ctx->failed) {
mailbox_transaction_rollback(&ctx->t);
i_free(ctx);
return -1;
}
ret = mailbox_transaction_commit(&ctx->t);
if (status.uidvalidity != ctx->client->uidvalidity) {
/* most clients would get confused by this. disconnect them. */
client_disconnect_with_error(ctx->client,
"Mailbox UIDVALIDITY changed");
}
if (!ctx->no_newmail) {
if (status.messages < ctx->messages_count)
i_panic("Message count decreased");
ctx->client->messages_count = status.messages;
if (status.messages != ctx->messages_count) {
client_send_line(ctx->client,
t_strdup_printf("* %u EXISTS", status.messages));
}
if (status.recent != ctx->client->recent_count &&
!ctx->no_newmail) {
ctx->client->recent_count = status.recent;
client_send_line(ctx->client,
t_strdup_printf("* %u RECENT", status.recent));
}
}
array_free(&ctx->tmp_keywords);
i_free(ctx);
return ret;
}
static int imap_sync_send_flags(struct imap_sync_context *ctx, string_t *str)
{
enum mail_flags flags;
const char *const *keywords;
mail_set_seq(ctx->mail, ctx->seq);
flags = mail_get_flags(ctx->mail);
keywords = client_get_keyword_names(ctx->client, &ctx->tmp_keywords,
mail_get_keyword_indexes(ctx->mail));
str_truncate(str, 0);
str_printfa(str, "* %u FETCH (", ctx->seq);
if (ctx->imap_flags & IMAP_SYNC_FLAG_SEND_UID)
str_printfa(str, "UID %u ", ctx->mail->uid);
str_append(str, "FLAGS (");
imap_write_flags(str, flags, keywords);
str_append(str, "))");
return client_send_line(ctx->client, str_c(str));
}
int imap_sync_more(struct imap_sync_context *ctx)
{
string_t *str;
int ret = 1;
str = t_str_new(256);
for (;;) {
if (ctx->seq == 0) {
/* get next one */
if (!mailbox_sync_next(ctx->sync_ctx, &ctx->sync_rec)) {
/* finished */
ret = 1;
break;
}
}
if (ctx->sync_rec.seq2 > ctx->messages_count) {
/* don't send change notifications of messages we
haven't even announced to client yet */
if (ctx->sync_rec.seq1 > ctx->messages_count) {
ctx->seq = 0;
continue;
}
ctx->sync_rec.seq2 = ctx->messages_count;
}
switch (ctx->sync_rec.type) {
case MAILBOX_SYNC_TYPE_FLAGS:
if (ctx->seq == 0)
ctx->seq = ctx->sync_rec.seq1;
ret = 1;
for (; ctx->seq <= ctx->sync_rec.seq2; ctx->seq++) {
if (ret <= 0)
break;
ret = imap_sync_send_flags(ctx, str);
}
break;
case MAILBOX_SYNC_TYPE_EXPUNGE:
if (ctx->seq == 0)
ctx->seq = ctx->sync_rec.seq2;
ret = 1;
for (; ctx->seq >= ctx->sync_rec.seq1; ctx->seq--) {
if (ret <= 0)
break;
str_truncate(str, 0);
str_printfa(str, "* %u EXPUNGE", ctx->seq);
ret = client_send_line(ctx->client, str_c(str));
}
if (ctx->seq < ctx->sync_rec.seq1) {
/* update only after we're finished, so that
the seq2 > messages_count check above
doesn't break */
ctx->messages_count -=
ctx->sync_rec.seq2 -
ctx->sync_rec.seq1 + 1;
}
break;
}
if (ret <= 0) {
/* failure / buffer full */
break;
}
ctx->seq = 0;
}
return ret;
}
static bool cmd_sync_continue(struct client_command_context *sync_cmd)
{
struct client_command_context *cmd;
struct client *client = sync_cmd->client;
struct imap_sync_context *ctx = sync_cmd->context;
int ret;
i_assert(ctx->client == client);
if ((ret = imap_sync_more(ctx)) == 0)
return FALSE;
if (ret < 0)
ctx->failed = TRUE;
client->syncing = FALSE;
if (imap_sync_deinit(ctx) < 0) {
client_send_untagged_storage_error(client,
mailbox_get_storage(client->mailbox));
}
sync_cmd->context = NULL;
/* finish all commands that waited for this sync */
for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC &&
cmd != sync_cmd &&
cmd->sync_counter+1 == client->sync_counter) {
client_send_tagline(cmd, cmd->sync_tagline);
client_command_free(cmd);
}
}
client_send_tagline(sync_cmd, sync_cmd->sync_tagline);
return TRUE;
}
static void get_common_sync_flags(struct client *client,
enum mailbox_sync_flags *flags_r,
enum imap_sync_flags *imap_flags_r)
{
struct client_command_context *cmd;
unsigned int count = 0, fast_count = 0, noexpunges_count = 0;
*flags_r = 0;
*imap_flags_r = 0;
for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
if (cmd->sync_tagline != NULL &&
cmd->sync_counter == client->sync_counter) {
if ((cmd->sync_flags & MAILBOX_SYNC_FLAG_FAST) != 0)
fast_count++;
if (cmd->sync_flags & MAILBOX_SYNC_FLAG_NO_EXPUNGES)
noexpunges_count++;
*flags_r |= cmd->sync_flags;
*imap_flags_r |= cmd->sync_imap_flags;
count++;
}
}
if (fast_count != count)
*flags_r &= ~MAILBOX_SYNC_FLAG_FAST;
if (noexpunges_count != count)
*flags_r &= ~MAILBOX_SYNC_FLAG_NO_EXPUNGES;
i_assert((*flags_r & (MAILBOX_SYNC_AUTO_STOP |
MAILBOX_SYNC_FLAG_FIX_INCONSISTENT)) == 0);
}
static bool cmd_sync_client(struct client_command_context *sync_cmd)
{
struct client *client = sync_cmd->client;
struct imap_sync_context *ctx;
enum mailbox_sync_flags flags;
enum imap_sync_flags imap_flags;
bool no_newmail;
/* there may be multiple commands waiting. use their combined flags */
get_common_sync_flags(client, &flags, &imap_flags);
client->sync_counter++;
no_newmail = (client_workarounds & WORKAROUND_DELAY_NEWMAIL) != 0 &&
(imap_flags & IMAP_SYNC_FLAG_SAFE) == 0;
if (no_newmail) {
/* expunges might break the client just as badly as new mail
notifications. */
flags |= MAILBOX_SYNC_FLAG_NO_EXPUNGES;
}
client->syncing = TRUE;
ctx = imap_sync_init(client, client->mailbox, imap_flags, flags);
ctx->no_newmail = no_newmail;
/* handle the syncing using sync_cmd. it doesn't actually matter which
one of the pending commands it is. */
sync_cmd->func = cmd_sync_continue;
sync_cmd->context = ctx;
sync_cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
if (!cmd_sync_continue(sync_cmd)) {
o_stream_set_flush_pending(client->output, TRUE);
return FALSE;
}
client_command_free(sync_cmd);
(void)cmd_sync_delayed(client);
return TRUE;
}
bool cmd_sync(struct client_command_context *cmd, enum mailbox_sync_flags flags,
enum imap_sync_flags imap_flags, const char *tagline)
{
struct client *client = cmd->client;
i_assert(client->output_lock == cmd || client->output_lock == NULL);
if (cmd->cancel)
return TRUE;
if (client->mailbox == NULL) {
/* no mailbox selected, no point in delaying the sync */
client_send_tagline(cmd, tagline);
return TRUE;
}
cmd->sync_counter = client->sync_counter;
cmd->sync_flags = flags;
cmd->sync_imap_flags = imap_flags;
cmd->sync_tagline = p_strdup(cmd->pool, tagline);
cmd->state = CLIENT_COMMAND_STATE_WAIT_SYNC;
cmd->func = NULL;
cmd->context = NULL;
client->output_lock = NULL;
if (client->input_lock == cmd)
client->input_lock = NULL;
return FALSE;
}
static bool cmd_sync_drop_fast(struct client *client)
{
struct client_command_context *cmd, *next;
bool ret = FALSE;
for (cmd = client->command_queue; cmd != NULL; cmd = next) {
next = cmd->next;
if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC &&
(cmd->sync_flags & MAILBOX_SYNC_FLAG_FAST) != 0) {
client_send_tagline(cmd, cmd->sync_tagline);
client_command_free(cmd);
ret = TRUE;
}
}
return ret;
}
bool cmd_sync_delayed(struct client *client)
{
struct client_command_context *cmd;
if (client->output_lock != NULL) {
/* wait until we can send output to client */
return FALSE;
}
if (client->syncing ||
(client->mailbox != NULL &&
mailbox_transaction_get_count(client->mailbox) > 0)) {
/* wait until mailbox can be synced */
return cmd_sync_drop_fast(client);
}
/* find a command that we can sync */
for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC) {
if (cmd->sync_counter == client->sync_counter)
break;
}
}
if (cmd == NULL)
return cmd_sync_drop_fast(client);
i_assert(client->mailbox != NULL);
return cmd_sync_client(cmd);
}