# HG changeset patch # User Timo Sirainen # Date 1363268469 -7200 # Node ID c6082de4bf5b510130daf2eb9bdfcfad11b0a08f # Parent f3cd9e5cbe99ccb60fa2021bde31cfb81fd38a87 dsync: Added support for syncing mailbox attributes. diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/Makefile.am --- a/src/doveadm/dsync/Makefile.am Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/Makefile.am Thu Mar 14 15:41:09 2013 +0200 @@ -21,6 +21,7 @@ dsync-brain-mails.c \ dsync-deserializer.c \ dsync-mail.c \ + dsync-mailbox.c \ dsync-mailbox-import.c \ dsync-mailbox-export.c \ dsync-mailbox-state.c \ diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-brain-mailbox.c --- a/src/doveadm/dsync/dsync-brain-mailbox.c Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-brain-mailbox.c Thu Mar 14 15:41:09 2013 +0200 @@ -84,7 +84,7 @@ { if (brain->backup_send) { /* we have an exporter, but no importer. */ - brain->box_send_state = DSYNC_BOX_STATE_CHANGES; + brain->box_send_state = DSYNC_BOX_STATE_ATTRIBUTES; brain->box_recv_state = brain->mail_requests ? DSYNC_BOX_STATE_MAIL_REQUESTS : DSYNC_BOX_STATE_RECV_LAST_COMMON; @@ -93,10 +93,10 @@ brain->box_send_state = brain->mail_requests ? DSYNC_BOX_STATE_MAIL_REQUESTS : DSYNC_BOX_STATE_DONE; - brain->box_recv_state = DSYNC_BOX_STATE_CHANGES; + brain->box_recv_state = DSYNC_BOX_STATE_ATTRIBUTES; } else { - brain->box_send_state = DSYNC_BOX_STATE_CHANGES; - brain->box_recv_state = DSYNC_BOX_STATE_CHANGES; + brain->box_send_state = DSYNC_BOX_STATE_ATTRIBUTES; + brain->box_recv_state = DSYNC_BOX_STATE_ATTRIBUTES; } } diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-brain-mails.c --- a/src/doveadm/dsync/dsync-brain-mails.c Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-brain-mails.c Thu Mar 14 15:41:09 2013 +0200 @@ -11,6 +11,7 @@ const char *dsync_box_state_names[DSYNC_BOX_STATE_DONE+1] = { "mailbox", "changes", + "attributes", "mail_requests", "mails", "recv_last_common", @@ -59,6 +60,34 @@ return TRUE; } +static bool dsync_brain_recv_mailbox_attribute(struct dsync_brain *brain) +{ + const struct dsync_mailbox_attribute *attr; + enum dsync_ibc_recv_ret ret; + + if ((ret = dsync_ibc_recv_mailbox_attribute(brain->ibc, &attr)) == 0) + return FALSE; + if (ret == DSYNC_IBC_RECV_RET_FINISHED) { + brain->box_recv_state = DSYNC_BOX_STATE_CHANGES; + return TRUE; + } + if (dsync_mailbox_import_attribute(brain->box_importer, attr) < 0) + brain->failed = TRUE; + return TRUE; +} + +static void dsync_brain_send_mailbox_attribute(struct dsync_brain *brain) +{ + const struct dsync_mailbox_attribute *attr; + + while ((attr = dsync_mailbox_export_next_attr(brain->box_exporter)) != NULL) { + if (dsync_ibc_send_mailbox_attribute(brain->ibc, attr) == 0) + return; + } + dsync_ibc_send_end_of_list(brain->ibc); + brain->box_send_state = DSYNC_BOX_STATE_CHANGES; +} + static bool dsync_brain_recv_mail_change(struct dsync_brain *brain) { const struct dsync_mail_change *change; @@ -278,6 +307,9 @@ case DSYNC_BOX_STATE_MAILBOX: changed = dsync_brain_master_sync_recv_mailbox(brain); break; + case DSYNC_BOX_STATE_ATTRIBUTES: + changed = dsync_brain_recv_mailbox_attribute(brain); + break; case DSYNC_BOX_STATE_CHANGES: changed = dsync_brain_recv_mail_change(brain); break; @@ -299,6 +331,10 @@ case DSYNC_BOX_STATE_MAILBOX: /* wait for mailbox to be received first */ break; + case DSYNC_BOX_STATE_ATTRIBUTES: + dsync_brain_send_mailbox_attribute(brain); + changed = TRUE; + break; case DSYNC_BOX_STATE_CHANGES: dsync_brain_send_mail_change(brain); changed = TRUE; diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-brain-private.h --- a/src/doveadm/dsync/dsync-brain-private.h Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-brain-private.h Thu Mar 14 15:41:09 2013 +0200 @@ -38,6 +38,7 @@ enum dsync_box_state { DSYNC_BOX_STATE_MAILBOX, DSYNC_BOX_STATE_CHANGES, + DSYNC_BOX_STATE_ATTRIBUTES, DSYNC_BOX_STATE_MAIL_REQUESTS, DSYNC_BOX_STATE_MAILS, DSYNC_BOX_STATE_RECV_LAST_COMMON, diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-ibc-pipe.c --- a/src/doveadm/dsync/dsync-ibc-pipe.c Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-ibc-pipe.c Thu Mar 14 15:41:09 2013 +0200 @@ -16,6 +16,7 @@ ITEM_MAILBOX_TREE_NODE, ITEM_MAILBOX_DELETE, ITEM_MAILBOX, + ITEM_MAILBOX_ATTRIBUTE, ITEM_MAIL_CHANGE, ITEM_MAIL_REQUEST, ITEM_MAIL @@ -31,6 +32,7 @@ struct dsync_mailbox_node node; guid_128_t mailbox_guid; struct dsync_mailbox dsync_box; + struct dsync_mailbox_attribute attr; struct dsync_mail_change change; struct dsync_mail_request request; struct dsync_mail mail; @@ -84,6 +86,7 @@ case ITEM_HANDSHAKE: case ITEM_MAILBOX: case ITEM_MAILBOX_TREE_NODE: + case ITEM_MAILBOX_ATTRIBUTE: case ITEM_MAIL_CHANGE: case ITEM_MAIL_REQUEST: case ITEM_MAIL: @@ -342,6 +345,35 @@ } static void +dsync_ibc_pipe_send_mailbox_attribute(struct dsync_ibc *ibc, + const struct dsync_mailbox_attribute *attr) +{ + struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; + struct item *item; + + item = dsync_ibc_pipe_push_item(pipe->remote, ITEM_MAILBOX_ATTRIBUTE); + dsync_mailbox_attribute_dup(item->pool, attr, &item->u.attr); +} + +static enum dsync_ibc_recv_ret +dsync_ibc_pipe_recv_mailbox_attribute(struct dsync_ibc *ibc, + const struct dsync_mailbox_attribute **attr_r) +{ + struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; + struct item *item; + + if (dsync_ibc_pipe_try_pop_eol(pipe)) + return DSYNC_IBC_RECV_RET_FINISHED; + + item = dsync_ibc_pipe_pop_item(pipe, ITEM_MAILBOX_ATTRIBUTE); + if (item == NULL) + return DSYNC_IBC_RECV_RET_TRYAGAIN; + + *attr_r = &item->u.attr; + return DSYNC_IBC_RECV_RET_OK; +} + +static void dsync_ibc_pipe_send_change(struct dsync_ibc *ibc, const struct dsync_mail_change *change) { @@ -470,6 +502,8 @@ dsync_ibc_pipe_recv_mailbox_deletes, dsync_ibc_pipe_send_mailbox, dsync_ibc_pipe_recv_mailbox, + dsync_ibc_pipe_send_mailbox_attribute, + dsync_ibc_pipe_recv_mailbox_attribute, dsync_ibc_pipe_send_change, dsync_ibc_pipe_recv_change, dsync_ibc_pipe_send_mail_request, diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-ibc-private.h --- a/src/doveadm/dsync/dsync-ibc-private.h Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-ibc-private.h Thu Mar 14 15:41:09 2013 +0200 @@ -43,6 +43,12 @@ (*recv_mailbox)(struct dsync_ibc *ibc, const struct dsync_mailbox **dsync_box_r); + void (*send_mailbox_attribute)(struct dsync_ibc *ibc, + const struct dsync_mailbox_attribute *attr); + enum dsync_ibc_recv_ret + (*recv_mailbox_attribute)(struct dsync_ibc *ibc, + const struct dsync_mailbox_attribute **attr_r); + void (*send_change)(struct dsync_ibc *ibc, const struct dsync_mail_change *change); enum dsync_ibc_recv_ret diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-ibc-stream.c --- a/src/doveadm/dsync/dsync-ibc-stream.c Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-ibc-stream.c Thu Mar 14 15:41:09 2013 +0200 @@ -28,7 +28,10 @@ #define DSYNC_IBC_STREAM_OUTBUF_THROTTLE_SIZE (1024*128) #define DSYNC_PROTOCOL_VERSION_MAJOR 3 -#define DSYNC_HANDSHAKE_VERSION "VERSION\tdsync\t3\t0\n" +#define DSYNC_PROTOCOL_VERSION_MINOR 1 +#define DSYNC_HANDSHAKE_VERSION "VERSION\tdsync\t3\t1\n" + +#define DSYNC_PROTOCOL_MINOR_HAVE_ATTRIBUTES 1 enum item_type { ITEM_NONE, @@ -40,6 +43,7 @@ ITEM_MAILBOX_DELETE, ITEM_MAILBOX, + ITEM_MAILBOX_ATTRIBUTE, ITEM_MAIL_CHANGE, ITEM_MAIL_REQUEST, ITEM_MAIL, @@ -92,6 +96,11 @@ "first_recent_uid highest_modseq highest_pvt_modseq", .optional_keys = "mailbox_lost cache_fields have_guids" }, + { .name = "mailbox_attribute", + .chr = 'A', + .required_keys = "type key", + .optional_keys = "value deleted last_change modseq" + }, { .name = "mail_change", .chr = 'C', .required_keys = "type uid", @@ -126,6 +135,7 @@ struct io *io; struct timeout *to; + unsigned int minor_version; struct dsync_serializer *serializers[ITEM_END_OF_LIST]; struct dsync_deserializer *deserializers[ITEM_END_OF_LIST]; @@ -195,16 +205,9 @@ &data, &size, 0)) > 0) { add = '\0'; for (i = 0; i < size; i++) { - if (data[i] == '\n') { - if ((i == 0 && ibc->mail_output_last != '\r') || - (i > 0 && data[i-1] != '\r')) { - /* missing CR */ - add = '\r'; - break; - } - } else if (data[i] == '.' && - ((i == 0 && ibc->mail_output_last == '\n') || - (i > 0 && data[i-1] == '\n'))) { + if (data[i] == '.' && + ((i == 0 && ibc->mail_output_last == '\n') || + (i > 0 && data[i-1] == '\n'))) { /* escape the dot */ add = '.'; break; @@ -243,7 +246,8 @@ return -1; } - /* finished sending the stream */ + /* finished sending the stream. use "CRLF." instead of "LF." just in + case we're sending binary data that ends with CR. */ o_stream_nsend_str(ibc->output, "\r\n.\r\n"); i_stream_unref(&ibc->mail_output); return 1; @@ -429,8 +433,9 @@ return TRUE; if (!ibc->version_received) { - if (!version_string_verify(line, "dsync", - DSYNC_PROTOCOL_VERSION_MAJOR)) { + if (!version_string_verify_full(line, "dsync", + DSYNC_PROTOCOL_VERSION_MAJOR, + &ibc->minor_version)) { dsync_ibc_input_error(ibc, NULL, "Remote dsync doesn't use compatible protocol"); return DSYNC_IBC_RECV_RET_TRYAGAIN; @@ -1185,6 +1190,107 @@ } static void +dsync_ibc_stream_send_mailbox_attribute(struct dsync_ibc *_ibc, + const struct dsync_mailbox_attribute *attr) +{ + struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; + struct dsync_serializer_encoder *encoder; + string_t *str = t_str_new(128); + char type[2]; + + if (ibc->minor_version < DSYNC_PROTOCOL_MINOR_HAVE_ATTRIBUTES) + return; + + str_append_c(str, items[ITEM_MAILBOX_ATTRIBUTE].chr); + encoder = dsync_serializer_encode_begin(ibc->serializers[ITEM_MAILBOX_ATTRIBUTE]); + + type[0] = type[1] = '\0'; + switch (attr->type) { + case MAIL_ATTRIBUTE_TYPE_PRIVATE: + type[0] = 'p'; + break; + case MAIL_ATTRIBUTE_TYPE_SHARED: + type[0] = 's'; + break; + } + i_assert(type[0] != '\0'); + dsync_serializer_encode_add(encoder, "type", type); + dsync_serializer_encode_add(encoder, "key", attr->key); + if (attr->value != NULL) + dsync_serializer_encode_add(encoder, "value", attr->value); + + if (attr->deleted) + dsync_serializer_encode_add(encoder, "deleted", ""); + if (attr->last_change != 0) { + dsync_serializer_encode_add(encoder, "last_change", + dec2str(attr->last_change)); + } + if (attr->modseq != 0) { + dsync_serializer_encode_add(encoder, "modseq", + dec2str(attr->modseq)); + } + + dsync_serializer_encode_finish(&encoder, str); + dsync_ibc_stream_send_string(ibc, str); +} + +static enum dsync_ibc_recv_ret +dsync_ibc_stream_recv_mailbox_attribute(struct dsync_ibc *_ibc, + const struct dsync_mailbox_attribute **attr_r) +{ + struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; + pool_t pool = ibc->ret_pool; + struct dsync_deserializer_decoder *decoder; + struct dsync_mailbox_attribute *attr; + const char *value; + enum dsync_ibc_recv_ret ret; + + if (ibc->minor_version < DSYNC_PROTOCOL_MINOR_HAVE_ATTRIBUTES) + return DSYNC_IBC_RECV_RET_FINISHED; + + p_clear(pool); + attr = p_new(pool, struct dsync_mailbox_attribute, 1); + + ret = dsync_ibc_stream_input_next(ibc, ITEM_MAIL_CHANGE, &decoder); + if (ret != DSYNC_IBC_RECV_RET_OK) + return ret; + + value = dsync_deserializer_decode_get(decoder, "type"); + switch (*value) { + case 'p': + attr->type = MAIL_ATTRIBUTE_TYPE_PRIVATE; + break; + case 's': + attr->type = MAIL_ATTRIBUTE_TYPE_SHARED; + break; + default: + dsync_ibc_input_error(ibc, decoder, "Invalid type: %s", value); + return DSYNC_IBC_RECV_RET_TRYAGAIN; + } + + value = dsync_deserializer_decode_get(decoder, "key"); + attr->key = p_strdup(pool, value); + + if (dsync_deserializer_decode_try(decoder, "value", &value)) + attr->value = p_strdup(pool, value); + if (dsync_deserializer_decode_try(decoder, "deleted", &value)) + attr->deleted = TRUE; + if (dsync_deserializer_decode_try(decoder, "last_change", &value) && + str_to_time(value, &attr->last_change) < 0) { + dsync_ibc_input_error(ibc, decoder, "Invalid last_change"); + return DSYNC_IBC_RECV_RET_TRYAGAIN; + } + if (dsync_deserializer_decode_try(decoder, "modseq", &value) && + str_to_uint64(value, &attr->modseq) < 0) { + dsync_ibc_input_error(ibc, decoder, "Invalid modseq"); + return DSYNC_IBC_RECV_RET_TRYAGAIN; + } + + *attr_r = attr; + return DSYNC_IBC_RECV_RET_OK; +} + +static void dsync_ibc_stream_send_change(struct dsync_ibc *_ibc, const struct dsync_mail_change *change) { @@ -1580,6 +1686,8 @@ dsync_ibc_stream_recv_mailbox_deletes, dsync_ibc_stream_send_mailbox, dsync_ibc_stream_recv_mailbox, + dsync_ibc_stream_send_mailbox_attribute, + dsync_ibc_stream_recv_mailbox_attribute, dsync_ibc_stream_send_change, dsync_ibc_stream_recv_change, dsync_ibc_stream_send_mail_request, diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-ibc.c --- a/src/doveadm/dsync/dsync-ibc.c Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-ibc.c Thu Mar 14 15:41:09 2013 +0200 @@ -123,6 +123,23 @@ return ibc->v.recv_mailbox(ibc, dsync_box_r); } +enum dsync_ibc_send_ret ATTR_NOWARN_UNUSED_RESULT +dsync_ibc_send_mailbox_attribute(struct dsync_ibc *ibc, + const struct dsync_mailbox_attribute *attr) +{ + T_BEGIN { + ibc->v.send_mailbox_attribute(ibc, attr); + } T_END; + return dsync_ibc_send_ret(ibc); +} + +enum dsync_ibc_recv_ret +dsync_ibc_recv_mailbox_attribute(struct dsync_ibc *ibc, + const struct dsync_mailbox_attribute **attr_r) +{ + return ibc->v.recv_mailbox_attribute(ibc, attr_r); +} + enum dsync_ibc_send_ret dsync_ibc_send_change(struct dsync_ibc *ibc, const struct dsync_mail_change *change) diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-ibc.h --- a/src/doveadm/dsync/dsync-ibc.h Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-ibc.h Thu Mar 14 15:41:09 2013 +0200 @@ -11,6 +11,7 @@ struct dsync_mailbox_state; struct dsync_mailbox_node; struct dsync_mailbox_delete; +struct dsync_mailbox_attribute; struct dsync_mail; struct dsync_mail_change; struct dsync_mail_request; @@ -96,6 +97,13 @@ const struct dsync_mailbox **dsync_box_r); enum dsync_ibc_send_ret ATTR_NOWARN_UNUSED_RESULT +dsync_ibc_send_mailbox_attribute(struct dsync_ibc *ibc, + const struct dsync_mailbox_attribute *attr); +enum dsync_ibc_recv_ret +dsync_ibc_recv_mailbox_attribute(struct dsync_ibc *ibc, + const struct dsync_mailbox_attribute **attr_r); + +enum dsync_ibc_send_ret ATTR_NOWARN_UNUSED_RESULT dsync_ibc_send_change(struct dsync_ibc *ibc, const struct dsync_mail_change *change); enum dsync_ibc_recv_ret diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-mailbox-export.c --- a/src/doveadm/dsync/dsync-mailbox-export.c Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-mailbox-export.c Thu Mar 14 15:41:09 2013 +0200 @@ -8,6 +8,7 @@ #include "mail-search-build.h" #include "dsync-transaction-log-scan.h" #include "dsync-mail.h" +#include "dsync-mailbox.h" #include "dsync-mailbox-export.h" struct dsync_mail_guid_instances { @@ -41,6 +42,9 @@ unsigned int change_idx; uint32_t highest_changed_uid; + ARRAY(struct dsync_mailbox_attribute) attr_changes; + unsigned int attr_change_idx; + struct dsync_mail_change change; struct dsync_mail dsync_mail; @@ -396,6 +400,101 @@ } static void +dsync_mailbox_export_attr_changes(struct dsync_mailbox_exporter *exporter, + enum mail_attribute_type type) +{ + HASH_TABLE_TYPE(dsync_attr_change) attr_changes; + struct mailbox_attribute_iter *iter; + struct dsync_mailbox_attribute lookup_attr, *attr; + struct dsync_mailbox_attribute *attr_change; + const char *key; + struct mail_attribute_value value; + bool export_all_attrs; + + export_all_attrs = exporter->return_all_mails || + exporter->last_common_uid == 0; + attr_changes = dsync_transaction_log_scan_get_attr_hash(exporter->log_scan); + lookup_attr.type = type; + + iter = mailbox_attribute_iter_init(exporter->box, type, ""); + while ((key = mailbox_attribute_iter_next(iter)) != NULL) { + lookup_attr.key = key; + attr_change = hash_table_lookup(attr_changes, &lookup_attr); + if (attr_change == NULL && !export_all_attrs) + continue; + + if (mailbox_attribute_get(exporter->trans, type, key, &value) < 0) { + exporter->error = p_strdup_printf(exporter->pool, + "Mailbox attribute %s lookup failed: %s", key, + mailbox_get_last_error(exporter->box, NULL)); + break; + } + if ((value.flags & MAIL_ATTRIBUTE_VALUE_FLAG_READONLY) != 0) { + /* readonly attributes can't be changed, + no point in exporting them */ + continue; + } + + attr = array_append_space(&exporter->attr_changes); + attr->type = type; + attr->value = p_strdup(exporter->pool, value.value); + attr->last_change = value.last_change; + if (attr_change != NULL) { + i_assert(!attr_change->exported); + attr_change->exported = TRUE; + attr->key = attr_change->key; + attr->deleted = attr_change->deleted && + attr->value == NULL; + attr->modseq = attr_change->modseq; + } else { + attr->key = p_strdup(exporter->pool, key); + } + } + if (mailbox_attribute_iter_deinit(&iter) < 0) { + exporter->error = p_strdup_printf(exporter->pool, + "Mailbox attribute iteration failed: %s", + mailbox_get_last_error(exporter->box, NULL)); + } +} + +static void +dsync_mailbox_export_nonexistent_attrs(struct dsync_mailbox_exporter *exporter) +{ + HASH_TABLE_TYPE(dsync_attr_change) attr_changes; + struct hash_iterate_context *iter; + struct dsync_mailbox_attribute *attr; + struct mail_attribute_value value; + + attr_changes = dsync_transaction_log_scan_get_attr_hash(exporter->log_scan); + + iter = hash_table_iterate_init(attr_changes); + while (hash_table_iterate(iter, attr_changes, &attr, &attr)) { + if (attr->exported || !attr->deleted) + continue; + + /* lookup the value mainly to get its last_change value. */ + if (mailbox_attribute_get(exporter->trans, attr->type, + attr->key, &value) < 0) { + exporter->error = p_strdup_printf(exporter->pool, + "Mailbox attribute %s lookup failed: %s", attr->key, + mailbox_get_last_error(exporter->box, NULL)); + break; + } + if ((value.flags & MAIL_ATTRIBUTE_VALUE_FLAG_READONLY) != 0) + continue; + + attr->last_change = value.last_change; + if (value.value != NULL) { + attr->value = p_strdup(exporter->pool, value.value); + attr->deleted = FALSE; + } + attr->exported = TRUE; + array_append(&exporter->attr_changes, attr, 1); + } + hash_table_iterate_deinit(&iter); +} + +static void dsync_mailbox_export_log_scan(struct dsync_mailbox_exporter *exporter, struct dsync_transaction_log_scan *log_scan) { @@ -456,9 +555,29 @@ dsync_mailbox_export_search(exporter); /* get the changes sorted by UID */ dsync_mailbox_export_sort_changes(exporter); + + p_array_init(&exporter->attr_changes, pool, 16); + dsync_mailbox_export_attr_changes(exporter, MAIL_ATTRIBUTE_TYPE_PRIVATE); + dsync_mailbox_export_attr_changes(exporter, MAIL_ATTRIBUTE_TYPE_SHARED); + dsync_mailbox_export_nonexistent_attrs(exporter); return exporter; } +const struct dsync_mailbox_attribute * +dsync_mailbox_export_next_attr(struct dsync_mailbox_exporter *exporter) +{ + const struct dsync_mailbox_attribute *changes; + unsigned int count; + + if (exporter->error != NULL) + return NULL; + + changes = array_get(&exporter->attr_changes, &count); + if (exporter->attr_change_idx == count) + return NULL; + return &changes[exporter->attr_change_idx++]; +} + const struct dsync_mail_change * dsync_mailbox_export_next(struct dsync_mailbox_exporter *exporter) { @@ -471,7 +590,6 @@ changes = array_get(&exporter->sorted_changes, &count); if (exporter->change_idx == count) return NULL; - return changes[exporter->change_idx++]; } diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-mailbox-export.h --- a/src/doveadm/dsync/dsync-mailbox-export.h Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-mailbox-export.h Thu Mar 14 15:41:09 2013 +0200 @@ -11,6 +11,8 @@ struct dsync_transaction_log_scan *log_scan, uint32_t last_common_uid, enum dsync_mailbox_exporter_flags flags); +const struct dsync_mailbox_attribute * +dsync_mailbox_export_next_attr(struct dsync_mailbox_exporter *exporter); const struct dsync_mail_change * dsync_mailbox_export_next(struct dsync_mailbox_exporter *exporter); @@ -18,7 +20,6 @@ const struct dsync_mail_request *request); const struct dsync_mail * dsync_mailbox_export_next_mail(struct dsync_mailbox_exporter *exporter); - int dsync_mailbox_export_deinit(struct dsync_mailbox_exporter **exporter, const char **error_r); diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-mailbox-import.c --- a/src/doveadm/dsync/dsync-mailbox-import.c Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-mailbox-import.c Thu Mar 14 15:41:09 2013 +0200 @@ -10,6 +10,7 @@ #include "mail-search-build.h" #include "dsync-transaction-log-scan.h" #include "dsync-mail.h" +#include "dsync-mailbox.h" #include "dsync-mailbox-import.h" struct importer_mail { @@ -66,6 +67,7 @@ /* UID => struct dsync_mail_change */ HASH_TABLE_TYPE(dsync_uid_mail_change) local_changes; + HASH_TABLE_TYPE(dsync_attr_change) local_attr_changes; ARRAY_TYPE(seq_range) maybe_expunge_uids; ARRAY(struct dsync_mail_change *) maybe_saves; @@ -196,9 +198,122 @@ dsync_mailbox_import_search_init(importer); importer->local_changes = dsync_transaction_log_scan_get_hash(log_scan); + importer->local_attr_changes = dsync_transaction_log_scan_get_attr_hash(log_scan); return importer; } +static int +dsync_mailbox_import_lookup_attr(struct dsync_mailbox_importer *importer, + enum mail_attribute_type type, const char *key, + const struct dsync_mailbox_attribute **attr_r) +{ + struct dsync_mailbox_attribute lookup_attr, *attr; + const struct dsync_mailbox_attribute *attr_change; + struct mail_attribute_value value; + + *attr_r = NULL; + + if (mailbox_attribute_get(importer->trans, type, key, &value) < 0) { + i_error("Mailbox %s: Failed to get attribute %s: %s", + mailbox_get_vname(importer->box), key, + mailbox_get_last_error(importer->box, NULL)); + importer->failed = TRUE; + return -1; + } + + lookup_attr.type = type; + lookup_attr.key = key; + + attr_change = hash_table_lookup(importer->local_attr_changes, + &lookup_attr); + if (attr_change == NULL && value.value == NULL) { + /* we have no knowledge of this attribute */ + return 0; + } + attr = t_new(struct dsync_mailbox_attribute, 1); + attr->type = type; + attr->key = key; + attr->value = value.value; + attr->last_change = value.last_change; + if (attr_change != NULL) { + attr->deleted = attr_change->deleted && attr->value == NULL; + attr->modseq = attr_change->modseq; + } + *attr_r = attr; + return 0; +} + +int dsync_mailbox_import_attribute(struct dsync_mailbox_importer *importer, + const struct dsync_mailbox_attribute *attr) +{ + const struct dsync_mailbox_attribute *local_attr; + int ret; + + i_assert(attr->value != NULL || attr->deleted); + + if (dsync_mailbox_import_lookup_attr(importer, attr->type, + attr->key, &local_attr) < 0) + return -1; + if (local_attr == NULL) { + /* we haven't seen this locally -> use whatever remote has */ + } else if (null_strcmp(attr->value, local_attr->value) == 0) { + /* the values are identical anyway -> we can skip them. + FIXME: but should timestamp/modseq still be updated?.. */ + return 0; + } else if (local_attr->modseq <= importer->last_common_modseq && + attr->modseq > importer->last_common_modseq && + importer->last_common_modseq > 0) { + /* we're doing incremental syncing, and we can see that the + attribute was changed remotely, but not locally -> use it */ + } else if (local_attr->modseq > importer->last_common_modseq && + attr->modseq <= importer->last_common_modseq && + importer->last_common_modseq > 0) { + /* we're doing incremental syncing, and we can see that the + attribute was changed locally, but not remotely -> ignore */ + return 0; + } else if (attr->last_change > local_attr->last_change) { + /* remote has a newer timestamp -> use it */ + } else if (attr->last_change < local_attr->last_change) { + /* remote has an older timestamp -> ignore */ + return 0; + } else { + /* the timestamps are the same. now we're down to guessing + the right answer. first try to use modseqs, but if even they + are the same, fallback to just picking one based on the + value. */ + if (attr->modseq > local_attr->modseq) { + /* remote has a higher modseq -> use it */ + } else if (attr->modseq < local_attr->modseq) { + /* remote has an older modseq -> ignore */ + return 0; + } else if (attr->value != NULL && local_attr->value == NULL) { + /* remote has a value and local doesn't -> use it */ + } else if (attr->value == NULL && local_attr->value != NULL) { + /* remote doesn't have a value, bt local does -> skip */ + return 0; + } else { + /* now we have absolutely no reasonable guesses left. + just pick one of them that are used. */ + if (strcmp(attr->value, local_attr->value) < 0) + return 0; + } + } + if (attr->value != NULL) { + ret = mailbox_attribute_set(importer->trans, attr->type, + attr->key, attr->value); + } else { + ret = mailbox_attribute_unset(importer->trans, attr->type, + attr->key); + } + if (ret < 0) { + i_error("Mailbox %s: Failed to set attribute %s: %s", + mailbox_get_vname(importer->box), attr->key, + mailbox_get_last_error(importer->box, NULL)); + importer->failed = TRUE; + } + return ret; +} + static void dsync_mail_error(struct dsync_mailbox_importer *importer, struct mail *mail, const char *field) { diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-mailbox-import.h --- a/src/doveadm/dsync/dsync-mailbox-import.h Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-mailbox-import.h Thu Mar 14 15:41:09 2013 +0200 @@ -10,6 +10,7 @@ }; struct mailbox; +struct dsync_mailbox_attribute; struct dsync_mail; struct dsync_mail_change; struct dsync_transaction_log_scan; @@ -25,6 +26,8 @@ uint64_t remote_highest_modseq, uint64_t remote_highest_pvt_modseq, enum dsync_mailbox_import_flags flags); +int dsync_mailbox_import_attribute(struct dsync_mailbox_importer *importer, + const struct dsync_mailbox_attribute *attr); int dsync_mailbox_import_change(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change); void dsync_mailbox_import_changes_finish(struct dsync_mailbox_importer *importer); diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-mailbox.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/doveadm/dsync/dsync-mailbox.c Thu Mar 14 15:41:09 2013 +0200 @@ -0,0 +1,17 @@ +/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "dsync-mailbox.h" + +void dsync_mailbox_attribute_dup(pool_t pool, + const struct dsync_mailbox_attribute *src, + struct dsync_mailbox_attribute *dest_r) +{ + dest_r->type = src->type; + dest_r->key = p_strdup(pool, src->key); + dest_r->value = p_strdup(pool, src->value); + + dest_r->deleted = src->deleted; + dest_r->last_change = src->last_change; + dest_r->modseq = src->modseq; +} diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-mailbox.h --- a/src/doveadm/dsync/dsync-mailbox.h Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-mailbox.h Thu Mar 14 15:41:09 2013 +0200 @@ -15,4 +15,20 @@ ARRAY_TYPE(mailbox_cache_field) cache_fields; }; +struct dsync_mailbox_attribute { + enum mail_attribute_type type; + const char *key; + const char *value; /* NULL = not looked up yet / deleted */ + + time_t last_change; /* 0 = unknown */ + uint64_t modseq; /* 0 = unknown */ + + bool deleted; /* attribute is known to have been deleted */ + bool exported; /* internally used by exporting */ +}; + +void dsync_mailbox_attribute_dup(pool_t pool, + const struct dsync_mailbox_attribute *src, + struct dsync_mailbox_attribute *dest_r); + #endif diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-transaction-log-scan.c --- a/src/doveadm/dsync/dsync-transaction-log-scan.c Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-transaction-log-scan.c Thu Mar 14 15:41:09 2013 +0200 @@ -5,11 +5,13 @@ #include "mail-index-modseq.h" #include "mail-storage-private.h" #include "dsync-mail.h" +#include "dsync-mailbox.h" #include "dsync-transaction-log-scan.h" struct dsync_transaction_log_scan { pool_t pool; HASH_TABLE_TYPE(dsync_uid_mail_change) changes; + HASH_TABLE_TYPE(dsync_attr_change) attr_changes; struct mail_index_view *view; uint32_t highest_wanted_uid; @@ -303,6 +305,44 @@ } } +static void +log_add_attribute_update_key(struct dsync_transaction_log_scan *ctx, + const char *attr_change, uint64_t modseq) +{ + struct dsync_mailbox_attribute lookup_attr, *attr; + + i_assert(strlen(attr_change) > 2); /* checked by lib-index */ + + lookup_attr.type = attr_change[1] == 'p' ? + MAIL_ATTRIBUTE_TYPE_PRIVATE : MAIL_ATTRIBUTE_TYPE_SHARED; + lookup_attr.key = attr_change+2; + + attr = hash_table_lookup(ctx->attr_changes, &lookup_attr); + if (attr == NULL) { + attr = p_new(ctx->pool, struct dsync_mailbox_attribute, 1); + attr->type = lookup_attr.type; + attr->key = p_strdup(ctx->pool, lookup_attr.key); + hash_table_insert(ctx->attr_changes, attr, attr); + } + attr->deleted = attr_change[0] == '-'; + attr->modseq = modseq; +} + +static void +log_add_attribute_update(struct dsync_transaction_log_scan *ctx, + const void *data, + const struct mail_transaction_header *hdr, + uint64_t modseq) +{ + const char *attr_changes = data; + unsigned int i; + + for (i = 0; i < hdr->size && attr_changes[i] != '\0'; ) { + log_add_attribute_update_key(ctx, attr_changes+i, modseq); + i += strlen(attr_changes+i) + 1; + } +} + static int dsync_log_set(struct dsync_transaction_log_scan *ctx, struct mail_index_view *view, bool pvt_scan, @@ -342,6 +382,7 @@ const void *data; uint32_t file_seq, max_seq; uoff_t file_offset, max_offset; + uint64_t cur_modseq; log_view = mail_transaction_log_view_open(view->index->log); if (dsync_log_set(ctx, view, pvt_scan, log_view, modseq) < 0) { @@ -393,6 +434,10 @@ case MAIL_TRANSACTION_MODSEQ_UPDATE: log_add_modseq_update(ctx, data, hdr, pvt_scan); break; + case MAIL_TRANSACTION_ATTRIBUTE_UPDATE: + cur_modseq = mail_transaction_log_view_get_prev_modseq(log_view); + log_add_attribute_update(ctx, data, hdr, cur_modseq); + break; } } @@ -404,6 +449,23 @@ return 0; } +static int +dsync_mailbox_attribute_cmp(const struct dsync_mailbox_attribute *attr1, + const struct dsync_mailbox_attribute *attr2) +{ + if (attr1->type < attr2->type) + return -1; + if (attr1->type > attr2->type) + return 1; + return strcmp(attr1->key, attr2->key); +} + +static unsigned int +dsync_mailbox_attribute_hash(const struct dsync_mailbox_attribute *attr) +{ + return str_hash(attr->key) ^ attr->type; +} + int dsync_transaction_log_scan_init(struct mail_index_view *view, struct mail_index_view *pvt_view, uint32_t highest_wanted_uid, @@ -418,6 +480,9 @@ ctx = p_new(pool, struct dsync_transaction_log_scan, 1); ctx->pool = pool; hash_table_create_direct(&ctx->changes, pool, 0); + hash_table_create(&ctx->attr_changes, pool, 0, + dsync_mailbox_attribute_hash, + dsync_mailbox_attribute_cmp); ctx->view = view; ctx->highest_wanted_uid = highest_wanted_uid; @@ -438,6 +503,12 @@ return scan->changes; } +HASH_TABLE_TYPE(dsync_attr_change) +dsync_transaction_log_scan_get_attr_hash(struct dsync_transaction_log_scan *scan) +{ + return scan->attr_changes; +} + bool dsync_transaction_log_scan_has_all_changes(struct dsync_transaction_log_scan *scan) { @@ -491,5 +562,6 @@ *_scan = NULL; hash_table_destroy(&scan->changes); + hash_table_destroy(&scan->attr_changes); pool_unref(&scan->pool); } diff -r f3cd9e5cbe99 -r c6082de4bf5b src/doveadm/dsync/dsync-transaction-log-scan.h --- a/src/doveadm/dsync/dsync-transaction-log-scan.h Thu Mar 14 15:32:14 2013 +0200 +++ b/src/doveadm/dsync/dsync-transaction-log-scan.h Thu Mar 14 15:41:09 2013 +0200 @@ -3,6 +3,9 @@ HASH_TABLE_DEFINE_TYPE(dsync_uid_mail_change, void *, struct dsync_mail_change *); +HASH_TABLE_DEFINE_TYPE(dsync_attr_change, + struct dsync_mailbox_attribute *, + struct dsync_mailbox_attribute *); struct mail_index_view; struct dsync_transaction_log_scan; @@ -14,6 +17,8 @@ struct dsync_transaction_log_scan **scan_r); HASH_TABLE_TYPE(dsync_uid_mail_change) dsync_transaction_log_scan_get_hash(struct dsync_transaction_log_scan *scan); +HASH_TABLE_TYPE(dsync_attr_change) +dsync_transaction_log_scan_get_attr_hash(struct dsync_transaction_log_scan *scan); /* Returns TRUE if the entire transaction log was scanned */ bool dsync_transaction_log_scan_has_all_changes(struct dsync_transaction_log_scan *scan); /* If the given UID has been expunged after the initial log scan, create/update