mirror of
https://github.com/mjl-/mox.git
synced 2025-07-18 22:46:37 +03:00
imapserver: implement "inprogress" response code (RFC 9585) for keepalive during long search
For long searches in big mailboxes, without any matches, we would previously keep working and not say anything. Clients could interpret this silence as a broken connection at some point. We now send a "we're still searching" untagged OK responses with code INPROGRESS every 10 seconds while we're still searching, to prevent the client from closing the connection. We also send how many messages we've processed, and usually also how many we need to process in grand total. Clients can use this to show a progress bar.
This commit is contained in:
@ -131,6 +131,7 @@ var knownCodes = stringMap(
|
||||
// With parameters.
|
||||
"BADCHARSET", "CAPABILITY", "PERMANENTFLAGS", "UIDNEXT", "UIDVALIDITY", "UNSEEN", "APPENDUID", "COPYUID",
|
||||
"HIGHESTMODSEQ", "MODIFIED",
|
||||
"INPROGRESS", // ../rfc/9585:104
|
||||
)
|
||||
|
||||
func stringMap(l ...string) map[string]struct{} {
|
||||
@ -222,6 +223,30 @@ func (c *Conn) xrespCode() (string, CodeArg) {
|
||||
c.xspace()
|
||||
modified := c.xuidset()
|
||||
codeArg = CodeModified(NumSet{Ranges: modified})
|
||||
case "INPROGRESS":
|
||||
// ../rfc/9585:238
|
||||
var tag string
|
||||
var current, goal *uint32
|
||||
if c.space() {
|
||||
c.xtake("(")
|
||||
tag = c.xquoted()
|
||||
c.xspace()
|
||||
if c.peek('n') || c.peek('N') {
|
||||
c.xtake("nil")
|
||||
} else {
|
||||
v := c.xuint32()
|
||||
current = &v
|
||||
}
|
||||
c.xspace()
|
||||
if c.peek('n') || c.peek('N') {
|
||||
c.xtake("nil")
|
||||
} else {
|
||||
v := c.xnzuint32()
|
||||
goal = &v
|
||||
}
|
||||
c.xtake(")")
|
||||
}
|
||||
codeArg = CodeInProgress{tag, current, goal}
|
||||
}
|
||||
return W, codeArg
|
||||
}
|
||||
|
@ -159,6 +159,32 @@ func (c CodeHighestModSeq) CodeString() string {
|
||||
return fmt.Sprintf("HIGHESTMODSEQ %d", c)
|
||||
}
|
||||
|
||||
// "INPROGRESS" response code.
|
||||
type CodeInProgress struct {
|
||||
Tag string // Nil is empty string.
|
||||
Current *uint32
|
||||
Goal *uint32
|
||||
}
|
||||
|
||||
func (c CodeInProgress) CodeString() string {
|
||||
// ABNF allows inprogress-tag/state with all nil values. Doesn't seem useful enough
|
||||
// to keep track of.
|
||||
if c.Tag == "" && c.Current == nil && c.Goal == nil {
|
||||
return "INPROGRESS"
|
||||
}
|
||||
|
||||
// todo: quote tag properly
|
||||
current := "nil"
|
||||
goal := "nil"
|
||||
if c.Current != nil {
|
||||
current = fmt.Sprintf("%d", *c.Current)
|
||||
}
|
||||
if c.Goal != nil {
|
||||
goal = fmt.Sprintf("%d", *c.Goal)
|
||||
}
|
||||
return fmt.Sprintf("INPROGRESS (%q %s %s)", c.Tag, current, goal)
|
||||
}
|
||||
|
||||
// RespText represents a response line minus the leading tag.
|
||||
type RespText struct {
|
||||
Code string // The first word between [] after the status.
|
||||
|
Reference in New Issue
Block a user