[PATCH offmap] config: implement folder mapping
Export this patch
Implement folder mapping from remote folder name to local folder name.
Add test for mappings, update docs.
Maps must be of the form:
REMOTE = LOCAL
And it is recommended in the docs that the key should be quoted as well,
specifically when the remote uses '.' delimiters, as this will break
toml parsing. Quoted keys with '.' are parsed as raw strings and will
not break toml.
Example:
"INBOX.test" = "Inbox/test" // Works
INBOX.test = "Inbox/test" // Doesn't work
Here Inbox works, all mail does not unfortunately.
[formsense.map]
INBOX = "Inbox"
"[GMAIL]/All Mail" = "Archive"
I just tried this on my gmail account. On mine, the folder shows up at
"[Gmail]/All Mail". Your caps need to match how it is, we aren't doing any
string conversions for the comparison. Check the folders that were made...I'm
guessing it's the same casing as mine, in which case you can fix it by just
updating your key.
Maybe we should compare on a lower case basis? The spec says the only strict
case-sensitive name is INBOX, all others are implementation defined.
Jeeez, yes, you are right, and now it works :)
--
+36305425054
bence.ferdinandy.com
--
Tim
Thanks,
Bence
--
+36305425054
bence.ferdinandy.com
Implements: https://todo.sr.ht/~rockorager/offmap/19
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
config.go | 15 ++++++++ -------
doc/offmap.5.scd | 17 +++++++++++++++++
imap/imap.go | 26 ++++++++++++++++++++++ ----
imap/imap_test.go | 33 +++++++++++++++++++++++++++++++++
maildir/maildir.go | 2 ++
5 files changed, 82 insertions(+), 11 deletions(-)
create mode 100644 imap/imap_test.go
diff --git a/config.go b/config.go
index e6fc2016d5d0..2c6bd07d4cc8 100644
--- a/config.go
+++ b/config.go
@@ -11,13 +11,14 @@ import (
type Config struct {
Name string
- Maildir string `toml:"maildir"`
- Host string `toml:"host"`
- Username string `toml:"username"`
- Password string `toml:"password"`
- PasswordCmd string `toml:"password-cmd"`
- AuthMech string `toml:"auth-mech"`
- MaxConnections int `toml:"max-connections"`
+ Maildir string `toml:"maildir"`
+ Host string `toml:"host"`
+ Username string `toml:"username"`
+ Password string `toml:"password"`
+ PasswordCmd string `toml:"password-cmd"`
+ AuthMech string `toml:"auth-mech"`
+ MaxConnections int `toml:"max-connections"`
+ Map map[string]string `toml:"map"`
}
func LoadConfig(accts []string) ([]*Config, error) {
diff --git a/doc/offmap.5.scd b/doc/offmap.5.scd
index da4ee3b7a0b0..e715439ecef0 100644
--- a/doc/offmap.5.scd
+++ b/doc/offmap.5.scd
@@ -68,3 +68,20 @@ auth-mech = "oauth2"
effect for IMAP servers who *do not* support LIST-STATUS.
Default: 1
+
+ *map*
+ A table of remote mailbox names mapped to local mailbox names. The key
+ should always be the remote mailbox name, and is recommended to be in
+ quotes. IMAP servers with '.' delimiters require quotes. Maps will apply
+ to any children of the mapped mailbox.
+
+ Example:
+ ```
+ [account-name.map]
+ "INBOX" = "inbox"
+ "Lists/offmap" = "offmap"
+ ```
+
+ In the above example, a child of "INBOX" would be mapped from
+ "INBOX/child" to "inbox/child". However, siblings of "Lists/offmap"
+ would not be mapped, and would still exist under "Lists/*".
diff --git a/imap/imap.go b/imap/imap.go
index 791248b04615..db1f7a853097 100644
--- a/imap/imap.go
+++ b/imap/imap.go
@@ -292,10 +292,7 @@ func (s *Store) listMailboxes() ([]*offmap.Mailbox, error) {
// Process the statuses
for _, status := range statuses {
- local := status.Name
- if s.Delimiter() != "/" {
- local = strings.ReplaceAll(local, s.Delimiter(), "/")
- }
+ local := s.localName(status.Name)
mailbox := offmap.NewMailbox(status.UidValidity, local, status.Name)
mailbox.UIDNext = status.UidNext
if s.SupportCondstore() {
@@ -306,6 +303,27 @@ func (s *Store) listMailboxes() ([]*offmap.Mailbox, error) {
return s.mailboxes, nil
}
+ // Finds the local name by replacing delimiters and checking the configured map
+ func (s *Store) localName(remote string) string {
+ local := remote
+ nodes := strings.Split(remote, s.Delimiter())
+ // Find mapped paths by starting at most specific, and moving up the
+ // tree. The first mapped path breaks the loop, as this is the most
+ // specific mapping and the only one we will apply
+ for i := len(nodes); i > 0; i -= 1 {
+ path := strings.Join(nodes[:i], s.Delimiter())
+ mapped, ok := s.cfg.Map[path]
+ if ok {
+ local = strings.Replace(local, path, mapped, 1)
+ break
+ }
+ }
+ if s.Delimiter() != "/" {
+ local = strings.ReplaceAll(local, s.Delimiter(), "/")
+ }
+ return local
+ }
+
// Returns the delimiter used on the server
func (s *Store) Delimiter() string {
if s.delim == "" {
diff --git a/imap/imap_test.go b/imap/imap_test.go
new file mode 100644
index 000000000000..045bd0dd4c92
--- /dev/null
+++ b/imap/imap_test.go
@@ -0,0 +1,33 @@
+ package imap
+
+ import (
+ "testing"
+
+ "git.sr.ht/~rockorager/offmap"
+ "github.com/stretchr/testify/assert"
+ )
+
+ func TestLocalName(t *testing.T) {
+ cfg := &offmap.Config{}
+ cfg.Map = map[string]string{
+ "INBOX": "inbox",
+ "INBOX/child": "child",
+ }
+ store := Store{cfg: cfg}
+ store.delim = "/"
+ assert := assert.New(t)
+ assert.Equal("inbox", store.localName("INBOX"))
+ assert.Equal("child", store.localName("INBOX/child"))
+ assert.Equal("child/grandchild", store.localName("INBOX/child/grandchild"))
+ assert.Equal("inbox/sibling", store.localName("INBOX/sibling"))
+
+ store.delim = "."
+ cfg.Map = map[string]string{
+ "INBOX": "inbox",
+ "INBOX.child": "child",
+ }
+ assert.Equal("inbox", store.localName("INBOX"))
+ assert.Equal("child", store.localName("INBOX.child"))
+ assert.Equal("child/grandchild", store.localName("INBOX.child.grandchild"))
+ assert.Equal("inbox/sibling", store.localName("INBOX.sibling"))
+ }
diff --git a/maildir/maildir.go b/maildir/maildir.go
index a3ed66496a54..7743f40841d8 100644
--- a/maildir/maildir.go
+++ b/maildir/maildir.go
@@ -21,6 +21,7 @@ type Store struct {
state *offmap.State
// map of relpath to mailbox
mailboxes map[string]*offmap.Mailbox
+ dirmap map[string]string
}
func NewStore(cfg *offmap.Config) (*Store, error) {
@@ -35,6 +36,7 @@ func NewStore(cfg *offmap.Config) (*Store, error) {
if err != nil {
return nil, fmt.Errorf("maildir: error creating root: %v", err)
}
+ store.dirmap = cfg.Map
return store, nil
}
--
2.39.0
offmap/patches/.build.yml: FAILED in 45s
[config: implement folder mapping][0] from [Tim Culverhouse][1]
[0]: https://lists.sr.ht/~rockorager/offmap/patches/37788
[1]: mailto:tim@timculverhouse.com
✗ #912193 FAILED offmap/patches/.build.yml https://builds.sr.ht/~rockorager/job/912193