imapserver: fix storing previews when requested over imap and they are missing from the database

found while testing.
This commit is contained in:
Mechiel Lukkien 2025-03-29 20:13:10 +01:00
parent 6ab31c15b7
commit 3ac38aacca
No known key found for this signature in database
5 changed files with 41 additions and 14 deletions

View File

@ -143,7 +143,7 @@ func (c *conn) cmdxFetch(isUID bool, tag, cmdstr string, p *parser) {
var uids []store.UID var uids []store.UID
var vanishedUIDs []store.UID var vanishedUIDs []store.UID
cmd := &fetchCmd{conn: c, isUID: isUID, hasChangedSince: haveChangedSince} cmd := &fetchCmd{conn: c, isUID: isUID, hasChangedSince: haveChangedSince, newPreviews: map[store.UID]string{}}
defer func() { defer func() {
if cmd.rtx == nil { if cmd.rtx == nil {

View File

@ -444,6 +444,42 @@ Content-Transfer-Encoding: Quoted-printable
tc.client.Unselect() tc.client.Unselect()
tc.client.Examine("inbox") tc.client.Examine("inbox")
// Preview
preview := "Hello Joe, do you think we can meet at 3:30 tomorrow?"
tc.transactf("ok", "fetch 1 preview")
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, imapclient.FetchPreview{Preview: &preview}}})
tc.transactf("ok", "fetch 1 preview (lazy)")
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, imapclient.FetchPreview{Preview: &preview}}})
// On-demand preview and saving on first request.
err = tc.account.DB.Write(ctxbg, func(tx *bstore.Tx) error {
m := store.Message{ID: 1}
err := tx.Get(&m)
tcheck(t, err, "get message")
if m.UID != 1 {
t.Fatalf("uid %d instead of 1", m.UID)
}
m.Preview = nil
err = tx.Update(&m)
tcheck(t, err, "remove preview from message")
return nil
})
tcheck(t, err, "remove preview from database")
tc.transactf("ok", "fetch 1 preview")
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, imapclient.FetchPreview{Preview: &preview}}})
m := store.Message{ID: 1}
err = tc.account.DB.Get(ctxbg, &m)
tcheck(t, err, "get message")
if m.Preview == nil {
t.Fatalf("preview missing")
} else if *m.Preview != preview+"\n" {
t.Fatalf("got preview %q, expected %q", *m.Preview, preview+"\n")
}
tc.transactf("bad", "fetch 1 preview (bogus)")
// Start a second session. Use it to remove the message. First session should still // Start a second session. Use it to remove the message. First session should still
// be able to access the messages. // be able to access the messages.
tc2 := startNoSwitchboard(t) tc2 := startNoSwitchboard(t)
@ -466,15 +502,5 @@ Content-Transfer-Encoding: Quoted-printable
tc.transactf("ok", "fetch 1 rfc822") tc.transactf("ok", "fetch 1 rfc822")
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfc1}}) tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfc1}})
// Preview
preview := "Hello Joe, do you think we can meet at 3:30 tomorrow?"
tc.transactf("ok", "fetch 1 preview")
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, imapclient.FetchPreview{Preview: &preview}}})
tc.transactf("ok", "fetch 1 preview (lazy)")
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, imapclient.FetchPreview{Preview: &preview}}})
tc.transactf("bad", "fetch 1 preview (bogus)")
tc.client.Logout() tc.client.Logout()
} }

View File

@ -569,7 +569,8 @@ type Message struct {
// through the IMAP fetch attribute "PREVIEW" (without "LAZY")), and stored with // through the IMAP fetch attribute "PREVIEW" (without "LAZY")), and stored with
// the message at that time. // the message at that time.
// The preview is at most 256 characters (can be more bytes), with detected quoted // The preview is at most 256 characters (can be more bytes), with detected quoted
// text replaced with "[..."]. // text replaced with "[...]". Previews typically end with a newline, callers may
// want to strip whitespace.
Preview *string Preview *string
// ParsedBuf message structure. Currently saved as JSON of message.Part because bstore // ParsedBuf message structure. Currently saved as JSON of message.Part because bstore

View File

@ -2608,7 +2608,7 @@
}, },
{ {
"Name": "Preview", "Name": "Preview",
"Docs": "If non-nil, a preview of the message based on text and/or html parts of the message. Used in the webmail and IMAP PREVIEW extension. If non-nil, it is empty if no preview could be created, or the message has not textual content or couldn't be parsed. Previews are typically created when delivering a message, but not when importing messages, for speed. Previews are generated on first request (in the webmail, or through the IMAP fetch attribute \"PREVIEW\" (without \"LAZY\")), and stored with the message at that time. The preview is at most 256 characters (can be more bytes), with detected quoted text replaced with \"[...\"].", "Docs": "If non-nil, a preview of the message based on text and/or html parts of the message. Used in the webmail and IMAP PREVIEW extension. If non-nil, it is empty if no preview could be created, or the message has not textual content or couldn't be parsed. Previews are typically created when delivering a message, but not when importing messages, for speed. Previews are generated on first request (in the webmail, or through the IMAP fetch attribute \"PREVIEW\" (without \"LAZY\")), and stored with the message at that time. The preview is at most 256 characters (can be more bytes), with detected quoted text replaced with \"[...]\".",
"Typewords": [ "Typewords": [
"nullable", "nullable",
"string" "string"

View File

@ -377,7 +377,7 @@ export interface Message {
Size: number Size: number
TrainedJunk?: boolean | null // If nil, no training done yet. Otherwise, true is trained as junk, false trained as nonjunk. TrainedJunk?: boolean | null // If nil, no training done yet. Otherwise, true is trained as junk, false trained as nonjunk.
MsgPrefix?: string | null // Typically holds received headers and/or header separator. MsgPrefix?: string | null // Typically holds received headers and/or header separator.
Preview?: string | null // If non-nil, a preview of the message based on text and/or html parts of the message. Used in the webmail and IMAP PREVIEW extension. If non-nil, it is empty if no preview could be created, or the message has not textual content or couldn't be parsed. Previews are typically created when delivering a message, but not when importing messages, for speed. Previews are generated on first request (in the webmail, or through the IMAP fetch attribute "PREVIEW" (without "LAZY")), and stored with the message at that time. The preview is at most 256 characters (can be more bytes), with detected quoted text replaced with "[..."]. Preview?: string | null // If non-nil, a preview of the message based on text and/or html parts of the message. Used in the webmail and IMAP PREVIEW extension. If non-nil, it is empty if no preview could be created, or the message has not textual content or couldn't be parsed. Previews are typically created when delivering a message, but not when importing messages, for speed. Previews are generated on first request (in the webmail, or through the IMAP fetch attribute "PREVIEW" (without "LAZY")), and stored with the message at that time. The preview is at most 256 characters (can be more bytes), with detected quoted text replaced with "[...]".
ParsedBuf?: string | null // ParsedBuf message structure. Currently saved as JSON of message.Part because bstore cannot yet store recursive types. Created when first needed, and saved in the database. todo: once replaced with non-json storage, remove date fixup in ../message/part.go. ParsedBuf?: string | null // ParsedBuf message structure. Currently saved as JSON of message.Part because bstore cannot yet store recursive types. Created when first needed, and saved in the database. todo: once replaced with non-json storage, remove date fixup in ../message/part.go.
} }