Files
Simon Martin 4d5d7db82b imap: don't use list-status to discover the list of directories
This is a redo of 194365b9, that uses LIST (not LIST-STATUS) to properly
discover the list of mailboxes. The goal was to properly get the role of
each mailbox, and fully support the `restrict-deletes` functionality
(incidentally it also fixes mailbox listing for Strato); it was lost via
8a151bac.

This patch reinstates this and then uses LIST-STATUS (if it's supported)
to get each mailbox's UIDVALIDITY.

Fixes: 8a151bac ("models: add validity identifier to Directory structs")
Reported-by: post <post@sonnenkinder.org>
Signed-off-by: Simon Martin <simon@nasilyan.com>
Acked-by: Robin Jarry <robin@jarry.cc>
2026-03-07 21:17:49 +01:00

163 lines
3.6 KiB
Go

package imap
import (
"fmt"
"strings"
"github.com/emersion/go-imap"
"git.sr.ht/~rjarry/aerc/lib/log"
"git.sr.ht/~rjarry/aerc/models"
"git.sr.ht/~rjarry/aerc/worker/types"
)
func (imapw *IMAPWorker) handleListDirectories(msg *types.ListDirectories) error {
imapw.worker.Tracef("Listing mailboxes")
var mailboxes []*imap.MailboxInfo
var statuses []*imap.MailboxStatus
var err error
mailboxes, err = imapw.listMailboxes()
if err != nil {
return err
}
if imapw.liststatus {
items := []imap.StatusItem{imap.StatusUidValidity}
_, statuses, err = imapw.listMailboxesStatus(items)
}
if err != nil {
return err
}
statusMap := make(map[string]*imap.MailboxStatus)
for _, status := range statuses {
statusMap[status.Name] = status
}
labels := make([]string, 0)
provider := imapw.config.provider
useLabels := provider == GMail || provider == Proton
for _, mbox := range mailboxes {
if !canOpen(mbox) {
continue
}
dir := &models.Directory{
Name: mbox.Name,
}
if status, ok := statusMap[mbox.Name]; ok && status.UidValidity != 0 {
dir.Uid = fmt.Sprintf("%d", status.UidValidity)
}
switch provider {
case GMail:
labels = append(labels, mbox.Name)
case Proton:
if after, ok := strings.CutPrefix(mbox.Name, "Labels/"); ok {
labels = append(labels, after)
}
}
for _, attr := range mbox.Attributes {
attr = strings.TrimPrefix(attr, "\\")
attr = strings.ToLower(attr)
if role, ok := models.Roles[attr]; ok {
dir.Role = role
}
}
if mbox.Name == "INBOX" {
dir.Role = models.InboxRole
}
imapw.worker.PostMessage(&types.Directory{
Message: types.RespondTo(msg),
Dir: dir,
}, nil)
}
if useLabels {
imapw.worker.Debugf("Available labels: %s", labels)
imapw.worker.PostMessage(&types.LabelList{Labels: labels}, nil)
}
return nil
}
func (imapw *IMAPWorker) listMailboxes() ([]*imap.MailboxInfo, error) {
ch := make(chan *imap.MailboxInfo)
done := make(chan []*imap.MailboxInfo)
go func() {
defer log.PanicHandler()
var list []*imap.MailboxInfo
for mbox := range ch {
list = append(list, mbox)
}
done <- list
}()
err := imapw.client.List("", "*", ch)
return <-done, err
}
func (imapw *IMAPWorker) listMailboxesStatus(
items []imap.StatusItem,
) ([]*imap.MailboxInfo, []*imap.MailboxStatus, error) {
ch := make(chan *imap.MailboxInfo)
done := make(chan []*imap.MailboxInfo)
go func() {
defer log.PanicHandler()
var list []*imap.MailboxInfo
for mbox := range ch {
list = append(list, mbox)
}
done <- list
}()
statuses, err := imapw.client.liststatus.ListStatus("", "*", items, ch)
return <-done, statuses, err
}
const NonExistentAttr = "\\NonExistent"
func canOpen(mbox *imap.MailboxInfo) bool {
for _, attr := range mbox.Attributes {
if attr == imap.NoSelectAttr ||
attr == NonExistentAttr {
return false
}
}
return true
}
func (imapw *IMAPWorker) handleSearchDirectory(msg *types.SearchDirectory) error {
imapw.worker.Tracef("Executing search")
if msg.Context().Err() != nil {
return msg.Context().Err()
}
if err := imapw.ensureSelected(msg.Directory); err != nil {
return err
}
// Try Gmail X-GM-EXT-1 search first if available
if imapw.caps.Has("X-GM-EXT-1") && imapw.handleGmailSearch(msg) {
return nil
}
criteria := translateSearch(msg.Criteria)
uids, err := imapw.client.UidSearch(criteria)
if err != nil {
return err
}
if msg.Context().Err() != nil {
return msg.Context().Err()
}
imapw.worker.PostMessage(&types.SearchResults{
Message: types.RespondTo(msg),
Directory: msg.Directory,
Criteria: msg.Criteria,
Uids: imapw.Uint32ToUidList(uids),
}, nil)
return nil
}