diff options
-rw-r--r-- | doc/aerc-accounts.5.scd | 5 | ||||
-rw-r--r-- | worker/middleware/foldermapper.go | 45 | ||||
-rw-r--r-- | worker/middleware/foldermapper_test.go | 103 |
3 files changed, 142 insertions, 11 deletions
diff --git a/doc/aerc-accounts.5.scd b/doc/aerc-accounts.5.scd index 0cf7071b..6fe558cd 100644 --- a/doc/aerc-accounts.5.scd +++ b/doc/aerc-accounts.5.scd @@ -105,6 +105,11 @@ Note that many of these configuration options are written for you, such as G = [Gmail]\* ``` + Remove a prefix for all subfolders: + ``` + * = [Gmail]/\* + ``` + Remap all subfolders and avoid a folder collision: ``` Archive/existing = Archive\* diff --git a/worker/middleware/foldermapper.go b/worker/middleware/foldermapper.go index c78833a2..ee57b9c8 100644 --- a/worker/middleware/foldermapper.go +++ b/worker/middleware/foldermapper.go @@ -1,6 +1,7 @@ package middleware import ( + "fmt" "strings" "sync" @@ -28,7 +29,10 @@ func NewFolderMapper(base types.WorkerInteractor, mapping map[string]string, func (f *folderMapper) incoming(msg types.WorkerMessage, dir string) string { f.Lock() defer f.Unlock() - mapped := f.table[dir] + mapped, ok := f.table[dir] + if !ok { + return dir + } return mapped } @@ -52,13 +56,16 @@ func (f *folderMapper) store(s string) { f.Tracef("store display folder '%s' to '%s'", display, s) } -func (f *folderMapper) create(s string) string { +func (f *folderMapper) create(s string) (string, error) { f.Lock() defer f.Unlock() - backend := f.fm.Create(s) + 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 + return backend, nil } func (f *folderMapper) ProcessAction(msg types.WorkerMessage) types.WorkerMessage { @@ -74,7 +81,11 @@ func (f *folderMapper) ProcessAction(msg types.WorkerMessage) types.WorkerMessag case *types.MoveMessages: msg.Destination = f.incoming(msg, msg.Destination) case *types.CreateDirectory: - msg.Directory = f.create(msg.Directory) + 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: @@ -105,6 +116,10 @@ func (f *folderMapper) PostMessage(msg types.WorkerMessage, cb func(m types.Work case *types.OpenDirectory: 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) @@ -130,23 +145,31 @@ func (f *folderMap) Apply(s string) string { strict = false } if (strings.HasPrefix(s, v) && !strict) || (s == v && strict) { - s = k + strings.TrimPrefix(s, v) + 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 } -// Create reverses the mapping of a new folder name -func (f *folderMap) Create(s string) string { +// createFolder reverses the mapping of a new folder name +func createFolder(table map[string]string, s string) string { max, key := 0, "" - for k := range f.mapping { + for k := range table { if strings.HasPrefix(s, k) && len(k) > max { max, key = len(k), k } } if max > 0 && key != "" { - s = strings.TrimSuffix(f.mapping[key], "*") + - strings.TrimPrefix(s, key) + s = table[key] + strings.TrimPrefix(s, key) } return s } diff --git a/worker/middleware/foldermapper_test.go b/worker/middleware/foldermapper_test.go new file mode 100644 index 00000000..0b4f6dba --- /dev/null +++ b/worker/middleware/foldermapper_test.go @@ -0,0 +1,103 @@ +package middleware + +import ( + "reflect" + "testing" +) + +func TestFolderMap_Apply(t *testing.T) { + tests := []struct { + name string + mapping map[string]string + order []string + input []string + want []string + }{ + { + name: "strict single folder mapping", + mapping: map[string]string{"Drafts": "INBOX/Drafts"}, + order: []string{"Drafts"}, + input: []string{"INBOX/Drafts"}, + want: []string{"Drafts"}, + }, + { + name: "prefix mapping with * suffix", + mapping: map[string]string{"Prefix/": "INBOX/*"}, + order: []string{"Prefix/"}, + input: []string{"INBOX", "INBOX/Test1", "INBOX/Test2", "Archive"}, + want: []string{"INBOX", "Prefix/Test1", "Prefix/Test2", "Archive"}, + }, + { + name: "remove prefix with * in key", + mapping: map[string]string{"*": "INBOX/*"}, + order: []string{"*"}, + input: []string{"INBOX", "INBOX/Test1", "INBOX/Test2", "Archive"}, + want: []string{"INBOX", "Test1", "Test2", "Archive"}, + }, + { + name: "remove two prefixes with * in keys", + mapping: map[string]string{ + "*": "INBOX/*", + "**": "PROJECT/*", + }, + order: []string{"*", "**"}, + input: []string{"INBOX", "INBOX/Test1", "INBOX/Test2", "Archive", "PROJECT/sub1", "PROJECT/sub2"}, + want: []string{"INBOX", "Test1", "Test2", "Archive", "sub1", "sub2"}, + }, + { + name: "multiple, sequential mappings", + mapping: map[string]string{ + "Archive/existing": "Archive*", + "Archive": "Archivum*", + }, + order: []string{"Archive/existing", "Archive"}, + input: []string{"Archive", "Archive/sub", "Archivum", "Archivum/year1"}, + want: []string{"Archive/existing", "Archive/existing/sub", "Archive", "Archive/year1"}, + }, + } + + for i, test := range tests { + fm := &folderMap{ + mapping: test.mapping, + order: test.order, + } + var result []string + for _, in := range test.input { + result = append(result, fm.Apply(in)) + } + if !reflect.DeepEqual(result, test.want) { + t.Errorf("test (%d: %s) failed: want '%v' but got '%v'", + i, test.name, test.want, result) + } + } +} + +func TestFolderMap_createFolder(t *testing.T) { + tests := []struct { + name string + table map[string]string + input string + want string + }{ + { + name: "create normal folder", + table: map[string]string{"Drafts": "INBOX/Drafts"}, + input: "INBOX/Drafts2", + want: "INBOX/Drafts2", + }, + { + name: "create mapped folder", + table: map[string]string{"Drafts": "INBOX/Drafts"}, + input: "Drafts/Sub", + want: "INBOX/Drafts/Sub", + }, + } + + for i, test := range tests { + result := createFolder(test.table, test.input) + if result != test.want { + t.Errorf("test (%d: %s) failed: want '%v' but got '%v'", + i, test.name, test.want, result) + } + } +} |