/* delete from dest too */
if (!dest_deleted) {
dsync_worker_delete_mailbox(brain->dest_worker,
- &dest_boxes[dest]->mailbox_guid);
+ src_boxes[src]);
}
src++; dest++;
} else if (dest_deleted) {
/* delete from src too */
dsync_worker_delete_mailbox(brain->src_worker,
- &src_boxes[src]->mailbox_guid);
+ dest_boxes[dest]);
src++; dest++;
} else {
src++; dest++;
static bool
dsync_brain_is_unsubscribed(struct dsync_brain_subs_list *list,
- const struct dsync_worker_subscription *subs)
+ const struct dsync_worker_subscription *subs,
+ time_t *last_change_r)
{
const struct dsync_worker_unsubscription *unsubs;
struct dsync_worker_unsubscription lookup;
dsync_str_sha_to_guid(subs->storage_name, &lookup.name_sha1);
unsubs = array_bsearch(&list->unsubscriptions, &lookup,
dsync_worker_unsubscription_cmp);
- if (unsubs == NULL)
+ if (unsubs == NULL) {
+ *last_change_r = 0;
return FALSE;
- else
- return unsubs->last_change > subs->last_change;
+ } else if (unsubs->last_change <= subs->last_change) {
+ *last_change_r = subs->last_change;
+ return FALSE;
+ } else {
+ *last_change_r = unsubs->last_change;
+ return TRUE;
+ }
}
static void dsync_brain_sync_subscriptions(struct dsync_brain *brain)
{
const struct dsync_worker_subscription *src_subs, *dest_subs;
unsigned int src, dest, src_count, dest_count;
+ time_t last_change;
int ret;
/* subscriptions are sorted by name. */
if (ret < 0) {
/* subscribed only in source */
if (dsync_brain_is_unsubscribed(brain->dest_subs_list,
- &src_subs[src])) {
+ &src_subs[src],
+ &last_change)) {
dsync_worker_set_subscribed(brain->src_worker,
src_subs[src].vname,
- FALSE);
+ last_change, FALSE);
} else {
dsync_worker_set_subscribed(brain->dest_worker,
src_subs[src].vname,
- TRUE);
+ last_change, TRUE);
}
src++;
} else {
/* subscribed only in dest */
if (dsync_brain_is_unsubscribed(brain->src_subs_list,
- &dest_subs[dest])) {
+ &dest_subs[dest],
+ &last_change)) {
dsync_worker_set_subscribed(brain->dest_worker,
dest_subs[dest].vname,
- FALSE);
+ last_change, FALSE);
} else {
dsync_worker_set_subscribed(brain->src_worker,
dest_subs[dest].vname,
- TRUE);
+ last_change, TRUE);
}
dest++;
}
dsync_brain_sync_rename_mailbox(struct dsync_brain *brain,
const struct dsync_brain_mailbox *mailbox)
{
- if (mailbox->src->last_renamed > mailbox->dest->last_renamed) {
+ if (mailbox->src->last_changed > mailbox->dest->last_changed) {
dsync_worker_rename_mailbox(brain->dest_worker,
&mailbox->box.mailbox_guid,
mailbox->src);
uint32_t uid_validity, uid_next;
uint64_t highest_modseq;
- time_t last_renamed;
+ time_t last_changed;
enum dsync_mailbox_flags flags;
ARRAY_TYPE(const_string) cache_fields;
};
static void
proxy_client_worker_set_subscribed(struct dsync_worker *_worker,
- const char *name, bool set)
+ const char *name, time_t last_change,
+ bool set)
{
struct proxy_client_dsync_worker *worker =
(struct proxy_client_dsync_worker *)_worker;
str_append(str, "SUBS-SET\t");
str_tabescape_write(str, name);
- str_printfa(str, "\t%d\n", set ? 1 : 0);
+ str_printfa(str, "\t%s\t%d\n", dec2str(last_change),
+ set ? 1 : 0);
o_stream_send(worker->output, str_data(str), str_len(str));
} T_END;
}
static void
proxy_client_worker_delete_mailbox(struct dsync_worker *_worker,
- const mailbox_guid_t *mailbox)
+ const struct dsync_mailbox *dsync_box)
{
struct proxy_client_dsync_worker *worker =
(struct proxy_client_dsync_worker *)_worker;
string_t *str = t_str_new(128);
str_append(str, "BOX-DELETE\t");
- dsync_proxy_mailbox_guid_export(str, mailbox);
- str_append_c(str, '\n');
+ dsync_proxy_mailbox_guid_export(str, &dsync_box->mailbox_guid);
+ str_printfa(str, "\t%s\n", dec2str(dsync_box->last_changed));
o_stream_send(worker->output, str_data(str), str_len(str));
} T_END;
}
static int
cmd_subs_set(struct dsync_proxy_server *server, const char *const *args)
{
- if (args[0] == NULL || args[1] == NULL) {
+ if (str_array_length(args) < 3) {
i_error("subs-set: Missing parameters");
return -1;
}
dsync_worker_set_subscribed(server->worker, args[0],
- strcmp(args[1], "1") == 0);
+ strtoul(args[1], NULL, 10),
+ strcmp(args[2], "1") == 0);
return 1;
}
cmd_box_delete(struct dsync_proxy_server *server, const char *const *args)
{
mailbox_guid_t guid;
+ struct dsync_mailbox dsync_box;
- if (args[0] == NULL ||
- dsync_proxy_mailbox_guid_import(args[0], &guid) < 0) {
+ if (str_array_length(args) < 2)
+ return -1;
+ if (dsync_proxy_mailbox_guid_import(args[0], &guid) < 0) {
i_error("box-delete: Invalid mailbox GUID '%s'", args[0]);
return -1;
}
- dsync_worker_delete_mailbox(server->worker, &guid);
+ memset(&dsync_box, 0, sizeof(dsync_box));
+ dsync_box.mailbox_guid = guid;
+ dsync_box.last_changed = strtoul(args[1], NULL, 10);
+ dsync_worker_delete_mailbox(server->worker, &dsync_box);
return 1;
}
str_tabescape_write(str, s);
str_append_c(str, '\t');
dsync_proxy_mailbox_guid_export(str, &box->dir_guid);
- str_printfa(str, "\t%lu\t%u", (unsigned long)box->last_renamed,
+ str_printfa(str, "\t%lu\t%u", (unsigned long)box->last_changed,
box->flags);
if (mail_guid_128_is_empty(box->mailbox_guid.guid)) {
*error_r = "Invalid dir GUID";
return -1;
}
- box_r->last_renamed = strtoul(args[i++], &p, 10);
+ box_r->last_changed = strtoul(args[i++], &p, 10);
if (*p != '\0') {
*error_r = "Invalid mailbox last_renamed";
return -1;
struct local_dsync_mailbox_change {
mailbox_guid_t guid;
time_t last_renamed;
+ time_t last_deleted;
unsigned int deleted_mailbox:1;
unsigned int deleted_dir:1;
};
dsync_worker_init_local(struct mail_user *user, char alt_char)
{
struct local_dsync_worker *worker;
- struct mail_namespace *ns;
pool_t pool;
- /* whatever we do, we do it because we're trying to sync,
- not because of a user action. don't log these mailbox list changes
- so we don't do wrong decisions on future syncs. */
- for (ns = user->namespaces; ns != NULL; ns = ns->next)
- mailbox_list_set_changelog_writable(ns->list, FALSE);
-
pool = pool_alloconly_create("local dsync worker", 10240);
worker = p_new(pool, struct local_dsync_worker, 1);
worker->worker.v = local_dsync_worker;
const struct mailbox_log_record *rec)
{
struct local_dsync_mailbox_change *change;
+ time_t stamp;
change = hash_table_lookup(worker->mailbox_changes_hash,
rec->mailbox_guid);
hash_table_insert(worker->mailbox_changes_hash,
change->guid.guid, change);
}
+
+ stamp = mailbox_log_record_get_timestamp(rec);
switch (rec->type) {
case MAILBOX_LOG_RECORD_DELETE_MAILBOX:
change->deleted_mailbox = TRUE;
+ if (change->last_deleted < stamp)
+ change->last_deleted = stamp;
break;
case MAILBOX_LOG_RECORD_DELETE_DIR:
change->deleted_dir = TRUE;
break;
case MAILBOX_LOG_RECORD_RENAME:
- change->last_renamed =
- mailbox_log_record_get_timestamp(rec);
+ if (change->last_renamed < stamp)
+ change->last_renamed = stamp;
break;
case MAILBOX_LOG_RECORD_SUBSCRIBE:
case MAILBOX_LOG_RECORD_UNSUBSCRIBE:
const struct mailbox_log_record *rec)
{
struct local_dsync_subscription_change *change, new_change;
+ time_t stamp;
memset(&new_change, 0, sizeof(new_change));
new_change.list = list;
memcpy(new_change.name_sha1.guid, rec->mailbox_guid,
sizeof(new_change.name_sha1.guid));
+ stamp = mailbox_log_record_get_timestamp(rec);
change = hash_table_lookup(worker->subscription_changes_hash,
&new_change);
if (change == NULL) {
*change = new_change;
hash_table_insert(worker->subscription_changes_hash,
change, change);
+ } else if (change->last_change > stamp) {
+ /* we've already seen a newer subscriptions state. this is
+ probably a stale record created by dsync */
+ return;
}
+ change->last_change = stamp;
+
switch (rec->type) {
case MAILBOX_LOG_RECORD_DELETE_MAILBOX:
case MAILBOX_LOG_RECORD_DELETE_DIR:
change->unsubscribed = TRUE;
break;
}
- change->last_change = mailbox_log_record_get_timestamp(rec);
}
static int
/* the name doesn't matter */
dsync_box_r->name = "";
dsync_box_r->mailbox_guid = change->guid;
+ dsync_box_r->last_changed = change->last_deleted;
dsync_box_r->flags |=
DSYNC_MAILBOX_FLAG_DELETED_MAILBOX;
return 1;
/* the name doesn't matter */
dsync_box_r->name = "";
dsync_box_r->dir_guid = change->guid;
+ dsync_box_r->last_changed = change->last_deleted;
dsync_box_r->flags |= DSYNC_MAILBOX_FLAG_DELETED_DIR;
return 1;
}
return -1;
}
- /* get last rename timestamp */
+ /* get last change timestamp */
change = hash_table_lookup(worker->mailbox_changes_hash,
dsync_box_r->dir_guid.guid);
if (change != NULL) {
/* it shouldn't be marked as deleted, but drop it to be sure */
change->deleted_dir = FALSE;
- dsync_box_r->last_renamed = change->last_renamed;
+ dsync_box_r->last_changed = change->last_renamed;
}
storage_name = mail_namespace_get_storage_name(info->ns, info->name);
static void
local_worker_set_subscribed(struct dsync_worker *_worker,
- const char *name, bool set)
+ const char *name, time_t last_change, bool set)
{
struct local_dsync_worker *worker =
(struct local_dsync_worker *)_worker;
return;
}
+ mailbox_list_set_changelog_timestamp(ns->list, last_change);
if (mailbox_list_set_subscribed(ns->list, storage_name, set) < 0) {
dsync_worker_set_failure(_worker);
i_error("Can't update subscription %s: %s", name,
mailbox_list_get_last_error(ns->list, NULL));
}
+ mailbox_list_set_changelog_timestamp(ns->list, (time_t)-1);
}
static int local_mailbox_open(struct local_dsync_worker *worker,
static void
local_worker_delete_mailbox(struct dsync_worker *_worker,
- const mailbox_guid_t *mailbox)
+ const struct dsync_mailbox *dsync_box)
{
struct local_dsync_worker *worker =
(struct local_dsync_worker *)_worker;
struct local_dsync_mailbox *lbox;
+ const mailbox_guid_t *mailbox = &dsync_box->mailbox_guid;
lbox = hash_table_lookup(worker->mailbox_hash, mailbox);
if (lbox == NULL) {
return;
}
+ mailbox_list_set_changelog_timestamp(lbox->ns->list,
+ dsync_box->last_changed);
if (mailbox_list_delete_mailbox(lbox->ns->list,
lbox->storage_name) < 0) {
i_error("Can't delete mailbox %s: %s", lbox->storage_name,
mailbox_list_get_last_error(lbox->ns->list, NULL));
dsync_worker_set_failure(_worker);
}
+ mailbox_list_set_changelog_timestamp(lbox->ns->list, (time_t)-1);
}
static void
return;
}
+ mailbox_list_set_changelog_timestamp(list, dsync_box->last_changed);
if (mailbox_list_rename_mailbox(list, lbox->storage_name,
list, newname, TRUE) < 0) {
i_error("Can't rename mailbox %s to %s: %s", lbox->storage_name,
local_worker_rename_children(worker, oldname, newname,
lbox->ns->sep);
}
+ mailbox_list_set_changelog_timestamp(list, (time_t)-1);
}
static bool
struct dsync_worker_unsubscription *rec_r);
int (*subs_iter_deinit)(struct dsync_worker_subs_iter *iter);
void (*set_subscribed)(struct dsync_worker *worker,
- const char *name, bool set);
+ const char *name, time_t last_change, bool set);
struct dsync_worker_msg_iter *
(*msg_iter_init)(struct dsync_worker *worker,
void (*create_mailbox)(struct dsync_worker *worker,
const struct dsync_mailbox *dsync_box);
void (*delete_mailbox)(struct dsync_worker *worker,
- const mailbox_guid_t *mailbox);
+ const struct dsync_mailbox *dsync_box);
void (*rename_mailbox)(struct dsync_worker *worker,
const mailbox_guid_t *mailbox,
const struct dsync_mailbox *dsync_box);
}
void dsync_worker_set_subscribed(struct dsync_worker *worker,
- const char *name, bool set)
+ const char *name, time_t last_change, bool set)
{
- worker->v.set_subscribed(worker, name, set);
+ worker->v.set_subscribed(worker, name, last_change, set);
}
struct dsync_worker_msg_iter *
}
void dsync_worker_delete_mailbox(struct dsync_worker *worker,
- const mailbox_guid_t *mailbox)
+ const struct dsync_mailbox *dsync_box)
{
if (!worker->readonly)
- worker->v.delete_mailbox(worker, mailbox);
+ worker->v.delete_mailbox(worker, dsync_box);
}
void dsync_worker_rename_mailbox(struct dsync_worker *worker,
int dsync_worker_subs_iter_deinit(struct dsync_worker_subs_iter **iter);
/* Subscribe/unsubscribe mailbox */
void dsync_worker_set_subscribed(struct dsync_worker *worker,
- const char *name, bool set);
+ const char *name, time_t last_change,
+ bool set);
/* Iterate through all messages in given mailboxes. The mailboxes are iterated
in the given order. */
const struct dsync_mailbox *dsync_box);
/* Delete mailbox/dir with given GUID. */
void dsync_worker_delete_mailbox(struct dsync_worker *worker,
- const mailbox_guid_t *mailbox);
+ const struct dsync_mailbox *dsync_box);
/* Change a mailbox and its childrens' name. The name is taken from the given
dsync_box (applying name_sep if necessary). */
void dsync_worker_rename_mailbox(struct dsync_worker *worker,
const struct dsync_mailbox *obox)
{
return memcmp(dbox->mailbox_guid.guid, obox->mailbox_guid.guid,
- sizeof(dbox->mailbox_guid.guid)) == 0;
+ sizeof(dbox->mailbox_guid.guid)) == 0 &&
+ dbox->last_changed == obox->last_changed;
}
static void
memset(&box, 0, sizeof(box));
box.name = "\t\001\r\nname\t\001\n\r";
box.name_sep = '/';
- box.last_renamed = 992;
+ box.last_changed = 992;
box.flags = 123;
memcpy(box.dir_guid.guid, test_mailbox_guid1, MAIL_GUID_128_SIZE);
test_worker->box_iter.next_box = &box;
test_assert(strcmp(event.box.name, "noselect") == 0);
test_assert(event.box.name_sep == '/');
test_assert(memcmp(event.box.dir_guid.guid, test_mailbox_guid2, MAIL_GUID_128_SIZE) == 0);
- test_assert(event.box.last_renamed == 553);
+ test_assert(event.box.last_changed == 553);
test_assert(event.box.flags == 99);
test_assert(event.box.uid_validity == 0);
test_assert(event.box.uid_validity == 1234567890);
test_assert(event.box.uid_next == 9876);
test_assert(event.box.highest_modseq == 28427847284728);
- test_assert(event.box.last_renamed == 61);
+ test_assert(event.box.last_changed == 61);
test_end();
}
test_begin("proxy server box delete");
- test_assert(run_cmd("BOX-DELETE", TEST_MAILBOX_GUID1, NULL) == 1);
+ test_assert(run_cmd("BOX-DELETE", TEST_MAILBOX_GUID1, "4351", NULL) == 1);
test_assert(test_dsync_worker_next_box_event(test_worker, &event));
test_assert(event.type == LAST_BOX_TYPE_DELETE);
test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid1, MAIL_GUID_128_SIZE) == 0);
+ test_assert(event.box.last_changed == 4351);
- test_assert(run_cmd("BOX-DELETE", TEST_MAILBOX_GUID2, NULL) == 1);
+ test_assert(run_cmd("BOX-DELETE", TEST_MAILBOX_GUID2, "653", NULL) == 1);
test_assert(test_dsync_worker_next_box_event(test_worker, &event));
test_assert(event.type == LAST_BOX_TYPE_DELETE);
test_assert(memcmp(event.box.mailbox_guid.guid, test_mailbox_guid2, MAIL_GUID_128_SIZE) == 0);
+ test_assert(event.box.last_changed == 653);
test_end();
}
test_assert(event.box.uid_validity == 34343);
test_assert(event.box.uid_next == 22);
test_assert(event.box.highest_modseq == 2238427847284728);
- test_assert(event.box.last_renamed == 53);
+ test_assert(event.box.last_changed == 53);
test_end();
}
static void
test_worker_set_subscribed(struct dsync_worker *_worker,
- const char *name, bool set)
+ const char *name, time_t last_change, bool set)
{
struct dsync_mailbox dsync_box;
memset(&dsync_box, 0, sizeof(dsync_box));
dsync_box.name = name;
+ dsync_box.last_changed = last_change;
test_worker_set_last_box(_worker, &dsync_box,
set ? LAST_BOX_TYPE_SUBSCRIBE :
LAST_BOX_TYPE_UNSUBSCRIBE);
static void
test_worker_delete_mailbox(struct dsync_worker *_worker,
- const mailbox_guid_t *mailbox)
+ const struct dsync_mailbox *dsync_box)
{
struct test_dsync_worker *worker = (struct test_dsync_worker *)_worker;
struct test_dsync_box_event event;
memset(&event, 0, sizeof(event));
event.type = LAST_BOX_TYPE_DELETE;
- event.box.mailbox_guid = *mailbox;
- event.box.name = "";
+ event.box = *dsync_box;
array_append(&worker->box_events, &event, 1);
}
const char *file_create_gid_origin;
struct mailbox_log *changelog;
+ time_t changelog_timestamp;
char *error_string;
enum mail_error error;
bool temporary_error;
ARRAY_DEFINE(module_contexts, union mailbox_list_module_context *);
-
- unsigned int changelog_disabled:1;
};
struct mailbox_list_iterate_context {
list->file_create_mode = (mode_t)-1;
list->dir_create_mode = (mode_t)-1;
list->file_create_gid = (gid_t)-1;
+ list->changelog_timestamp = (time_t)-1;
/* copy settings */
list->set.root_dir = p_strdup(list->pool, set->root_dir);
const uint8_t mailbox_guid[MAIL_GUID_128_SIZE])
{
struct mailbox_log_record rec;
+ time_t stamp;
- if (!mailbox_list_init_changelog(list) || list->changelog_disabled ||
+ if (!mailbox_list_init_changelog(list) ||
mail_guid_128_is_empty(mailbox_guid))
return;
+ stamp = list->changelog_timestamp != (time_t)-1 ?
+ list->changelog_timestamp : ioloop_time;
+
memset(&rec, 0, sizeof(rec));
rec.type = type;
memcpy(rec.mailbox_guid, mailbox_guid, sizeof(rec.mailbox_guid));
- mailbox_log_record_set_timestamp(&rec, ioloop_time);
+ mailbox_log_record_set_timestamp(&rec, stamp);
(void)mailbox_log_append(list->changelog, &rec);
}
return !mailbox_list_init_changelog(list) ? NULL : list->changelog;
}
-void mailbox_list_set_changelog_writable(struct mailbox_list *list, bool set)
+void mailbox_list_set_changelog_timestamp(struct mailbox_list *list,
+ time_t stamp)
{
- list->changelog_disabled = !set;
+ list->changelog_timestamp = stamp;
}
static int mailbox_list_try_delete(struct mailbox_list *list, const char *dir)
uint8_t mailbox_guid[MAIL_GUID_128_SIZE]);
/* Returns mailbox's change log, or NULL if it doesn't have one. */
struct mailbox_log *mailbox_list_get_changelog(struct mailbox_list *list);
-/* Enable/disable writing mailbox changes to changelog. */
-void mailbox_list_set_changelog_writable(struct mailbox_list *list, bool set);
+/* Specify timestamp to use when writing mailbox changes to changelog.
+ The same timestamp is used until stamp is set to (time_t)-1, after which
+ current time is used */
+void mailbox_list_set_changelog_timestamp(struct mailbox_list *list,
+ time_t stamp);
/* Returns a prefix that temporary files should use without conflicting
with the namespace. */