From dc2a2c2dfd6dc327fe40fbf2da922ef6c3d520be Mon Sep 17 00:00:00 2001 From: y0ast Date: Fri, 12 Nov 2021 18:12:02 +0100 Subject: messages: allow displaying email threads Display threads in the message list. For now, only supported by the notmuch backend and on IMAP when the server supports the THREAD extension. Setting threading-enable=true is global and will cause the message list to be empty with maildir:// accounts. Co-authored-by: Kevin Kuehler Co-authored-by: Reto Brunner Signed-off-by: Robin Jarry --- worker/imap/open.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++ worker/imap/worker.go | 7 ++++-- 2 files changed, 68 insertions(+), 2 deletions(-) (limited to 'worker/imap') diff --git a/worker/imap/open.go b/worker/imap/open.go index 4b4e943e..28495731 100644 --- a/worker/imap/open.go +++ b/worker/imap/open.go @@ -1,6 +1,8 @@ package imap import ( + "sort" + "github.com/emersion/go-imap" sortthread "github.com/emersion/go-imap-sortthread" @@ -92,3 +94,64 @@ func translateSortCriterions( } return result } + +func (imapw *IMAPWorker) handleDirectoryThreaded( + msg *types.FetchDirectoryThreaded) { + imapw.worker.Logger.Printf("Fetching threaded UID list") + + seqSet := &imap.SeqSet{} + seqSet.AddRange(1, imapw.selected.Messages) + threads, err := imapw.client.thread.UidThread(sortthread.References, + &imap.SearchCriteria{SeqNum: seqSet}) + if err != nil { + imapw.worker.PostMessage(&types.Error{ + Message: types.RespondTo(msg), + Error: err, + }, nil) + } else { + aercThreads, count := convertThreads(threads, nil) + sort.Sort(types.ByUID(aercThreads)) + imapw.worker.Logger.Printf("Found %d threaded messages", count) + imapw.seqMap = make([]uint32, count) + imapw.worker.PostMessage(&types.DirectoryThreaded{ + Message: types.RespondTo(msg), + Threads: aercThreads, + }, nil) + imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil) + } +} + +func convertThreads(threads []*sortthread.Thread, parent *types.Thread) ([]*types.Thread, int) { + if threads == nil { + return nil, 0 + } + conv := make([]*types.Thread, len(threads)) + count := 0 + + for i := 0; i < len(threads); i++ { + t := threads[i] + conv[i] = &types.Thread{ + Uid: t.Id, + } + + // Set the first child node + children, childCount := convertThreads(t.Children, conv[i]) + if len(children) > 0 { + conv[i].FirstChild = children[0] + } + + // Set the parent node + if parent != nil { + conv[i].Parent = parent + + // elements of threads are siblings + if i > 0 { + conv[i].PrevSibling = conv[i-1] + conv[i-1].NextSibling = conv[i] + } + } + + count += childCount + 1 + } + return conv, count +} diff --git a/worker/imap/worker.go b/worker/imap/worker.go index cd525369..2c0e6a60 100644 --- a/worker/imap/worker.go +++ b/worker/imap/worker.go @@ -26,7 +26,8 @@ var errUnsupported = fmt.Errorf("unsupported command") type imapClient struct { *client.Client - sort *sortthread.SortClient + thread *sortthread.ThreadClient + sort *sortthread.SortClient } type IMAPWorker struct { @@ -158,7 +159,7 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error { } c.Updates = w.updates - w.client = &imapClient{c, sortthread.NewSortClient(c)} + w.client = &imapClient{c, sortthread.NewThreadClient(c), sortthread.NewSortClient(c)} w.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil) case *types.Disconnect: if w.client == nil { @@ -175,6 +176,8 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error { w.handleOpenDirectory(msg) case *types.FetchDirectoryContents: w.handleFetchDirectoryContents(msg) + case *types.FetchDirectoryThreaded: + w.handleDirectoryThreaded(msg) case *types.CreateDirectory: w.handleCreateDirectory(msg) case *types.RemoveDirectory: -- cgit