static xint proxy_get_caps( store_t *gctx )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	xint rv;
	rv = ctx->real_driver->get_caps( ctx->real_store );
	debug( "%sCalled get_caps, ret=%#x\n", ctx->label, rv );
	return rv;
}

typedef union {
	gen_cmd_t gen;
	struct {
		GEN_CMD
		void (*callback)( int sts, void *aux );
		void *callback_aux;
	};
} connect_store_cmd_t;

static void
proxy_connect_store_cb( int sts, void *aux )
{
	connect_store_cmd_t *cmd = (connect_store_cmd_t *)aux;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Callback enter connect_store, sts=%d\n", ctx->label, cmd->tag, sts );
	cmd->callback( sts, cmd->callback_aux );
	debug( "%s[% 2d] Callback leave connect_store\n", ctx->label, cmd->tag );
	proxy_cmd_done( &cmd->gen );
}

static void
proxy_do_connect_store( gen_cmd_t *gcmd )
{
	connect_store_cmd_t *cmd = (connect_store_cmd_t *)gcmd;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Enter connect_store\n", ctx->label, cmd->tag );
	ctx->real_driver->connect_store( ctx->real_store, proxy_connect_store_cb, cmd );
	debug( "%s[% 2d] Leave connect_store\n", ctx->label, cmd->tag );
}

static void proxy_connect_store( store_t *gctx, void (*cb)( int sts, void *aux ), void *aux )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	connect_store_cmd_t *cmd = (connect_store_cmd_t *)proxy_cmd_new( ctx, sizeof(connect_store_cmd_t) );
	cmd->queued_cb = proxy_do_connect_store;
	cmd->callback = cb;
	cmd->callback_aux = aux;
	proxy_invoke( &cmd->gen, "connect_store" );
}

static void proxy_free_store( store_t *gctx )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	debug( "%sEnter free_store\n", ctx->label );
	proxy_cancel_queued_cmds( ctx );
	ctx->real_driver->free_store( ctx->real_store );
	debug( "%sLeave free_store\n", ctx->label );
	proxy_store_deref( ctx );
}

static void proxy_cancel_store( store_t *gctx )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	debug( "%sEnter cancel_store\n", ctx->label );
	proxy_cancel_queued_cmds( ctx );
	ctx->real_driver->cancel_store( ctx->real_store );
	debug( "%sLeave cancel_store\n", ctx->label );
	proxy_store_deref( ctx );
}

typedef union {
	gen_cmd_t gen;
	struct {
		GEN_CMD
		void (*callback)( int sts, string_list_t *boxes, void *aux );
		void *callback_aux;
		int flags;
	};
} list_store_cmd_t;

static void
proxy_list_store_cb( int sts, string_list_t *boxes, void *aux )
{
	list_store_cmd_t *cmd = (list_store_cmd_t *)aux;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Callback enter list_store, sts=%d\n", ctx->label, cmd->tag, sts );
	if (sts == DRV_OK) {
		for (string_list_t *box = boxes; box; box = box->next)
			debug( "  %s\n", box->string );
	}
	cmd->callback( sts, boxes, cmd->callback_aux );
	debug( "%s[% 2d] Callback leave list_store\n", ctx->label, cmd->tag );
	proxy_cmd_done( &cmd->gen );
}

static void
proxy_do_list_store( gen_cmd_t *gcmd )
{
	list_store_cmd_t *cmd = (list_store_cmd_t *)gcmd;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Enter list_store, flags=%d\n", ctx->label, cmd->tag, cmd->flags );
	ctx->real_driver->list_store( ctx->real_store, cmd->flags, proxy_list_store_cb, cmd );
	debug( "%s[% 2d] Leave list_store\n", ctx->label, cmd->tag );
}

static void proxy_list_store( store_t *gctx, int flags, void (*cb)( int sts, string_list_t *boxes, void *aux ), void *aux )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	list_store_cmd_t *cmd = (list_store_cmd_t *)proxy_cmd_new( ctx, sizeof(list_store_cmd_t) );
	cmd->queued_cb = proxy_do_list_store;
	cmd->callback = cb;
	cmd->callback_aux = aux;
	cmd->flags = flags;
	proxy_invoke( &cmd->gen, "list_store" );
}

static int proxy_select_box( store_t *gctx, const char *name )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	debug( "%sEnter select_box, name=%s\n", ctx->label, name );
	int rv;
	ctx->is_fake = 0;
	rv = ctx->real_driver->select_box( ctx->real_store, name );
	debug( "%sLeave select_box, ret=%d\n", ctx->label, rv );
	return rv;
}

static const char *proxy_get_box_path( store_t *gctx )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	const char *rv;
	rv = ctx->real_driver->get_box_path( ctx->real_store );
	debug( "%sCalled get_box_path, ret=%s\n", ctx->label, rv );
	return rv;
}

typedef union {
	gen_cmd_t gen;
	struct {
		GEN_CMD
		void (*callback)( int sts, void *aux );
		void *callback_aux;
	};
} create_box_cmd_t;

static void
proxy_create_box_cb( int sts, void *aux )
{
	create_box_cmd_t *cmd = (create_box_cmd_t *)aux;
	proxy_store_t *ctx = cmd->ctx;

	countStep();
	debug( "%s[% 2d] Callback enter create_box, sts=%d\n", ctx->label, cmd->tag, sts );
	cmd->callback( sts, cmd->callback_aux );
	debug( "%s[% 2d] Callback leave create_box\n", ctx->label, cmd->tag );
	proxy_cmd_done( &cmd->gen );
}

static void
proxy_do_create_box( gen_cmd_t *gcmd )
{
	create_box_cmd_t *cmd = (create_box_cmd_t *)gcmd;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Enter create_box%s\n", ctx->label, cmd->tag, (DFlags & DRYRUN) ? " [DRY]" : "" );
	if (DFlags & DRYRUN) {
		ctx->is_fake = 1;
		proxy_create_box_cb( DRV_OK, cmd );
	} else {
		ctx->real_driver->create_box( ctx->real_store, proxy_create_box_cb, cmd );
	}
	debug( "%s[% 2d] Leave create_box\n", ctx->label, cmd->tag );
}

static void proxy_create_box( store_t *gctx, void (*cb)( int sts, void *aux ), void *aux )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	create_box_cmd_t *cmd = (create_box_cmd_t *)proxy_cmd_new( ctx, sizeof(create_box_cmd_t) );
	cmd->queued_cb = proxy_do_create_box;
	cmd->callback = cb;
	cmd->callback_aux = aux;
	proxy_invoke( &cmd->gen, "create_box" );
}

typedef union {
	gen_cmd_t gen;
	struct {
		GEN_CMD
		void (*callback)( int sts, uint uidvalidity, void *aux );
		void *callback_aux;
	};
} open_box_cmd_t;

static void
proxy_open_box_cb( int sts, uint uidvalidity, void *aux )
{
	open_box_cmd_t *cmd = (open_box_cmd_t *)aux;
	proxy_store_t *ctx = cmd->ctx;

	if (sts == DRV_OK) {
		debug( "%s[% 2d] Callback enter open_box, sts=" stringify(DRV_OK) ", uidvalidity=%u\n", ctx->label, cmd->tag, uidvalidity );
	} else {
		debug( "%s[% 2d] Callback enter open_box, sts=%d\n", ctx->label, cmd->tag, sts );
	}
	cmd->callback( sts, uidvalidity, cmd->callback_aux );
	debug( "%s[% 2d] Callback leave open_box\n", ctx->label, cmd->tag );
	proxy_cmd_done( &cmd->gen );
}

static void
proxy_do_open_box( gen_cmd_t *gcmd )
{
	open_box_cmd_t *cmd = (open_box_cmd_t *)gcmd;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Enter open_box%s\n", ctx->label, cmd->tag, ctx->is_fake ? " [FAKE]" : "" );
	if (ctx->is_fake) {
		ctx->fake_nextuid = 1;
		proxy_open_box_cb( DRV_OK, 1, cmd );
	} else {
		ctx->real_driver->open_box( ctx->real_store, proxy_open_box_cb, cmd );
	}
	debug( "%s[% 2d] Leave open_box\n", ctx->label, cmd->tag );
}

static void proxy_open_box( store_t *gctx, void (*cb)( int sts, uint uidvalidity, void *aux ), void *aux )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	open_box_cmd_t *cmd = (open_box_cmd_t *)proxy_cmd_new( ctx, sizeof(open_box_cmd_t) );
	cmd->queued_cb = proxy_do_open_box;
	cmd->callback = cb;
	cmd->callback_aux = aux;
	proxy_invoke( &cmd->gen, "open_box" );
}

static uint proxy_get_uidnext( store_t *gctx )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	uint rv;
	if (ctx->is_fake) {
		rv = ctx->fake_nextuid;
	} else {
		rv = ctx->real_driver->get_uidnext( ctx->real_store );
		ctx->fake_nextuid = rv;
	}
	debug( "%sCalled get_uidnext%s, ret=%u\n", ctx->label, ctx->is_fake ? " [FAKE]" : "", rv );
	return rv;
}

static xint proxy_get_supported_flags( store_t *gctx )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	xint rv;
	if (ctx->is_fake)
		rv = 255;
	else
		rv = ctx->real_driver->get_supported_flags( ctx->real_store );
	debug( "%sCalled get_supported_flags%s, ret=%#x\n", ctx->label, ctx->is_fake ? " [FAKE]" : "", rv );
	return rv;
}

static int proxy_confirm_box_empty( store_t *gctx )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	debug( "%sEnter confirm_box_empty%s\n", ctx->label, ctx->is_fake ? " [FAKE]" : "" );
	int rv;
	if (ctx->is_fake)
		rv = 1;
	else
		rv = ctx->real_driver->confirm_box_empty( ctx->real_store );
	debug( "%sLeave confirm_box_empty, ret=%d\n", ctx->label, rv );
	return rv;
}

typedef union {
	gen_cmd_t gen;
	struct {
		GEN_CMD
		void (*callback)( int sts, void *aux );
		void *callback_aux;
	};
} delete_box_cmd_t;

static void
proxy_delete_box_cb( int sts, void *aux )
{
	delete_box_cmd_t *cmd = (delete_box_cmd_t *)aux;
	proxy_store_t *ctx = cmd->ctx;

	countStep();
	debug( "%s[% 2d] Callback enter delete_box, sts=%d\n", ctx->label, cmd->tag, sts );
	cmd->callback( sts, cmd->callback_aux );
	debug( "%s[% 2d] Callback leave delete_box\n", ctx->label, cmd->tag );
	proxy_cmd_done( &cmd->gen );
}

static void
proxy_do_delete_box( gen_cmd_t *gcmd )
{
	delete_box_cmd_t *cmd = (delete_box_cmd_t *)gcmd;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Enter delete_box%s\n", ctx->label, cmd->tag, (DFlags & DRYRUN) ? " [DRY]" : "" );
	if (DFlags & DRYRUN) {
		ctx->is_fake = 0;
		proxy_delete_box_cb( DRV_OK, cmd );
	} else {
		ctx->real_driver->delete_box( ctx->real_store, proxy_delete_box_cb, cmd );
	}
	debug( "%s[% 2d] Leave delete_box\n", ctx->label, cmd->tag );
}

static void proxy_delete_box( store_t *gctx, void (*cb)( int sts, void *aux ), void *aux )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	delete_box_cmd_t *cmd = (delete_box_cmd_t *)proxy_cmd_new( ctx, sizeof(delete_box_cmd_t) );
	cmd->queued_cb = proxy_do_delete_box;
	cmd->callback = cb;
	cmd->callback_aux = aux;
	proxy_invoke( &cmd->gen, "delete_box" );
}

static int proxy_finish_delete_box( store_t *gctx )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	debug( "%sEnter finish_delete_box%s\n", ctx->label, (DFlags & DRYRUN) ? " [DRY]" : "" );
	int rv;
	if (DFlags & DRYRUN)
		rv = DRV_OK;
	else
		rv = ctx->real_driver->finish_delete_box( ctx->real_store );
	debug( "%sLeave finish_delete_box, ret=%d\n", ctx->label, rv );
	return rv;
}

static xint proxy_prepare_load_box( store_t *gctx, xint opts )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	debug( "%sEnter prepare_load_box, opts=%s\n", ctx->label, fmt_opts( opts ).str );
	xint rv;
	rv = ctx->real_driver->prepare_load_box( ctx->real_store, opts );
	debug( "%sLeave prepare_load_box, ret=%s\n", ctx->label, fmt_opts( rv ).str );
	return rv;
}

typedef union {
	gen_cmd_t gen;
	struct {
		GEN_CMD
		void (*callback)( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux );
		void *callback_aux;
		uint minuid;
		uint maxuid;
		uint finduid;
		uint pairuid;
		uint newuid;
		uint_array_t excs;
	};
} load_box_cmd_t;

static void
proxy_load_box_cb( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux )
{
	load_box_cmd_t *cmd = (load_box_cmd_t *)aux;
	proxy_store_t *ctx = cmd->ctx;

	if (sts == DRV_OK) {
		debug( "%s[% 2d] Callback enter load_box, sts=" stringify(DRV_OK) ", total=%d, recent=%d\n", ctx->label, cmd->tag, total_msgs, recent_msgs );
		for (message_t *msg = msgs; msg; msg = msg->next) {
			if (msg->status & M_DEAD)
				continue;
			debug( "  uid=%-5u flags=%-4s size=%-6u tuid=%." stringify(TUIDL) "s\n",
			       msg->uid, (msg->status & M_FLAGS) ? fmt_flags( msg->flags ).str : "?", msg->size, *msg->tuid ? msg->tuid : "?" );
		}
	} else {
		debug( "%s[% 2d] Callback enter load_box, sts=%d\n", ctx->label, cmd->tag, sts );
	}
	cmd->callback( sts, msgs, total_msgs, recent_msgs, cmd->callback_aux );
	debug( "%s[% 2d] Callback leave load_box\n", ctx->label, cmd->tag );
	proxy_cmd_done( &cmd->gen );
}

static void
proxy_do_load_box( gen_cmd_t *gcmd )
{
	load_box_cmd_t *cmd = (load_box_cmd_t *)gcmd;
	proxy_store_t *ctx = cmd->ctx;

	char ubuf[12];
	debug( "%s[% 2d] Enter load_box%s, [%u,%s] (find >= %u, paired <= %u, new > %u)\n", ctx->label, cmd->tag, ctx->is_fake ? " [FAKE]" : "", cmd->minuid, (cmd->maxuid == UINT_MAX) ? "inf" : (nfsnprintf( ubuf, sizeof(ubuf), "%u", cmd->maxuid ), ubuf), cmd->finduid, cmd->pairuid, cmd->newuid );
	if (cmd->excs.size) {
		debugn( "  excs:" );
		for (uint t = 0; t < cmd->excs.size; t++)
			debugn( " %u", cmd->excs.data[t] );
		debug( "\n" );
	}
	if (ctx->is_fake)
		proxy_load_box_cb( DRV_OK, NULL, 0, 0, cmd );
	else
		ctx->real_driver->load_box( ctx->real_store, cmd->minuid, cmd->maxuid, cmd->finduid, cmd->pairuid, cmd->newuid, cmd->excs, proxy_load_box_cb, cmd );
	debug( "%s[% 2d] Leave load_box\n", ctx->label, cmd->tag );
}

static void proxy_load_box( store_t *gctx, uint minuid, uint maxuid, uint finduid, uint pairuid, uint newuid, uint_array_t excs, void (*cb)( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux ), void *aux )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	load_box_cmd_t *cmd = (load_box_cmd_t *)proxy_cmd_new( ctx, sizeof(load_box_cmd_t) );
	cmd->queued_cb = proxy_do_load_box;
	cmd->callback = cb;
	cmd->callback_aux = aux;
	cmd->minuid = minuid;
	cmd->maxuid = maxuid;
	cmd->finduid = finduid;
	cmd->pairuid = pairuid;
	cmd->newuid = newuid;
	cmd->excs = excs;
	proxy_invoke( &cmd->gen, "load_box" );
}

typedef union {
	gen_cmd_t gen;
	struct {
		GEN_CMD
		void (*callback)( int sts, void *aux );
		void *callback_aux;
		message_t *msg;
		msg_data_t *data;
		int minimal;
	};
} fetch_msg_cmd_t;

static void
proxy_fetch_msg_cb( int sts, void *aux )
{
	fetch_msg_cmd_t *cmd = (fetch_msg_cmd_t *)aux;
	proxy_store_t *ctx = cmd->ctx;

	if (sts == DRV_OK) {
		debug( "%s[% 2d] Callback enter fetch_msg, sts=" stringify(DRV_OK) ", flags=%s, date=%lld, size=%u\n", ctx->label, cmd->tag, fmt_flags( cmd->data->flags ).str, (long long)cmd->data->date, cmd->data->len );
		if (DFlags & DEBUG_DRV_ALL) {
			printf( "%s=========\n", cmd->ctx->label );
			fwrite( cmd->data->data, cmd->data->len, 1, stdout );
			printf( "%s=========\n", cmd->ctx->label );
			fflush( stdout );
		}
	} else {
		debug( "%s[% 2d] Callback enter fetch_msg, sts=%d\n", ctx->label, cmd->tag, sts );
	}
	cmd->callback( sts, cmd->callback_aux );
	debug( "%s[% 2d] Callback leave fetch_msg\n", ctx->label, cmd->tag );
	proxy_cmd_done( &cmd->gen );
}

static void
proxy_do_fetch_msg( gen_cmd_t *gcmd )
{
	fetch_msg_cmd_t *cmd = (fetch_msg_cmd_t *)gcmd;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Enter fetch_msg%s, uid=%u, want_flags=%s, want_date=%s\n", ctx->label, cmd->tag, (DFlags & DRYRUN) ? " [DRY]" : "", cmd->msg->uid, !(cmd->msg->status & M_FLAGS) ? "yes" : "no", cmd->data->date ? "yes" : "no" );
	if (DFlags & DRYRUN) {
		cmd->data->data = strdup( "" );
		cmd->data->len = 0;
		proxy_fetch_msg_cb( DRV_OK, cmd );
	} else {
		ctx->real_driver->fetch_msg( ctx->real_store, cmd->msg, cmd->data, cmd->minimal, proxy_fetch_msg_cb, cmd );
	}
	debug( "%s[% 2d] Leave fetch_msg\n", ctx->label, cmd->tag );
}

static void proxy_fetch_msg( store_t *gctx, message_t *msg, msg_data_t *data, int minimal, void (*cb)( int sts, void *aux ), void *aux )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	fetch_msg_cmd_t *cmd = (fetch_msg_cmd_t *)proxy_cmd_new( ctx, sizeof(fetch_msg_cmd_t) );
	cmd->queued_cb = proxy_do_fetch_msg;
	cmd->callback = cb;
	cmd->callback_aux = aux;
	cmd->msg = msg;
	cmd->data = data;
	cmd->minimal = minimal;
	proxy_invoke( &cmd->gen, "fetch_msg" );
}

typedef union {
	gen_cmd_t gen;
	struct {
		GEN_CMD
		void (*callback)( int sts, uint uid, void *aux );
		void *callback_aux;
		msg_data_t *data;
		int to_trash;
	};
} store_msg_cmd_t;

static void
proxy_store_msg_cb( int sts, uint uid, void *aux )
{
	store_msg_cmd_t *cmd = (store_msg_cmd_t *)aux;
	proxy_store_t *ctx = cmd->ctx;

	countStep();
	if (sts == DRV_OK) {
		debug( "%s[% 2d] Callback enter store_msg, sts=" stringify(DRV_OK) ", uid=%u\n", ctx->label, cmd->tag, uid );
	} else {
		debug( "%s[% 2d] Callback enter store_msg, sts=%d\n", ctx->label, cmd->tag, sts );
	}
	cmd->callback( sts, uid, cmd->callback_aux );
	debug( "%s[% 2d] Callback leave store_msg\n", ctx->label, cmd->tag );
	proxy_cmd_done( &cmd->gen );
}

static void
proxy_do_store_msg( gen_cmd_t *gcmd )
{
	store_msg_cmd_t *cmd = (store_msg_cmd_t *)gcmd;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Enter store_msg%s, flags=%s, date=%lld, size=%u, to_trash=%s\n", ctx->label, cmd->tag, (DFlags & DRYRUN) ? " [DRY]" : "", fmt_flags( cmd->data->flags ).str, (long long)cmd->data->date, cmd->data->len, cmd->to_trash ? "yes" : "no" );
	if (DFlags & DEBUG_DRV_ALL) {
		printf( "%s>>>>>>>>>\n", ctx->label );
		fwrite( cmd->data->data, cmd->data->len, 1, stdout );
		printf( "%s>>>>>>>>>\n", ctx->label );
		fflush( stdout );
	}
	if (DFlags & DRYRUN)
		proxy_store_msg_cb( DRV_OK, cmd->to_trash ? 0 : ctx->fake_nextuid++, cmd );
	else
		ctx->real_driver->store_msg( ctx->real_store, cmd->data, cmd->to_trash, proxy_store_msg_cb, cmd );
	debug( "%s[% 2d] Leave store_msg\n", ctx->label, cmd->tag );
}

static void proxy_store_msg( store_t *gctx, msg_data_t *data, int to_trash, void (*cb)( int sts, uint uid, void *aux ), void *aux )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	store_msg_cmd_t *cmd = (store_msg_cmd_t *)proxy_cmd_new( ctx, sizeof(store_msg_cmd_t) );
	cmd->queued_cb = proxy_do_store_msg;
	cmd->callback = cb;
	cmd->callback_aux = aux;
	cmd->data = data;
	cmd->to_trash = to_trash;
	proxy_invoke( &cmd->gen, "store_msg" );
}

typedef union {
	gen_cmd_t gen;
	struct {
		GEN_CMD
		void (*callback)( int sts, message_t *msgs, void *aux );
		void *callback_aux;
		uint newuid;
	};
} find_new_msgs_cmd_t;

static void
proxy_find_new_msgs_cb( int sts, message_t *msgs, void *aux )
{
	find_new_msgs_cmd_t *cmd = (find_new_msgs_cmd_t *)aux;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Callback enter find_new_msgs, sts=%d\n", ctx->label, cmd->tag, sts );
	if (sts == DRV_OK) {
		for (message_t *msg = msgs; msg; msg = msg->next) {
			if (msg->status & M_DEAD)
				continue;
			debug( "  uid=%-5u tuid=%." stringify(TUIDL) "s\n", msg->uid, msg->tuid );
		}
	}
	cmd->callback( sts, msgs, cmd->callback_aux );
	debug( "%s[% 2d] Callback leave find_new_msgs\n", ctx->label, cmd->tag );
	proxy_cmd_done( &cmd->gen );
}

static void
proxy_do_find_new_msgs( gen_cmd_t *gcmd )
{
	find_new_msgs_cmd_t *cmd = (find_new_msgs_cmd_t *)gcmd;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Enter find_new_msgs, newuid=%u\n", ctx->label, cmd->tag, cmd->newuid );
	ctx->real_driver->find_new_msgs( ctx->real_store, cmd->newuid, proxy_find_new_msgs_cb, cmd );
	debug( "%s[% 2d] Leave find_new_msgs\n", ctx->label, cmd->tag );
}

static void proxy_find_new_msgs( store_t *gctx, uint newuid, void (*cb)( int sts, message_t *msgs, void *aux ), void *aux )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	find_new_msgs_cmd_t *cmd = (find_new_msgs_cmd_t *)proxy_cmd_new( ctx, sizeof(find_new_msgs_cmd_t) );
	cmd->queued_cb = proxy_do_find_new_msgs;
	cmd->callback = cb;
	cmd->callback_aux = aux;
	cmd->newuid = newuid;
	proxy_invoke( &cmd->gen, "find_new_msgs" );
}

typedef union {
	gen_cmd_t gen;
	struct {
		GEN_CMD
		void (*callback)( int sts, void *aux );
		void *callback_aux;
		message_t *msg;
		uint uid;
		int add;
		int del;
	};
} set_msg_flags_cmd_t;

static void
proxy_set_msg_flags_cb( int sts, void *aux )
{
	set_msg_flags_cmd_t *cmd = (set_msg_flags_cmd_t *)aux;
	proxy_store_t *ctx = cmd->ctx;

	countStep();
	debug( "%s[% 2d] Callback enter set_msg_flags, sts=%d\n", ctx->label, cmd->tag, sts );
	cmd->callback( sts, cmd->callback_aux );
	debug( "%s[% 2d] Callback leave set_msg_flags\n", ctx->label, cmd->tag );
	proxy_cmd_done( &cmd->gen );
}

static void
proxy_do_set_msg_flags( gen_cmd_t *gcmd )
{
	set_msg_flags_cmd_t *cmd = (set_msg_flags_cmd_t *)gcmd;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Enter set_msg_flags%s, uid=%u, add=%s, del=%s\n", ctx->label, cmd->tag, (DFlags & DRYRUN) ? " [DRY]" : "", cmd->uid, fmt_flags( cmd->add ).str, fmt_flags( cmd->del ).str );
	if (DFlags & DRYRUN)
		proxy_set_msg_flags_cb( DRV_OK, cmd );
	else
		ctx->real_driver->set_msg_flags( ctx->real_store, cmd->msg, cmd->uid, cmd->add, cmd->del, proxy_set_msg_flags_cb, cmd );
	debug( "%s[% 2d] Leave set_msg_flags\n", ctx->label, cmd->tag );
}

static void proxy_set_msg_flags( store_t *gctx, message_t *msg, uint uid, int add, int del, void (*cb)( int sts, void *aux ), void *aux )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	set_msg_flags_cmd_t *cmd = (set_msg_flags_cmd_t *)proxy_cmd_new( ctx, sizeof(set_msg_flags_cmd_t) );
	cmd->queued_cb = proxy_do_set_msg_flags;
	cmd->callback = cb;
	cmd->callback_aux = aux;
	cmd->msg = msg;
	cmd->uid = uid;
	cmd->add = add;
	cmd->del = del;
	proxy_invoke( &cmd->gen, "set_msg_flags" );
}

typedef union {
	gen_cmd_t gen;
	struct {
		GEN_CMD
		void (*callback)( int sts, void *aux );
		void *callback_aux;
		message_t *msg;
	};
} trash_msg_cmd_t;

static void
proxy_trash_msg_cb( int sts, void *aux )
{
	trash_msg_cmd_t *cmd = (trash_msg_cmd_t *)aux;
	proxy_store_t *ctx = cmd->ctx;

	countStep();
	debug( "%s[% 2d] Callback enter trash_msg, sts=%d\n", ctx->label, cmd->tag, sts );
	cmd->callback( sts, cmd->callback_aux );
	debug( "%s[% 2d] Callback leave trash_msg\n", ctx->label, cmd->tag );
	proxy_cmd_done( &cmd->gen );
}

static void
proxy_do_trash_msg( gen_cmd_t *gcmd )
{
	trash_msg_cmd_t *cmd = (trash_msg_cmd_t *)gcmd;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Enter trash_msg%s, uid=%u\n", ctx->label, cmd->tag, (DFlags & DRYRUN) ? " [DRY]" : "", cmd->msg->uid );
	if (DFlags & DRYRUN)
		proxy_trash_msg_cb( DRV_OK, cmd );
	else
		ctx->real_driver->trash_msg( ctx->real_store, cmd->msg, proxy_trash_msg_cb, cmd );
	debug( "%s[% 2d] Leave trash_msg\n", ctx->label, cmd->tag );
}

static void proxy_trash_msg( store_t *gctx, message_t *msg, void (*cb)( int sts, void *aux ), void *aux )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	trash_msg_cmd_t *cmd = (trash_msg_cmd_t *)proxy_cmd_new( ctx, sizeof(trash_msg_cmd_t) );
	cmd->queued_cb = proxy_do_trash_msg;
	cmd->callback = cb;
	cmd->callback_aux = aux;
	cmd->msg = msg;
	proxy_invoke( &cmd->gen, "trash_msg" );
}

typedef union {
	gen_cmd_t gen;
	struct {
		GEN_CMD
		void (*callback)( int sts, int reported, void *aux );
		void *callback_aux;
	};
} close_box_cmd_t;

static void
proxy_close_box_cb( int sts, int reported, void *aux )
{
	close_box_cmd_t *cmd = (close_box_cmd_t *)aux;
	proxy_store_t *ctx = cmd->ctx;

	countStep();
	if (sts == DRV_OK) {
		debug( "%s[% 2d] Callback enter close_box, sts=" stringify(DRV_OK) ", reported=%d\n", ctx->label, cmd->tag, reported );
	} else {
		debug( "%s[% 2d] Callback enter close_box, sts=%d\n", ctx->label, cmd->tag, sts );
	}
	cmd->callback( sts, reported, cmd->callback_aux );
	debug( "%s[% 2d] Callback leave close_box\n", ctx->label, cmd->tag );
	proxy_cmd_done( &cmd->gen );
}

static void
proxy_do_close_box( gen_cmd_t *gcmd )
{
	close_box_cmd_t *cmd = (close_box_cmd_t *)gcmd;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Enter close_box%s\n", ctx->label, cmd->tag, (DFlags & DRYRUN) ? " [DRY]" : "" );
	if (DFlags & DRYRUN)
		proxy_close_box_cb( DRV_OK, 0, cmd );
	else
		ctx->real_driver->close_box( ctx->real_store, proxy_close_box_cb, cmd );
	debug( "%s[% 2d] Leave close_box\n", ctx->label, cmd->tag );
}

static void proxy_close_box( store_t *gctx, void (*cb)( int sts, int reported, void *aux ), void *aux )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	close_box_cmd_t *cmd = (close_box_cmd_t *)proxy_cmd_new( ctx, sizeof(close_box_cmd_t) );
	cmd->queued_cb = proxy_do_close_box;
	cmd->callback = cb;
	cmd->callback_aux = aux;
	proxy_invoke( &cmd->gen, "close_box" );
}

typedef union {
	gen_cmd_t gen;
	struct {
		GEN_CMD
		void (*callback)( void *aux );
		void *callback_aux;
	};
} cancel_cmds_cmd_t;

static void
proxy_cancel_cmds_cb( void *aux )
{
	cancel_cmds_cmd_t *cmd = (cancel_cmds_cmd_t *)aux;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Callback enter cancel_cmds\n", ctx->label, cmd->tag );
	proxy_cancel_queued_cmds( ctx );
	cmd->callback( cmd->callback_aux );
	debug( "%s[% 2d] Callback leave cancel_cmds\n", ctx->label, cmd->tag );
	proxy_cmd_done( &cmd->gen );
}

static void
proxy_do_cancel_cmds( gen_cmd_t *gcmd )
{
	cancel_cmds_cmd_t *cmd = (cancel_cmds_cmd_t *)gcmd;
	proxy_store_t *ctx = cmd->ctx;

	debug( "%s[% 2d] Enter cancel_cmds\n", ctx->label, cmd->tag );
	ctx->real_driver->cancel_cmds( ctx->real_store, proxy_cancel_cmds_cb, cmd );
	debug( "%s[% 2d] Leave cancel_cmds\n", ctx->label, cmd->tag );
}

static void proxy_cancel_cmds( store_t *gctx, void (*cb)( void *aux ), void *aux )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	cancel_cmds_cmd_t *cmd = (cancel_cmds_cmd_t *)proxy_cmd_new( ctx, sizeof(cancel_cmds_cmd_t) );
	cmd->queued_cb = proxy_do_cancel_cmds;
	cmd->callback = cb;
	cmd->callback_aux = aux;
	proxy_invoke( &cmd->gen, "cancel_cmds" );
}

static uint proxy_get_memory_usage( store_t *gctx )
{
	proxy_store_t *ctx = (proxy_store_t *)gctx;

	uint rv;
	rv = ctx->real_driver->get_memory_usage( ctx->real_store );
	debug( "%sCalled get_memory_usage, ret=%u\n", ctx->label, rv );
	return rv;
}

struct driver proxy_driver = {
	proxy_get_caps,
	NULL,
	NULL,
	NULL,
	proxy_set_callbacks,
	proxy_connect_store,
	proxy_free_store,
	proxy_cancel_store,
	proxy_list_store,
	proxy_select_box,
	proxy_get_box_path,
	proxy_create_box,
	proxy_open_box,
	proxy_get_uidnext,
	proxy_get_supported_flags,
	proxy_confirm_box_empty,
	proxy_delete_box,
	proxy_finish_delete_box,
	proxy_prepare_load_box,
	proxy_load_box,
	proxy_fetch_msg,
	proxy_store_msg,
	proxy_find_new_msgs,
	proxy_set_msg_flags,
	proxy_trash_msg,
	proxy_close_box,
	proxy_cancel_cmds,
	proxy_get_memory_usage,
	NULL,
};
