Writing an custom imap command
Ryan Beethe
ryan at splintermail.com
Sat May 1 18:32:56 EEST 2021
I'm interested in writing a custom imap command that behaves a bit like
IDLE but synchronizes some state that is specific to my mail client /
mail server.
I found that stateless commands were trivial to understand, and I really
like the plugin pattern for registering custom commands.
But I have a few questions on how to write a long-running command that I
was not able to answer by reading through the code. This mailing list
seems like the best place to ask them.
For reference, the source code for my custom command is here:
https://github.com/Splintermail/splintermail-client/blob/dev/server/xkeysync.c
Thanks,
Ryan
------
1. Why does cmd-idle.c sometimes call client_command_free()? But
sometimes it doesn't?
For example, cmd_idle_continue() frees it in some branches but not
others. That makes no sense to me; it seems like it should be based
on your entrypoint (mailbox notify callback vs input callback vs
timeout callback), not based on which branch of logic within that
entrypoint.
2. Why does cmd-idle.c ever call client_destroy()? That seems like
something that should be invoked only by the imap process, not by any
command.
It calls it in cmd-idle.c:idle_callback (which is a mailbox notify
callback). It invokes it after idle_sync_now() when it detects that
client->disconnected is set. Maybe that happens in imap_sync_init()
or something?
3. Why does cmd-idle.c ever call client_disconnect()? That also seems
like the responsibility of the imap process, and not any command.
idle_client_input_more() detects when i_stream_read returns -1,
meaning that the client has *already disconnected*. Then it calls
client_disconnect().
I think this is the crazy part... the istream is effectively unique
to the imap process, so it seems unreasonable that any command is
responsible for cleaning it up; it should just always happen at the
imap process level before exiting, right?
4. What does client_continue_pending_input() actually do, and under what
conditions does it need to be called?
There is one place that you *can't* call it; there is a section in
imap-client.c:client_handle_input() that calls
imap-client.c:client_handle_next_command(), which calls the
cmd->func(). That makes sense; that's the input trigger for the
command plugin, so maybe you only have to trigger it when you are
receiving input that doesn't fit into the normal command args
behavior.
It has a comment that says "this function is called at the end of
I/O callbacks (and only there)". It _is_ called by client_input()
and by client_output(), but also by:
- cmd-idle.c:idle_client_input (io_add_istream callback)
- cmd-append.c:client_input_append (io_add_istream callback)
- imap-search.c:cmd_search_more_callback (timeout_add callback)
The first two cases seem to be the only io_add_istream() commands
that even exist, so that explains them. I can't explain the
imap-search.c case at all.
Reading through it I have really no idea what
client_continue_pending_input is really doing.
My command has a DONE mechanic just like IDLE so I'm pretty sure I
need to invoke this function, I'm just concerned I'm going to do it
wrong if I don't understand the mechanics of it.
More information about the dovecot
mailing list