changeset 1637:a64391b49589 HEAD

API change for copying messages.
author Timo Sirainen <tss@iki.fi>
date Wed, 23 Jul 2003 03:40:49 +0300
parents 20212920c795
children e95c0e462591
files src/imap/cmd-copy.c src/lib-storage/index/index-copy.c src/lib-storage/index/index-fetch.c src/lib-storage/index/index-mail.c src/lib-storage/index/index-storage.h src/lib-storage/index/maildir/maildir-copy.c src/lib-storage/index/maildir/maildir-storage.c src/lib-storage/index/maildir/maildir-storage.h src/lib-storage/index/mbox/mbox-storage.c src/lib-storage/mail-storage.h
diffstat 10 files changed, 231 insertions(+), 206 deletions(-) [+]
line wrap: on
line diff
--- a/src/imap/cmd-copy.c	Wed Jul 23 03:29:32 2003 +0300
+++ b/src/imap/cmd-copy.c	Wed Jul 23 03:40:49 2003 +0300
@@ -3,11 +3,39 @@
 #include "common.h"
 #include "commands.h"
 
+static int fetch_and_copy(struct mail_copy_context *copy_ctx,
+			  struct mailbox *box, const char *messageset,
+			  int uidset, int *all_found)
+{
+	struct mail_fetch_context *fetch_ctx;
+	struct mail *mail;
+	int failed = FALSE;
+
+	fetch_ctx = box->fetch_init(box, MAIL_FETCH_STREAM_HEADER |
+				    MAIL_FETCH_STREAM_BODY, NULL,
+				    messageset, uidset);
+	if (fetch_ctx == NULL)
+		return FALSE;
+
+	while ((mail = box->fetch_next(fetch_ctx)) != NULL) {
+		if (!mail->copy(mail, copy_ctx)) {
+			failed = TRUE;
+			break;
+		}
+	}
+
+	if (!box->fetch_deinit(fetch_ctx, all_found))
+		return FALSE;
+
+	return !failed;
+}
+
 int cmd_copy(struct client *client)
 {
 	struct mailbox *destbox;
+        struct mail_copy_context *copy_ctx;
 	const char *messageset, *mailbox;
-	int ret;
+	int failed = FALSE, all_found = TRUE;
 
 	/* <message set> <mailbox> */
 	if (!client_read_string_args(client, 2, &messageset, &mailbox))
@@ -28,17 +56,31 @@
 		return TRUE;
 	}
 
-	/* copy the mail */
-	ret = client->mailbox->copy(client->mailbox, destbox,
-				    messageset, client->cmd_uid);
+	/* FIXME: copying from mailbox to itself is kind of kludgy here.
+	   currently it works simply because copy_init() will lock mbox
+	   exclusively and fetching wont drop it. */
+	copy_ctx = destbox->copy_init(destbox);
+	if (copy_ctx == NULL)
+		failed = TRUE;
+	else {
+		if (!fetch_and_copy(copy_ctx, client->mailbox,
+				    messageset, client->cmd_uid, &all_found))
+			failed = TRUE;
 
-	if (ret) {
+		if (!destbox->copy_deinit(copy_ctx, failed || !all_found))
+			failed = TRUE;
+	}
+
+	if (failed)
+		client_send_storage_error(client);
+	else if (!all_found) {
+		/* some messages were expunged, sync them */
+		client_sync_full(client);
+		client_send_tagline(client,
+			"NO Some of the requested messages no longer exist.");
+	} else {
 		client_sync_full_fast(client);
 		client_send_tagline(client, "OK Copy completed.");
-	} else {
-		/* if COPY fails because of expunges they'll get synced here */
-		client_sync_full(client);
-		client_send_storage_error(client);
 	}
 
 	destbox->close(destbox);
--- a/src/lib-storage/index/index-copy.c	Wed Jul 23 03:29:32 2003 +0300
+++ b/src/lib-storage/index/index-copy.c	Wed Jul 23 03:40:49 2003 +0300
@@ -8,90 +8,52 @@
 
 #include <unistd.h>
 
-static int copy_messageset(struct messageset_context *msgset_ctx,
-                           struct mail_save_context *save_ctx,
-			   struct index_mailbox *src,
-			   struct mailbox *dest)
+struct mail_copy_context {
+	struct mailbox *box;
+	struct mail_save_context *save_ctx;
+};
+
+struct mail_copy_context *index_storage_copy_init(struct mailbox *box)
 {
-        const struct messageset_mail *mail;
-	struct mail_full_flags flags;
-	struct istream *input;
-	time_t received_date;
-	int failed, deleted;
-
-	memset(&flags, 0, sizeof(flags));
-	flags.custom_flags =
-		mail_custom_flags_list_get(src->index->custom_flags);
-	flags.custom_flags_count = MAIL_CUSTOM_FLAGS_COUNT;
+	struct mail_copy_context *ctx;
+	struct mail_save_context *save_ctx;
 
-	while ((mail = index_messageset_next(msgset_ctx)) != NULL) {
-		input = src->index->open_mail(src->index, mail->rec,
-					      &received_date, &deleted);
-		if (input == NULL)
-			return FALSE;
+	save_ctx = box->save_init(box, TRUE);
+	if (save_ctx == NULL)
+		return NULL;
 
-		flags.flags = mail->rec->msg_flags;
-		failed = !dest->save_next(save_ctx, &flags, received_date,
-					  0, input);
-		i_stream_unref(input);
+	ctx = i_new(struct mail_copy_context, 1);
+	ctx->box = box;
+	ctx->save_ctx = save_ctx;
 
-		if (failed)
-			return FALSE;
-	}
-
-	return TRUE;
+	return ctx;
 }
 
-int index_storage_copy(struct mailbox *box, struct mailbox *destbox,
-		       const char *messageset, int uidset)
+int index_storage_copy_deinit(struct mail_copy_context *ctx, int rollback)
 {
-	struct index_mailbox *ibox = (struct index_mailbox *) box;
-	struct messageset_context *msgset_ctx;
-        struct mail_save_context *save_ctx;
-	int ret, ret2, copy_inside_mailbox;
+	int ret;
 
-	if (destbox->readonly) {
-		mail_storage_set_error(box->storage,
-				       "Destination mailbox is read-only");
-		return FALSE;
-	}
-
-	copy_inside_mailbox =
-		destbox->storage == box->storage &&
-		strcmp(destbox->name, box->name) == 0;
+	ret = ctx->box->save_deinit(ctx->save_ctx, rollback);
+	i_free(ctx);
+	return ret;
+}
 
-	if (copy_inside_mailbox) {
-		if (!index_storage_lock(ibox, MAIL_LOCK_EXCLUSIVE))
-			return FALSE;
-	} else {
-		if (!index_storage_sync_and_lock(ibox, TRUE, TRUE,
-						 MAIL_LOCK_SHARED))
-			return FALSE;
-	}
+int index_storage_copy(struct mail *mail, struct mail_copy_context *ctx)
+{
+	struct index_mail *imail = (struct index_mail *) mail;
+	struct istream *input;
+	time_t received_date;
+	int ret, deleted;
 
-	save_ctx = destbox->save_init(destbox, TRUE);
-	if (save_ctx == NULL)
-		ret = FALSE;
-	else {
-		/* abort if any of the messages are expunged */
-		msgset_ctx = index_messageset_init(ibox, messageset, uidset,
-						   FALSE);
-		ret = copy_messageset(msgset_ctx, save_ctx, ibox, destbox);
-		ret2 = index_messageset_deinit(msgset_ctx);
-		if (ret2 < 0)
-			ret = FALSE;
-		else if (ret2 == 0) {
-			mail_storage_set_error(ibox->box.storage,
-			     "Some of the requested messages no longer exist.");
-			ret = FALSE;
-		}
+	input = imail->ibox->index->open_mail(imail->ibox->index,
+					      imail->data.rec,
+					      &received_date, &deleted);
+	if (input == NULL)
+		return FALSE;
 
-		if (!destbox->save_deinit(save_ctx, !ret))
-			ret = FALSE;
-	}
-
-	if (!index_storage_lock(ibox, MAIL_LOCK_UNLOCK))
-		ret = FALSE;
+	ret = ctx->box->save_next(ctx->save_ctx, mail->get_flags(mail),
+				  received_date, 0, input);
+	i_stream_unref(input);
 
 	return ret;
 }
--- a/src/lib-storage/index/index-fetch.c	Wed Jul 23 03:29:32 2003 +0300
+++ b/src/lib-storage/index/index-fetch.c	Wed Jul 23 03:40:49 2003 +0300
@@ -18,6 +18,7 @@
 	struct index_mail mail;
 
 	int update_seen;
+	enum mail_lock_type old_lock;
 };
 
 struct mail_fetch_context *
@@ -30,6 +31,7 @@
 	int check_mail;
 
 	ctx = i_new(struct mail_fetch_context, 1);
+	ctx->old_lock = ibox->index->lock_type;
 
 	if (box->readonly && update_seen != NULL)
 		*update_seen = FALSE;
@@ -48,7 +50,8 @@
 
 	if (update_seen != NULL && *update_seen &&
 	    ibox->index->header->messages_count ==
-	    ibox->index->header->seen_messages_count) {
+	    ibox->index->header->seen_messages_count &&
+	    ctx->old_lock != MAIL_LOCK_EXCLUSIVE) {
 		/* if all messages are already seen, there's no point in
 		   keeping exclusive lock */
 		*update_seen = FALSE;
@@ -73,7 +76,7 @@
 	if (all_found != NULL)
 		*all_found = ret > 0;
 
-	if (!index_storage_lock(ctx->ibox, MAIL_LOCK_UNLOCK))
+	if (!index_storage_lock(ctx->ibox, ctx->old_lock))
 		ret = -1;
 
 	if (ctx->ibox->fetch_mail.pool != NULL)
--- a/src/lib-storage/index/index-mail.c	Wed Jul 23 03:29:32 2003 +0300
+++ b/src/lib-storage/index/index-mail.c	Wed Jul 23 03:40:49 2003 +0300
@@ -637,7 +637,7 @@
 }
 
 static struct mail index_mail = {
-	0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0,
 
 	get_flags,
 	get_parts,
@@ -648,7 +648,8 @@
 	get_address,
 	get_first_mailbox,
 	get_stream,
-	get_special
+	get_special,
+	index_storage_copy
 };
 
 void index_mail_init(struct index_mailbox *ibox, struct index_mail *mail,
@@ -656,11 +657,15 @@
 		     const char *const wanted_headers[])
 {
 	mail->mail = index_mail;
+	mail->mail.box = &ibox->box;
+
 	mail->pool = pool_alloconly_create("index_mail", 4096);
-
 	mail->ibox = ibox;
 	mail->wanted_fields = wanted_fields;
 	mail->wanted_headers = wanted_headers;
+
+	if (ibox->mail_init != NULL)
+		ibox->mail_init(mail);
 }
 
 int index_mail_next(struct index_mail *mail, struct mail_index_record *rec)
--- a/src/lib-storage/index/index-storage.h	Wed Jul 23 03:29:32 2003 +0300
+++ b/src/lib-storage/index/index-storage.h	Wed Jul 23 03:40:49 2003 +0300
@@ -18,6 +18,7 @@
 	/* expunge messages marked as deleted, requires index to be
 	   exclusively locked */
 	int (*expunge_locked)(struct index_mailbox *ibox, int notify);
+	void (*mail_init)(struct index_mail *mail);
 
 	struct mail_index *index;
 
@@ -82,8 +83,6 @@
 void index_storage_set_callbacks(struct mail_storage *storage,
 				 struct mail_storage_callbacks *callbacks,
 				 void *context);
-int index_storage_copy(struct mailbox *box, struct mailbox *destbox,
-		       const char *messageset, int uidset);
 int index_storage_expunge(struct mailbox *box, int notify);
 int index_storage_get_status(struct mailbox *box,
 			     enum mailbox_status_items items,
@@ -117,4 +116,8 @@
 int index_storage_search_deinit(struct mail_search_context *ctx);
 struct mail *index_storage_search_next(struct mail_search_context *ctx);
 
+struct mail_copy_context *index_storage_copy_init(struct mailbox *box);
+int index_storage_copy_deinit(struct mail_copy_context *ctx, int rollback);
+int index_storage_copy(struct mail *mail, struct mail_copy_context *ctx);
+
 #endif
--- a/src/lib-storage/index/maildir/maildir-copy.c	Wed Jul 23 03:29:32 2003 +0300
+++ b/src/lib-storage/index/maildir/maildir-copy.c	Wed Jul 23 03:40:49 2003 +0300
@@ -11,6 +11,16 @@
 #include <stdlib.h>
 #include <unistd.h>
 
+struct maildir_copy_context {
+	struct index_mailbox *ibox;
+	int hardlink;
+
+	pool_t pool;
+	struct rollback *rollbacks;
+
+	struct mail_copy_context *ctx;
+};
+
 struct rollback {
 	struct rollback *next;
 	const char *fname;
@@ -64,139 +74,122 @@
 	return 0;
 }
 
-static int hardlink_messageset(struct messageset_context *ctx,
-			       struct index_mailbox *src,
-			       struct index_mailbox *dest)
+static int maildir_copy_hardlink(struct mail *mail,
+				 struct maildir_copy_context *ctx)
 {
-	struct mail_index *index = src->index;
-	pool_t pool;
-        struct rollback *rollbacks, *rb;
-        const struct messageset_mail *mail;
-	enum mail_flags flags;
-	const char **custom_flags;
+	struct index_mail *imail = (struct index_mail *) mail;
+        struct rollback *rb;
 	const char *fname, *dest_fname, *dest_path;
-	int ret, i, found;
+	enum mail_flags flags;
+	int i, ret, found;
+
+	flags = mail->get_flags(mail)->flags;
 
-	pool = pool_alloconly_create("hard copy rollbacks", 2048);
-	rollbacks = NULL;
-
-	custom_flags = mail_custom_flags_list_get(index->custom_flags);
+	/* link the file */
+	dest_fname = maildir_generate_tmp_filename(&ioloop_timeval);
+	dest_fname = maildir_filename_set_flags(dest_fname, flags);
+	dest_path = t_strconcat(ctx->ibox->index->mailbox_path, "/new/",
+				dest_fname, NULL);
 
-	ret = 1;
-	while (ret > 0 && (mail = index_messageset_next(ctx)) != NULL) {
-		flags = mail->rec->msg_flags;
-		if (!index_mailbox_fix_custom_flags(dest, &flags,
-						    custom_flags,
-						    MAIL_CUSTOM_FLAGS_COUNT)) {
+	for (i = 0;; i++) {
+		ret = maildir_hardlink_file(imail->ibox->index, imail->data.rec,
+					    &fname, dest_path);
+		if (ret != 0)
+			break;
+
+		if (i == 10) {
+			mail_storage_set_error(mail->box->storage,
+				"File name keeps changing, copy failed");
+			break;
+		}
+
+		if (!maildir_index_sync_readonly(imail->ibox->index, fname,
+						 &found)) {
 			ret = -1;
 			break;
 		}
 
-		/* link the file */
-		t_push();
-
-		dest_fname = maildir_generate_tmp_filename(&ioloop_timeval);
-		dest_fname = maildir_filename_set_flags(dest_fname, flags);
-		dest_path = t_strconcat(dest->index->mailbox_path, "/new/",
-					dest_fname, NULL);
+		if (!found)
+			break;
+	}
 
-		for (i = 0;; i++) {
-			ret = maildir_hardlink_file(index, mail->rec, &fname,
-						    dest_path);
-			if (ret > 0) {
-				rb = p_new(pool, struct rollback, 1);
-				rb->fname = p_strdup(pool, dest_fname);
-				rb->next = rollbacks;
-				rollbacks = rb;
-				break;
-			}
-			if (ret < 0)
-				break;
+	if (ret > 0) {
+		if (ctx->pool == NULL) {
+			ctx->pool = pool_alloconly_create("hard copy rollbacks",
+							  2048);
+		}
 
-			if (i == 10) {
-                                mail_storage_set_error(src->box.storage,
-					"File name keeps changing, "
-					"copy failed");
-				break;
-			}
-
-			if (!maildir_index_sync_readonly(index, fname, &found))
-				break;
-
-			if (!found)
-				break;
-		}
-		t_pop();
+		rb = p_new(ctx->pool, struct rollback, 1);
+		rb->fname = p_strdup(ctx->pool, dest_fname);
+		rb->next = ctx->rollbacks;
+		ctx->rollbacks = rb;
 	}
 
-	if (ret <= 0) {
-		for (rb = rollbacks; rb != NULL; rb = rb->next) {
+	return ret;
+}
+
+struct mail_copy_context *maildir_storage_copy_init(struct mailbox *box)
+{
+	struct index_mailbox *ibox = (struct index_mailbox *) box;
+	struct maildir_copy_context *ctx;
+
+	if (box->readonly) {
+		mail_storage_set_error(box->storage,
+				       "Destination mailbox is read-only");
+		return NULL;
+	}
+
+	ctx = i_new(struct maildir_copy_context, 1);
+	ctx->hardlink = getenv("MAILDIR_COPY_WITH_HARDLINKS") != NULL;
+	ctx->ibox = ibox;
+	return (struct mail_copy_context *) ctx;
+}
+
+int maildir_storage_copy_deinit(struct mail_copy_context *_ctx, int rollback)
+{
+	struct maildir_copy_context *ctx = (struct maildir_copy_context *) _ctx;
+        struct rollback *rb;
+	int ret = TRUE;
+
+	if (ctx->ctx != NULL)
+		ret = index_storage_copy_deinit(ctx->ctx, rollback);
+
+	if (rollback) {
+		for (rb = ctx->rollbacks; rb != NULL; rb = rb->next) {
 			t_push();
-			(void)unlink(t_strconcat(dest->index->mailbox_path,
-						 "new/", rb->fname, NULL));
+			(void)unlink(t_strconcat(ctx->ibox->index->mailbox_path,
+						 "/new/", rb->fname, NULL));
 			t_pop();
 		}
 	}
 
-	pool_unref(pool);
+	if (ctx->pool != NULL)
+		pool_unref(ctx->pool);
+
+	i_free(ctx);
 	return ret;
 }
 
-static int copy_with_hardlinks(struct index_mailbox *src,
-			       struct index_mailbox *dest,
-			       const char *messageset, int uidset)
+int maildir_storage_copy(struct mail *mail, struct mail_copy_context *_ctx)
 {
-        struct messageset_context *ctx;
-	int exdev, ret, ret2;
-
-	if (!src->index->set_lock(src->index, MAIL_LOCK_SHARED))
-		return -1;
+	struct maildir_copy_context *ctx = (struct maildir_copy_context *) _ctx;
+	int ret;
 
-	ctx = index_messageset_init(src, messageset, uidset, FALSE);
-	ret = hardlink_messageset(ctx, src, dest);
-	exdev = ret < 0 && errno == EXDEV;
-	ret2 = index_messageset_deinit(ctx);
+	if (ctx->hardlink && mail->box->storage == ctx->ibox->box.storage) {
+		t_push();
+		ret = maildir_copy_hardlink(mail, ctx);
+		t_pop();
 
-	if (ret2 < 0)
-		ret = -1;
+		if (ret > 0)
+			return TRUE;
+		if (ret < 0)
+			return FALSE;
 
-	if (ret == 0 || ret2 == 0) {
-		mail_storage_set_error(src->box.storage,
-			"Some of the requested messages no longer exist.");
-		ret = -1;
+		/* non-fatal hardlinking failure, try the slow way */
 	}
 
-	(void)index_storage_lock(src, MAIL_LOCK_UNLOCK);
-
-	return exdev ? 0 : ret;
-}
-
-int maildir_storage_copy(struct mailbox *box, struct mailbox *destbox,
-			 const char *messageset, int uidset)
-{
-	struct index_mailbox *ibox = (struct index_mailbox *) box;
-
-	if (destbox->readonly) {
-		mail_storage_set_error(box->storage,
-				       "Destination mailbox is read-only");
-		return FALSE;
-	}
+	if (ctx->ctx == NULL)
+		ctx->ctx = index_storage_copy_init(&ctx->ibox->box);
 
-	if (getenv("MAILDIR_COPY_WITH_HARDLINKS") != NULL &&
-	    destbox->storage == box->storage) {
-		/* both source and destination mailbox are in maildirs and
-		   copy_with_hardlinks option is on, do it */
-		switch (copy_with_hardlinks(ibox,
-			(struct index_mailbox *) destbox, messageset, uidset)) {
-		case 1:
-			return TRUE;
-		case 0:
-			/* non-fatal hardlinking failure, try the slow way */
-			break;
-		default:
-			return FALSE;
-		}
-	}
-
-	return index_storage_copy(box, destbox, messageset, uidset);
+	return index_storage_copy(mail, ctx->ctx);
 }
--- a/src/lib-storage/index/maildir/maildir-storage.c	Wed Jul 23 03:29:32 2003 +0300
+++ b/src/lib-storage/index/maildir/maildir-storage.c	Wed Jul 23 03:40:49 2003 +0300
@@ -319,6 +319,11 @@
 		create_control_dir(storage, "INBOX");
 }
 
+static void maildir_mail_init(struct index_mail *mail)
+{
+	mail->mail.copy = maildir_storage_copy;
+}
+
 static struct mailbox *
 maildir_open(struct mail_storage *storage, const char *name,
 	     enum mailbox_open_flags flags)
@@ -339,8 +344,10 @@
 
 	ibox = index_storage_mailbox_init(storage, &maildir_mailbox,
 					  index, name, flags);
-	if (ibox != NULL)
+	if (ibox != NULL) {
 		ibox->expunge_locked = maildir_expunge_locked;
+		ibox->mail_init = maildir_mail_init;
+	}
 	return (struct mailbox *) ibox;
 }
 
@@ -739,7 +746,6 @@
 	maildir_storage_auto_sync,
 	index_storage_expunge,
 	index_storage_update_flags,
-	maildir_storage_copy,
 	index_storage_fetch_init,
 	index_storage_fetch_deinit,
 	index_storage_fetch_next,
@@ -752,6 +758,8 @@
 	maildir_storage_save_init,
 	maildir_storage_save_deinit,
 	maildir_storage_save_next,
+	maildir_storage_copy_init,
+	maildir_storage_copy_deinit,
 	mail_storage_is_inconsistency_error,
 
 	FALSE,
--- a/src/lib-storage/index/maildir/maildir-storage.h	Wed Jul 23 03:29:32 2003 +0300
+++ b/src/lib-storage/index/maildir/maildir-storage.h	Wed Jul 23 03:40:49 2003 +0300
@@ -3,8 +3,9 @@
 
 #include "index-storage.h"
 
-int maildir_storage_copy(struct mailbox *box, struct mailbox *destbox,
-			 const char *messageset, int uidset);
+struct mail_copy_context *maildir_storage_copy_init(struct mailbox *box);
+int maildir_storage_copy_deinit(struct mail_copy_context *ctx, int rollback);
+int maildir_storage_copy(struct mail *mail, struct mail_copy_context *ctx);
 
 struct mail_save_context *
 maildir_storage_save_init(struct mailbox *box, int transaction);
--- a/src/lib-storage/index/mbox/mbox-storage.c	Wed Jul 23 03:29:32 2003 +0300
+++ b/src/lib-storage/index/mbox/mbox-storage.c	Wed Jul 23 03:40:49 2003 +0300
@@ -748,7 +748,6 @@
 	mbox_storage_auto_sync,
 	index_storage_expunge,
 	index_storage_update_flags,
-	index_storage_copy,
 	index_storage_fetch_init,
 	index_storage_fetch_deinit,
 	index_storage_fetch_next,
@@ -761,6 +760,8 @@
 	mbox_storage_save_init,
 	mbox_storage_save_deinit,
 	mbox_storage_save_next,
+	index_storage_copy_init,
+	index_storage_copy_deinit,
 	mail_storage_is_inconsistency_error,
 
 	FALSE,
--- a/src/lib-storage/mail-storage.h	Wed Jul 23 03:29:32 2003 +0300
+++ b/src/lib-storage/mail-storage.h	Wed Jul 23 03:40:49 2003 +0300
@@ -255,10 +255,6 @@
 			    enum modify_type modify_type, int notify,
 			    int *all_found);
 
-	/* Copy mails to another mailbox. */
-	int (*copy)(struct mailbox *box, struct mailbox *destbox,
-		    const char *messageset, int uidset);
-
 	/* Initialize new fetch request. wanted_fields isn't required, but it
 	   can be used for optimizations. If *update_seen is TRUE, \Seen flag
 	   is set for all fetched mails. *update_seen may be changed back to
@@ -327,6 +323,13 @@
 			 time_t received_date, int timezone_offset,
 			 struct istream *data);
 
+	/* Initialize copying operation to this mailbox. The actual copying
+	   can be done by fetching or searching mails and calling mail's
+	   expunge() method. */
+	struct mail_copy_context *(*copy_init)(struct mailbox *box);
+	/* Finish copying. */
+	int (*copy_deinit)(struct mail_copy_context *ctx, int rollback);
+
 	/* Returns TRUE if mailbox is now in inconsistent state, meaning that
 	   the message IDs etc. may have changed - only way to recover this
 	   would be to fully close the mailbox and reopen it. With IMAP
@@ -343,6 +346,7 @@
 
 struct mail {
 	/* always set */
+	struct mailbox *box;
 	unsigned int seq;
 	unsigned int uid;
 
@@ -383,6 +387,9 @@
 	/* Get the any of the "special" fields. */
 	const char *(*get_special)(struct mail *mail,
 				   enum mail_fetch_field field);
+
+	/* Copy this mail to another mailbox. */
+	int (*copy)(struct mail *mail, struct mail_copy_context *ctx);
 };
 
 struct mailbox_list {