~rjarry/aerc

2040fc1885ca4b4bea07d91e9fc6a0aaebfe7133 — Moritz Poldrack 4 months ago 0eaf05d
imap: use delimiter from server

To accommodate servers that use a delimiter other than "/" ("." being a
common alternative), the delimiter is fetched from the server when
connecting.

Signed-off-by: Moritz Poldrack <git@moritz.sh>
Acked-by: Robin Jarry <robin@jarry.cc>
M CHANGELOG.md => CHANGELOG.md +1 -0
@@ 10,6 10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Colorize can style diff chunk function names with `diff_chunk_func`.
- Warn before sending emails with an empty subject with `empty-subject-warning`
  in `aerc.conf`.
- IMAP now uses the delimiter advertised by the server

## [0.15.2](https://git.sr.ht/~rjarry/aerc/refs/0.15.2) - 2023-05-11


M widgets/dirlist.go => widgets/dirlist.go +1 -2
@@ 4,7 4,6 @@ import (
	"bytes"
	"context"
	"math"
	"os"
	"regexp"
	"sort"
	"time"


@@ 79,7 78,7 @@ func NewDirectoryList(acctConf *config.AccountConfig,
	dirlist.spinner.Start()

	if uiConf.DirListTree {
		return NewDirectoryTree(dirlist, string(os.PathSeparator))
		return NewDirectoryTree(dirlist)
	}

	return dirlist

M widgets/dirtree.go => widgets/dirtree.go +9 -11
@@ 22,16 22,14 @@ type DirectoryTree struct {
	listIdx int
	list    []*types.Thread

	pathSeparator string
	treeDirs      []string
	treeDirs []string
}

func NewDirectoryTree(dirlist *DirectoryList, pathSeparator string) DirectoryLister {
func NewDirectoryTree(dirlist *DirectoryList) DirectoryLister {
	dt := &DirectoryTree{
		DirectoryList: dirlist,
		listIdx:       -1,
		list:          make([]*types.Thread, 0),
		pathSeparator: pathSeparator,
	}
	return dt
}


@@ 274,7 272,7 @@ func (dt *DirectoryTree) countVisible(list []*types.Thread) (n int) {
}

func (dt *DirectoryTree) displayText(node *types.Thread) string {
	elems := strings.Split(dt.treeDirs[getAnyUid(node)], dt.pathSeparator)
	elems := strings.Split(dt.treeDirs[getAnyUid(node)], dt.DirectoryList.worker.PathSeparator())
	return fmt.Sprintf("%s%s%s", threadPrefix(node, false, false), getFlag(node), elems[countLevels(node)])
}



@@ 301,12 299,12 @@ func (dt *DirectoryTree) hiddenDirectories() map[string]bool {
	hidden := make(map[string]bool, 0)
	for _, node := range dt.list {
		if node.Hidden && node.FirstChild != nil {
			elems := strings.Split(dt.treeDirs[getAnyUid(node)], dt.pathSeparator)
			elems := strings.Split(dt.treeDirs[getAnyUid(node)], dt.DirectoryList.worker.PathSeparator())
			if levels := countLevels(node); levels < len(elems) {
				if node.FirstChild != nil && (levels+1) < len(elems) {
					levels += 1
				}
				if dirStr := strings.Join(elems[:levels], dt.pathSeparator); dirStr != "" {
				if dirStr := strings.Join(elems[:levels], dt.DirectoryList.worker.PathSeparator()); dirStr != "" {
					hidden[dirStr] = true
				}
			}


@@ 317,12 315,12 @@ func (dt *DirectoryTree) hiddenDirectories() map[string]bool {

func (dt *DirectoryTree) setHiddenDirectories(hiddenDirs map[string]bool) {
	for _, node := range dt.list {
		elems := strings.Split(dt.treeDirs[getAnyUid(node)], dt.pathSeparator)
		elems := strings.Split(dt.treeDirs[getAnyUid(node)], dt.DirectoryList.worker.PathSeparator())
		if levels := countLevels(node); levels < len(elems) {
			if node.FirstChild != nil && (levels+1) < len(elems) {
				levels += 1
			}
			strDir := strings.Join(elems[:levels], dt.pathSeparator)
			strDir := strings.Join(elems[:levels], dt.DirectoryList.worker.PathSeparator())
			if hidden, ok := hiddenDirs[strDir]; hidden && ok {
				node.Hidden = true
			}


@@ 340,7 338,7 @@ func (dt *DirectoryTree) buildTree() {

	sTree := make([][]string, 0)
	for i, dir := range dt.dirs {
		elems := strings.Split(dir, dt.pathSeparator)
		elems := strings.Split(dir, dt.DirectoryList.worker.PathSeparator())
		if len(elems) == 0 {
			continue
		}


@@ 364,7 362,7 @@ func (dt *DirectoryTree) buildTree() {
	// folders-sort
	if dt.DirectoryList.acctConf.EnableFoldersSort {
		toStr := func(t *types.Thread) string {
			if elems := strings.Split(dt.treeDirs[getAnyUid(t)], dt.pathSeparator); len(elems) > 0 {
			if elems := strings.Split(dt.treeDirs[getAnyUid(t)], dt.DirectoryList.worker.PathSeparator()); len(elems) > 0 {
				return elems[0]
			}
			return ""

M worker/imap/connect.go => worker/imap/connect.go +11 -0
@@ 94,6 94,17 @@ func (w *IMAPWorker) connect() (*client.Client, error) {
		return nil, err
	}

	info := make(chan *imap.MailboxInfo, 1)
	if err := c.List("", "", info); err != nil {
		return nil, fmt.Errorf("failed to retrieve delimiter: %w", err)
	}
	mailboxinfo := <-info
	w.delimiter = mailboxinfo.Delimiter
	if w.delimiter == "" {
		// just in case some implementation does not follow standards
		w.delimiter = "/"
	}

	return c, nil
}


M worker/imap/worker.go => worker/imap/worker.go +13 -5
@@ 61,11 61,12 @@ type imapConfig struct {
type IMAPWorker struct {
	config imapConfig

	client   *imapClient
	selected *imap.MailboxStatus
	updates  chan client.Update
	worker   *types.Worker
	seqMap   SeqMap
	client    *imapClient
	selected  *imap.MailboxStatus
	updates   chan client.Update
	worker    *types.Worker
	seqMap    SeqMap
	delimiter string

	idler    *idler
	observer *observer


@@ 311,3 312,10 @@ func (w *IMAPWorker) Run() {
func (w *IMAPWorker) Capabilities() *models.Capabilities {
	return w.caps
}

func (w *IMAPWorker) PathSeparator() string {
	if w.delimiter == "" {
		return "/"
	}
	return w.delimiter
}

M worker/maildir/worker.go => worker/maildir/worker.go +7 -0
@@ 108,6 108,13 @@ func (w *Worker) Capabilities() *models.Capabilities {
	return w.capabilities
}

func (w *Worker) PathSeparator() string {
	if w.maildirpp {
		return "."
	}
	return string(os.PathSeparator)
}

func (w *Worker) handleAction(action types.WorkerMessage) {
	msg := w.worker.ProcessAction(action)
	switch msg := msg.(type) {

M worker/mbox/worker.go => worker/mbox/worker.go +4 -0
@@ 383,6 383,10 @@ func (w *mboxWorker) Capabilities() *models.Capabilities {
	return w.capabilities
}

func (w *mboxWorker) PathSeparator() string {
	return "/"
}

func filterUids(folder *container, uids []uint32, args []string) ([]uint32, error) {
	criteria, err := lib.GetSearchCriteria(args)
	if err != nil {

M worker/notmuch/worker.go => worker/notmuch/worker.go +7 -0
@@ 109,6 109,13 @@ func (w *worker) Capabilities() *models.Capabilities {
	return w.capabilities
}

func (w *worker) PathSeparator() string {
	// make it configurable?
	// <rockorager> You can use those in query maps to force a tree
	// <rockorager> Might be nice to be configurable? I see some notmuch people namespace with "::"
	return "/"
}

func (w *worker) done(msg types.WorkerMessage) {
	w.w.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
}

M worker/types/worker.go => worker/types/worker.go +5 -0
@@ 15,6 15,7 @@ var lastId int64 = 1 // access via atomic
type Backend interface {
	Run()
	Capabilities() *models.Capabilities
	PathSeparator() string
}

type Worker struct {


@@ 179,3 180,7 @@ func (worker *Worker) PostMessageInfoError(msg WorkerMessage, uid uint32, err er
		Message: RespondTo(msg),
	}, nil)
}

func (worker *Worker) PathSeparator() string {
	return worker.Backend.PathSeparator()
}