mirror of
https://git.sr.ht/~rjarry/aerc
synced 2026-10-29 12:15:10 +01:00
This is a redo of194365b9, 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 via8a151bac. 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>
163 lines
3.6 KiB
Go
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
|
|
}
|