mirror of
https://git.sr.ht/~rjarry/aerc
synced 2026-03-02 18:23:33 +01:00
Add Directory and Filter fields to DirectoryContents, DirectoryThreaded, SearchResults, MessageInfo, and FullMessage response types. Workers now populate these fields so the UI knows which directory and filter context each response belongs to. This metadata allows the message store to correctly associate incoming messages with their source context, which is essential for the offline worker to cache messages by directory and for proper handling of concurrent operations across multiple folders. Signed-off-by: Robin Jarry <robin@jarry.cc> Reviewed-by: Simon Martin <simon@nasilyan.com>
246 lines
7.1 KiB
Go
246 lines
7.1 KiB
Go
package middleware
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"git.sr.ht/~rjarry/aerc/worker/types"
|
|
)
|
|
|
|
type folderMapper struct {
|
|
sync.Mutex
|
|
types.WorkerInteractor
|
|
fm folderMap
|
|
table map[string]string
|
|
}
|
|
|
|
func NewFolderMapper(base types.WorkerInteractor, mapping map[string]string,
|
|
order []string,
|
|
) types.WorkerInteractor {
|
|
base.Infof("loading worker middleware: foldermapper")
|
|
return &folderMapper{
|
|
WorkerInteractor: base,
|
|
fm: folderMap{mapping, order},
|
|
table: make(map[string]string),
|
|
}
|
|
}
|
|
|
|
func (f *folderMapper) Unwrap() types.WorkerInteractor {
|
|
return f.WorkerInteractor
|
|
}
|
|
|
|
func (f *folderMapper) incoming(msg types.WorkerMessage, dir string) string {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
mapped, ok := f.table[dir]
|
|
if !ok {
|
|
return dir
|
|
}
|
|
return mapped
|
|
}
|
|
|
|
func (f *folderMapper) outgoing(msg types.WorkerMessage, dir string) string {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
for k, v := range f.table {
|
|
if v == dir {
|
|
mapped := k
|
|
return mapped
|
|
}
|
|
}
|
|
return dir
|
|
}
|
|
|
|
func (f *folderMapper) store(s string) {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
display := f.fm.Apply(s)
|
|
f.table[display] = s
|
|
f.Tracef("store display folder '%s' to '%s'", display, s)
|
|
}
|
|
|
|
func (f *folderMapper) create(s string) (string, error) {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
backend := createFolder(f.table, s)
|
|
if _, exists := f.table[s]; exists {
|
|
return s, fmt.Errorf("folder already exists: %s", s)
|
|
}
|
|
f.table[s] = backend
|
|
f.Tracef("create display folder '%s' as '%s'", s, backend)
|
|
return backend, nil
|
|
}
|
|
|
|
func (f *folderMapper) ProcessAction(msg types.WorkerMessage) types.WorkerMessage {
|
|
switch msg := msg.(type) {
|
|
case *types.CheckMail:
|
|
for i := range msg.Directories {
|
|
msg.Directories[i] = f.incoming(msg, msg.Directories[i])
|
|
}
|
|
case *types.CopyMessages:
|
|
msg.Source = f.incoming(msg, msg.Source)
|
|
msg.Destination = f.incoming(msg, msg.Destination)
|
|
case *types.AppendMessage:
|
|
msg.Destination = f.incoming(msg, msg.Destination)
|
|
case *types.MoveMessages:
|
|
msg.Source = f.incoming(msg, msg.Source)
|
|
msg.Destination = f.incoming(msg, msg.Destination)
|
|
case *types.CreateDirectory:
|
|
var err error
|
|
msg.Directory, err = f.create(msg.Directory)
|
|
if err != nil {
|
|
f.Errorf("error creating new directory: %v", err)
|
|
}
|
|
case *types.RemoveDirectory:
|
|
msg.Directory = f.incoming(msg, msg.Directory)
|
|
case *types.OpenDirectory:
|
|
msg.Directory = f.incoming(msg, msg.Directory)
|
|
case *types.FetchDirectoryContents:
|
|
msg.Directory = f.incoming(msg, msg.Directory)
|
|
case *types.FetchDirectoryThreaded:
|
|
msg.Directory = f.incoming(msg, msg.Directory)
|
|
case *types.SearchDirectory:
|
|
msg.Directory = f.incoming(msg, msg.Directory)
|
|
case *types.FetchMessageHeaders:
|
|
msg.Directory = f.incoming(msg, msg.Directory)
|
|
case *types.FetchFullMessages:
|
|
msg.Directory = f.incoming(msg, msg.Directory)
|
|
case *types.FetchMessageBodyPart:
|
|
msg.Directory = f.incoming(msg, msg.Directory)
|
|
case *types.FetchMessageFlags:
|
|
msg.Directory = f.incoming(msg, msg.Directory)
|
|
case *types.DeleteMessages:
|
|
msg.Directory = f.incoming(msg, msg.Directory)
|
|
case *types.FlagMessages:
|
|
msg.Directory = f.incoming(msg, msg.Directory)
|
|
case *types.AnsweredMessages:
|
|
msg.Directory = f.incoming(msg, msg.Directory)
|
|
case *types.ForwardedMessages:
|
|
msg.Directory = f.incoming(msg, msg.Directory)
|
|
}
|
|
|
|
return f.WorkerInteractor.ProcessAction(msg)
|
|
}
|
|
|
|
func (f *folderMapper) PostMessage(msg types.WorkerMessage, cb func(m types.WorkerMessage)) {
|
|
switch msg := msg.(type) {
|
|
case *types.Done:
|
|
switch msg := msg.InResponseTo().(type) {
|
|
case *types.CheckMail:
|
|
for i := range msg.Directories {
|
|
msg.Directories[i] = f.outgoing(msg, msg.Directories[i])
|
|
}
|
|
case *types.CopyMessages:
|
|
msg.Source = f.outgoing(msg, msg.Source)
|
|
msg.Destination = f.outgoing(msg, msg.Destination)
|
|
case *types.AppendMessage:
|
|
msg.Destination = f.outgoing(msg, msg.Destination)
|
|
case *types.MoveMessages:
|
|
msg.Source = f.outgoing(msg, msg.Source)
|
|
msg.Destination = f.outgoing(msg, msg.Destination)
|
|
case *types.CreateDirectory:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.RemoveDirectory:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.OpenDirectory:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.FetchDirectoryContents:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.FetchDirectoryThreaded:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.SearchDirectory:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.FetchMessageHeaders:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.FetchFullMessages:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.FetchMessageBodyPart:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.FetchMessageFlags:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.DeleteMessages:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.FlagMessages:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.AnsweredMessages:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.ForwardedMessages:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
}
|
|
case *types.CheckMailDirectories:
|
|
for i := range msg.Directories {
|
|
msg.Directories[i] = f.outgoing(msg, msg.Directories[i])
|
|
}
|
|
case *types.Directory:
|
|
f.store(msg.Dir.Name)
|
|
msg.Dir.Name = f.outgoing(msg, msg.Dir.Name)
|
|
case *types.DirectoryInfo:
|
|
msg.Info.Name = f.outgoing(msg, msg.Info.Name)
|
|
case *types.MessagesMoved:
|
|
msg.Destination = f.outgoing(msg, msg.Destination)
|
|
case *types.MessagesCopied:
|
|
msg.Destination = f.outgoing(msg, msg.Destination)
|
|
case *types.RemoveDirectory:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.DirectoryContents:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.DirectoryThreaded:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.SearchResults:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
case *types.MessageInfo:
|
|
if msg.Info != nil {
|
|
msg.Info.Directory = f.outgoing(msg, msg.Info.Directory)
|
|
}
|
|
case *types.MessagesDeleted:
|
|
msg.Directory = f.outgoing(msg, msg.Directory)
|
|
}
|
|
f.WorkerInteractor.PostMessage(msg, cb)
|
|
}
|
|
|
|
// folderMap contains the mapping between the ui and backend folder names
|
|
type folderMap struct {
|
|
mapping map[string]string
|
|
order []string
|
|
}
|
|
|
|
// Apply applies the mapping from the folder map to the backend folder
|
|
func (f *folderMap) Apply(s string) string {
|
|
for _, k := range f.order {
|
|
v := f.mapping[k]
|
|
strict := true
|
|
if before, ok := strings.CutSuffix(v, "*"); ok {
|
|
v = before
|
|
strict = false
|
|
}
|
|
if (strings.HasPrefix(s, v) && !strict) || (s == v && strict) {
|
|
term := strings.TrimPrefix(s, v)
|
|
if strings.Contains(k, "*") && !strict {
|
|
prefix := k
|
|
for strings.Contains(prefix, "**") {
|
|
prefix = strings.ReplaceAll(prefix, "**", "*")
|
|
}
|
|
s = strings.Replace(prefix, "*", term, 1)
|
|
} else {
|
|
s = k + term
|
|
}
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// createFolder reverses the mapping of a new folder name
|
|
func createFolder(table map[string]string, s string) string {
|
|
max, key := 0, ""
|
|
for k := range table {
|
|
if strings.HasPrefix(s, k) && len(k) > max {
|
|
max, key = len(k), k
|
|
}
|
|
}
|
|
if max > 0 && key != "" {
|
|
s = table[key] + strings.TrimPrefix(s, key)
|
|
}
|
|
return s
|
|
}
|