imap-sync.c revision 5afa8e2edf4f313cd56e5909f92f39c3b5b7b4d3
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen#include "common.h"
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen#include "str.h"
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen#include "ostream.h"
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen#include "mail-storage.h"
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen#include "imap-quote.h"
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen#include "imap-util.h"
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen#include "imap-sync.h"
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen#include "commands.h"
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainenstruct client_sync_context {
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen /* if multiple commands are in progress, we may need to wait for them
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen to finish before syncing mailbox. */
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen unsigned int counter;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen enum mailbox_sync_flags flags;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen enum imap_sync_flags imap_flags;
39e6fcc3e8b1ccb13087c232cb6bdea04d1a20a4Timo Sirainen const char *tagline;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen imap_sync_callback_t *callback;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen};
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainenstruct imap_sync_context {
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen struct client *client;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen struct mailbox *box;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen enum imap_sync_flags imap_flags;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen struct mailbox_transaction_context *t;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen struct mailbox_sync_context *sync_ctx;
150e64c376365becf1ec5c9d45912ecb840eea96Timo Sirainen struct mail *mail;
150e64c376365becf1ec5c9d45912ecb840eea96Timo Sirainen
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen struct mailbox_sync_rec sync_rec;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen ARRAY_TYPE(keywords) tmp_keywords;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen ARRAY_TYPE(seq_range) expunges;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen uint32_t seq;
39e6fcc3e8b1ccb13087c232cb6bdea04d1a20a4Timo Sirainen
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen ARRAY_TYPE(seq_range) search_adds, search_removes;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
bc3698b8892df8003b410daea6f5bbcd20433808Timo Sirainen unsigned int messages_count;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen unsigned int failed:1;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen unsigned int no_newmail:1;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen};
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainenstatic void uids_to_seqs(struct mailbox *box, ARRAY_TYPE(seq_range) *uids)
39e6fcc3e8b1ccb13087c232cb6bdea04d1a20a4Timo Sirainen{
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen T_BEGIN {
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen ARRAY_TYPE(seq_range) seqs;
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen const struct seq_range *range;
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen unsigned int i, count;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen uint32_t seq1, seq2;
bc3698b8892df8003b410daea6f5bbcd20433808Timo Sirainen
bc3698b8892df8003b410daea6f5bbcd20433808Timo Sirainen range = array_get(uids, &count);
bc3698b8892df8003b410daea6f5bbcd20433808Timo Sirainen t_array_init(&seqs, count);
bc3698b8892df8003b410daea6f5bbcd20433808Timo Sirainen for (i = 0; i < count; i++) {
bc3698b8892df8003b410daea6f5bbcd20433808Timo Sirainen mailbox_get_seq_range(box, range[i].seq1, range[i].seq2,
bc3698b8892df8003b410daea6f5bbcd20433808Timo Sirainen &seq1, &seq2);
bc3698b8892df8003b410daea6f5bbcd20433808Timo Sirainen /* since we have to notify about expunged messages,
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen we expect that all the referenced UIDs exist */
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen i_assert(seq1 != 0);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen i_assert(seq2 - seq1 == range[i].seq2 - range[i].seq1);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen seq_range_array_add_range(&seqs, seq1, seq2);
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen }
ccb77e2f63626ec46e5745ef4f38baa8e8e504fcTimo Sirainen /* replace uids with seqs */
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen array_clear(uids);
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen array_append_array(uids, &seqs);
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen
5fb3bff645380804c9db2510940c41db6b8fdb01Timo Sirainen } T_END;
5fb3bff645380804c9db2510940c41db6b8fdb01Timo Sirainen}
5fb3bff645380804c9db2510940c41db6b8fdb01Timo Sirainen
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainenstatic void
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainenimap_sync_send_search_update(struct imap_sync_context *ctx,
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen const struct imap_search_update *update)
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen{
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen string_t *cmd;
ccb77e2f63626ec46e5745ef4f38baa8e8e504fcTimo Sirainen
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen mailbox_search_result_sync(update->result, &ctx->search_removes,
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen &ctx->search_adds);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen if (array_count(&ctx->search_adds) == 0 &&
ef11d3930c3602fc86349a4e3a53442df470b601Timo Sirainen array_count(&ctx->search_removes) == 0)
150e64c376365becf1ec5c9d45912ecb840eea96Timo Sirainen return;
ef11d3930c3602fc86349a4e3a53442df470b601Timo Sirainen
ef11d3930c3602fc86349a4e3a53442df470b601Timo Sirainen cmd = t_str_new(256);
ef11d3930c3602fc86349a4e3a53442df470b601Timo Sirainen str_append(cmd, "* ESEARCH (TAG ");
ef11d3930c3602fc86349a4e3a53442df470b601Timo Sirainen imap_quote_append_string(cmd, update->tag, FALSE);
ef11d3930c3602fc86349a4e3a53442df470b601Timo Sirainen str_append_c(cmd, ')');
ef11d3930c3602fc86349a4e3a53442df470b601Timo Sirainen if (update->return_uids)
ef11d3930c3602fc86349a4e3a53442df470b601Timo Sirainen str_append(cmd, " UID");
ef11d3930c3602fc86349a4e3a53442df470b601Timo Sirainen else {
ef11d3930c3602fc86349a4e3a53442df470b601Timo Sirainen /* convert to sequences */
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen uids_to_seqs(ctx->client->mailbox, &ctx->search_removes);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen uids_to_seqs(ctx->client->mailbox, &ctx->search_adds);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen }
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
ccb77e2f63626ec46e5745ef4f38baa8e8e504fcTimo Sirainen if (array_count(&ctx->search_removes) != 0) {
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen str_printfa(cmd, " REMOVEFROM (0 ");
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen imap_write_seq_range(cmd, &ctx->search_removes);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen str_append_c(cmd, ')');
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen }
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen if (array_count(&ctx->search_adds) != 0) {
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen str_printfa(cmd, " ADDTO (0 ");
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen imap_write_seq_range(cmd, &ctx->search_adds);
68efcccb384f2d6871164b072457e87473502c51Timo Sirainen str_append_c(cmd, ')');
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen }
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen str_append(cmd, ")\r\n");
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen o_stream_send(ctx->client->output, str_data(cmd), str_len(cmd));
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen}
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainenstatic void imap_sync_send_search_updates(struct imap_sync_context *ctx)
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen{
68efcccb384f2d6871164b072457e87473502c51Timo Sirainen const struct imap_search_update *updates;
68efcccb384f2d6871164b072457e87473502c51Timo Sirainen unsigned int i, count;
68efcccb384f2d6871164b072457e87473502c51Timo Sirainen
68efcccb384f2d6871164b072457e87473502c51Timo Sirainen if (!array_is_created(&ctx->client->search_updates))
68efcccb384f2d6871164b072457e87473502c51Timo Sirainen return;
68efcccb384f2d6871164b072457e87473502c51Timo Sirainen
68efcccb384f2d6871164b072457e87473502c51Timo Sirainen if (!array_is_created(&ctx->search_removes)) {
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen i_array_init(&ctx->search_removes, 64);
68efcccb384f2d6871164b072457e87473502c51Timo Sirainen i_array_init(&ctx->search_adds, 128);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen }
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
a54be2bd26d6f0860d194d3aeedfa6b7fc14d24cTimo Sirainen updates = array_get(&ctx->client->search_updates, &count);
a54be2bd26d6f0860d194d3aeedfa6b7fc14d24cTimo Sirainen for (i = 0; i < count; i++) T_BEGIN {
a54be2bd26d6f0860d194d3aeedfa6b7fc14d24cTimo Sirainen imap_sync_send_search_update(ctx, &updates[i]);
a54be2bd26d6f0860d194d3aeedfa6b7fc14d24cTimo Sirainen } T_END;
a54be2bd26d6f0860d194d3aeedfa6b7fc14d24cTimo Sirainen}
a54be2bd26d6f0860d194d3aeedfa6b7fc14d24cTimo Sirainen
a54be2bd26d6f0860d194d3aeedfa6b7fc14d24cTimo Sirainenstruct imap_sync_context *
a54be2bd26d6f0860d194d3aeedfa6b7fc14d24cTimo Sirainenimap_sync_init(struct client *client, struct mailbox *box,
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen enum imap_sync_flags imap_flags, enum mailbox_sync_flags flags)
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen{
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen struct imap_sync_context *ctx;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen i_assert(client->mailbox == box);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen ctx = i_new(struct imap_sync_context, 1);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen ctx->client = client;
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen ctx->box = box;
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen ctx->imap_flags = imap_flags;
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen ctx->sync_ctx = mailbox_sync_init(box, flags);
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen ctx->t = mailbox_transaction_begin(box, 0);
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen ctx->mail = mail_alloc(ctx->t, MAIL_FETCH_FLAGS, 0);
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen ctx->messages_count = client->messages_count;
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen i_array_init(&ctx->tmp_keywords, client->keywords.announce_count + 8);
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen if ((client->enabled_features & MAILBOX_FEATURE_QRESYNC) != 0)
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen i_array_init(&ctx->expunges, 128);
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen
39e6fcc3e8b1ccb13087c232cb6bdea04d1a20a4Timo Sirainen client_send_mailbox_flags(client, FALSE);
39e6fcc3e8b1ccb13087c232cb6bdea04d1a20a4Timo Sirainen /* send search updates the first time after sync is initialized.
39e6fcc3e8b1ccb13087c232cb6bdea04d1a20a4Timo Sirainen it now contains expunged messages that must be sent before
39e6fcc3e8b1ccb13087c232cb6bdea04d1a20a4Timo Sirainen EXPUNGE replies. */
39e6fcc3e8b1ccb13087c232cb6bdea04d1a20a4Timo Sirainen imap_sync_send_search_updates(ctx);
39e6fcc3e8b1ccb13087c232cb6bdea04d1a20a4Timo Sirainen return ctx;
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen}
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainenint imap_sync_deinit(struct imap_sync_context *ctx)
68efcccb384f2d6871164b072457e87473502c51Timo Sirainen{
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen struct mailbox_status status;
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen int ret;
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen
d42c9a8f362b76740418c4f9f9441eb7fc661e57Timo Sirainen mail_free(&ctx->mail);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen if (array_is_created(&ctx->expunges))
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen array_free(&ctx->expunges);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen if (mailbox_sync_deinit(&ctx->sync_ctx, STATUS_UIDVALIDITY |
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen STATUS_MESSAGES | STATUS_RECENT, &status) < 0 ||
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen ctx->failed) {
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen mailbox_transaction_rollback(&ctx->t);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen i_free(ctx);
68efcccb384f2d6871164b072457e87473502c51Timo Sirainen return -1;
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen }
d42c9a8f362b76740418c4f9f9441eb7fc661e57Timo Sirainen
d42c9a8f362b76740418c4f9f9441eb7fc661e57Timo Sirainen ret = mailbox_transaction_commit(&ctx->t);
d42c9a8f362b76740418c4f9f9441eb7fc661e57Timo Sirainen
d42c9a8f362b76740418c4f9f9441eb7fc661e57Timo Sirainen if (status.uidvalidity != ctx->client->uidvalidity) {
d42c9a8f362b76740418c4f9f9441eb7fc661e57Timo Sirainen /* most clients would get confused by this. disconnect them. */
d42c9a8f362b76740418c4f9f9441eb7fc661e57Timo Sirainen client_disconnect_with_error(ctx->client,
d42c9a8f362b76740418c4f9f9441eb7fc661e57Timo Sirainen "Mailbox UIDVALIDITY changed");
d42c9a8f362b76740418c4f9f9441eb7fc661e57Timo Sirainen }
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen if (!ctx->no_newmail) {
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen if (status.messages < ctx->messages_count)
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen i_panic("Message count decreased");
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen ctx->client->messages_count = status.messages;
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen if (status.messages != ctx->messages_count) {
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen client_send_line(ctx->client,
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen t_strdup_printf("* %u EXISTS", status.messages));
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen }
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen if (status.recent != ctx->client->recent_count &&
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen !ctx->no_newmail) {
68efcccb384f2d6871164b072457e87473502c51Timo Sirainen ctx->client->recent_count = status.recent;
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen client_send_line(ctx->client,
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen t_strdup_printf("* %u RECENT", status.recent));
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen }
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen }
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen /* send search updates the second time after syncing in done.
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen now it contains added/removed messages. */
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen imap_sync_send_search_updates(ctx);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen if (array_is_created(&ctx->search_removes)) {
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen array_free(&ctx->search_removes);
5fb3bff645380804c9db2510940c41db6b8fdb01Timo Sirainen array_free(&ctx->search_adds);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen }
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen array_free(&ctx->tmp_keywords);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen i_free(ctx);
e60a349c641bb2f4723e4a395a25f55531682d2bTimo Sirainen return ret;
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen}
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainenstatic int imap_sync_send_flags(struct imap_sync_context *ctx, string_t *str)
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen{
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen enum mail_flags flags;
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen const char *const *keywords;
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen mail_set_seq(ctx->mail, ctx->seq);
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen flags = mail_get_flags(ctx->mail);
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen keywords = client_get_keyword_names(ctx->client, &ctx->tmp_keywords,
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen mail_get_keyword_indexes(ctx->mail));
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen if ((flags & MAIL_DELETED) != 0)
e60a349c641bb2f4723e4a395a25f55531682d2bTimo Sirainen ctx->client->sync_seen_deletes = TRUE;
e60a349c641bb2f4723e4a395a25f55531682d2bTimo Sirainen
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen str_truncate(str, 0);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen str_printfa(str, "* %u FETCH (", ctx->seq);
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen if (ctx->imap_flags & IMAP_SYNC_FLAG_SEND_UID)
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen str_printfa(str, "UID %u ", ctx->mail->uid);
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen if ((mailbox_get_enabled_features(ctx->box) &
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen MAILBOX_FEATURE_CONDSTORE) != 0) {
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen str_printfa(str, "MODSEQ %llu ",
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen (unsigned long long)mail_get_modseq(ctx->mail));
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen }
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen str_append(str, "FLAGS (");
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen imap_write_flags(str, flags, keywords);
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen str_append(str, "))");
150e64c376365becf1ec5c9d45912ecb840eea96Timo Sirainen return client_send_line(ctx->client, str_c(str));
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen}
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainenstatic int imap_sync_send_modseq(struct imap_sync_context *ctx, string_t *str)
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen{
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen mail_set_seq(ctx->mail, ctx->seq);
e60a349c641bb2f4723e4a395a25f55531682d2bTimo Sirainen
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen str_truncate(str, 0);
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen str_printfa(str, "* %u FETCH (", ctx->seq);
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen if (ctx->imap_flags & IMAP_SYNC_FLAG_SEND_UID)
150e64c376365becf1ec5c9d45912ecb840eea96Timo Sirainen str_printfa(str, "UID %u ", ctx->mail->uid);
150e64c376365becf1ec5c9d45912ecb840eea96Timo Sirainen str_printfa(str, "MODSEQ %llu)",
150e64c376365becf1ec5c9d45912ecb840eea96Timo Sirainen (unsigned long long)mail_get_modseq(ctx->mail));
150e64c376365becf1ec5c9d45912ecb840eea96Timo Sirainen return client_send_line(ctx->client, str_c(str));
150e64c376365becf1ec5c9d45912ecb840eea96Timo Sirainen}
150e64c376365becf1ec5c9d45912ecb840eea96Timo Sirainen
18634dae6e304bac982bb1e0ff1b6b88fc448dbcTimo Sirainenstatic void imap_sync_vanished(struct imap_sync_context *ctx)
294f1a51763015cda0e2d874c5027d6fe7a2cd54Timo Sirainen{
e60a349c641bb2f4723e4a395a25f55531682d2bTimo Sirainen const struct seq_range *seqs;
e60a349c641bb2f4723e4a395a25f55531682d2bTimo Sirainen unsigned int i, count;
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen string_t *line;
39e6fcc3e8b1ccb13087c232cb6bdea04d1a20a4Timo Sirainen uint32_t seq, prev_uid, start_uid;
150e64c376365becf1ec5c9d45912ecb840eea96Timo Sirainen bool comma = FALSE;
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen
e60a349c641bb2f4723e4a395a25f55531682d2bTimo Sirainen /* Convert expunge sequences to UIDs and send them in VANISHED line. */
e60a349c641bb2f4723e4a395a25f55531682d2bTimo Sirainen seqs = array_get(&ctx->expunges, &count);
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen if (count == 0)
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen return;
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen
42507d758b053bb483de58fba55c73a9eb5d3fbaTimo Sirainen line = t_str_new(256);
e60a349c641bb2f4723e4a395a25f55531682d2bTimo Sirainen str_append(line, "* VANISHED ");
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen for (i = 0; i < count; i++) {
prev_uid = start_uid = 0;
for (seq = seqs[i].seq1; seq <= seqs[i].seq2; seq++) {
mail_set_seq(ctx->mail, seq);
if (prev_uid + 1 != ctx->mail->uid) {
if (start_uid != 0) {
if (!comma)
comma = TRUE;
else
str_append_c(line, ',');
str_printfa(line, "%u", start_uid);
if (start_uid != prev_uid) {
str_printfa(line, ":%u",
prev_uid);
}
}
start_uid = ctx->mail->uid;
}
prev_uid = ctx->mail->uid;
}
if (!comma)
comma = TRUE;
else
str_append_c(line, ',');
str_printfa(line, "%u", start_uid);
if (start_uid != prev_uid)
str_printfa(line, ":%u", prev_uid);
}
str_append(line, "\r\n");
o_stream_send(ctx->client->output, str_data(line), str_len(line));
}
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;
}
/* EXPUNGEs must come last */
i_assert(!array_is_created(&ctx->expunges) ||
array_count(&ctx->expunges) == 0 ||
ctx->sync_rec.type == MAILBOX_SYNC_TYPE_EXPUNGE);
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:
ctx->client->sync_seen_expunges = TRUE;
if (array_is_created(&ctx->expunges)) {
/* Use a single VANISHED line */
seq_range_array_add_range(&ctx->expunges,
ctx->sync_rec.seq1,
ctx->sync_rec.seq2);
ctx->messages_count -=
ctx->sync_rec.seq2 -
ctx->sync_rec.seq1 + 1;
break;
}
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;
case MAILBOX_SYNC_TYPE_MODSEQ:
if ((ctx->client->enabled_features &
MAILBOX_FEATURE_CONDSTORE) == 0)
break;
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_modseq(ctx, str);
}
break;
}
if (ret <= 0) {
/* failure / buffer full */
break;
}
ctx->seq = 0;
}
if (array_is_created(&ctx->expunges))
imap_sync_vanished(ctx);
return ret;
}
static bool cmd_finish_sync(struct client_command_context *cmd)
{
if (cmd->sync->callback != NULL)
return cmd->sync->callback(cmd);
else {
client_send_tagline(cmd, cmd->sync->tagline);
return TRUE;
}
}
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) {
if (cmd_finish_sync(cmd))
client_command_free(cmd);
}
}
return cmd_finish_sync(sync_cmd);
}
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 != 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;
}
static bool
cmd_sync_full(struct client_command_context *cmd, enum mailbox_sync_flags flags,
enum imap_sync_flags imap_flags, const char *tagline,
imap_sync_callback_t *callback)
{
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 */
i_assert(callback == NULL);
client_send_tagline(cmd, tagline);
return TRUE;
}
cmd->sync = p_new(cmd->pool, struct client_sync_context, 1);
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->sync->callback = callback;
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;
}
bool cmd_sync(struct client_command_context *cmd, enum mailbox_sync_flags flags,
enum imap_sync_flags imap_flags, const char *tagline)
{
return cmd_sync_full(cmd, flags, imap_flags, tagline, NULL);
}
bool cmd_sync_callback(struct client_command_context *cmd,
enum mailbox_sync_flags flags,
enum imap_sync_flags imap_flags,
imap_sync_callback_t *callback)
{
return cmd_sync_full(cmd, flags, imap_flags, NULL, callback);
}
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) {
if (cmd_finish_sync(cmd)) {
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);
}