diff options
Diffstat (limited to 'worker/jmap/cache')
-rw-r--r-- | worker/jmap/cache/blob.go | 45 | ||||
-rw-r--r-- | worker/jmap/cache/cache.go | 79 | ||||
-rw-r--r-- | worker/jmap/cache/email.go | 35 | ||||
-rw-r--r-- | worker/jmap/cache/folder_contents.go | 61 | ||||
-rw-r--r-- | worker/jmap/cache/gob.go | 35 | ||||
-rw-r--r-- | worker/jmap/cache/mailbox.go | 35 | ||||
-rw-r--r-- | worker/jmap/cache/mailbox_list.go | 32 | ||||
-rw-r--r-- | worker/jmap/cache/session.go | 32 | ||||
-rw-r--r-- | worker/jmap/cache/state.go | 30 |
9 files changed, 384 insertions, 0 deletions
diff --git a/worker/jmap/cache/blob.go b/worker/jmap/cache/blob.go new file mode 100644 index 00000000..2a239835 --- /dev/null +++ b/worker/jmap/cache/blob.go @@ -0,0 +1,45 @@ +package cache + +import ( + "os" + "path" + + "git.sr.ht/~rockorager/go-jmap" +) + +func (c *JMAPCache) GetBlob(id jmap.ID) ([]byte, error) { + fpath := c.blobPath(id) + if fpath == "" { + return nil, notfound + } + return os.ReadFile(fpath) +} + +func (c *JMAPCache) PutBlob(id jmap.ID, buf []byte) error { + fpath := c.blobPath(id) + if fpath == "" { + return nil + } + _ = os.MkdirAll(path.Dir(fpath), 0o700) + return os.WriteFile(fpath, buf, 0o600) +} + +func (c *JMAPCache) DeleteBlob(id jmap.ID) error { + fpath := c.blobPath(id) + if fpath == "" { + return nil + } + defer func() { + _ = os.Remove(path.Dir(fpath)) + }() + return os.Remove(fpath) +} + +func (c *JMAPCache) blobPath(id jmap.ID) string { + if c.blobsDir == "" { + return "" + } + name := string(id) + sub := name[len(name)-2:] + return path.Join(c.blobsDir, sub, name) +} diff --git a/worker/jmap/cache/cache.go b/worker/jmap/cache/cache.go new file mode 100644 index 00000000..ab264744 --- /dev/null +++ b/worker/jmap/cache/cache.go @@ -0,0 +1,79 @@ +package cache + +import ( + "errors" + "os" + "path" + + "github.com/mitchellh/go-homedir" + "github.com/syndtr/goleveldb/leveldb" +) + +type JMAPCache struct { + mem map[string][]byte + file *leveldb.DB + blobsDir string +} + +func NewJMAPCache(state, blobs bool, accountName string) (*JMAPCache, error) { + c := new(JMAPCache) + cacheDir, err := os.UserCacheDir() + if err != nil { + cacheDir, err = homedir.Expand("~/.cache") + if err != nil { + return nil, err + } + } + if state { + dir := path.Join(cacheDir, "aerc", accountName, "state") + _ = os.MkdirAll(dir, 0o700) + c.file, err = leveldb.OpenFile(dir, nil) + if err != nil { + return nil, err + } + } else { + c.mem = make(map[string][]byte) + } + if blobs { + c.blobsDir = path.Join(cacheDir, "aerc", accountName, "blobs") + } + return c, nil +} + +var notfound = errors.New("key not found") + +func (c *JMAPCache) get(key string) ([]byte, error) { + switch { + case c.file != nil: + return c.file.Get([]byte(key), nil) + case c.mem != nil: + value, ok := c.mem[key] + if !ok { + return nil, notfound + } + return value, nil + } + panic("jmap cache with no backend") +} + +func (c *JMAPCache) put(key string, value []byte) error { + switch { + case c.file != nil: + return c.file.Put([]byte(key), value, nil) + case c.mem != nil: + c.mem[key] = value + return nil + } + panic("jmap cache with no backend") +} + +func (c *JMAPCache) delete(key string) error { + switch { + case c.file != nil: + return c.file.Delete([]byte(key), nil) + case c.mem != nil: + delete(c.mem, key) + return nil + } + panic("jmap cache with no backend") +} diff --git a/worker/jmap/cache/email.go b/worker/jmap/cache/email.go new file mode 100644 index 00000000..52044281 --- /dev/null +++ b/worker/jmap/cache/email.go @@ -0,0 +1,35 @@ +package cache + +import ( + "git.sr.ht/~rockorager/go-jmap" + "git.sr.ht/~rockorager/go-jmap/mail/email" +) + +func (c *JMAPCache) GetEmail(id jmap.ID) (*email.Email, error) { + buf, err := c.get(emailey(id)) + if err != nil { + return nil, err + } + e := new(email.Email) + err = unmarshal(buf, e) + if err != nil { + return nil, err + } + return e, nil +} + +func (c *JMAPCache) PutEmail(id jmap.ID, e *email.Email) error { + buf, err := marshal(e) + if err != nil { + return err + } + return c.put(emailey(id), buf) +} + +func (c *JMAPCache) DeleteEmail(id jmap.ID) error { + return c.delete(emailey(id)) +} + +func emailey(id jmap.ID) string { + return "email/" + string(id) +} diff --git a/worker/jmap/cache/folder_contents.go b/worker/jmap/cache/folder_contents.go new file mode 100644 index 00000000..6c6a7d80 --- /dev/null +++ b/worker/jmap/cache/folder_contents.go @@ -0,0 +1,61 @@ +package cache + +import ( + "reflect" + + "git.sr.ht/~rockorager/go-jmap" + "git.sr.ht/~rockorager/go-jmap/mail/email" +) + +type FolderContents struct { + MailboxID jmap.ID + QueryState string + Filter *email.FilterCondition + Sort []*email.SortComparator + MessageIDs []jmap.ID +} + +func (c *JMAPCache) GetFolderContents(mailboxId jmap.ID) (*FolderContents, error) { + buf, err := c.get(folderContentsKey(mailboxId)) + if err != nil { + return nil, err + } + m := new(FolderContents) + err = unmarshal(buf, m) + if err != nil { + return nil, err + } + return m, nil +} + +func (c *JMAPCache) PutFolderContents(mailboxId jmap.ID, m *FolderContents) error { + buf, err := marshal(m) + if err != nil { + return err + } + return c.put(folderContentsKey(mailboxId), buf) +} + +func (c *JMAPCache) DeleteFolderContents(mailboxId jmap.ID) error { + return c.delete(folderContentsKey(mailboxId)) +} + +func folderContentsKey(mailboxId jmap.ID) string { + return "foldercontents/" + string(mailboxId) +} + +func (f *FolderContents) NeedsRefresh( + filter *email.FilterCondition, sort []*email.SortComparator, +) bool { + if f.QueryState == "" || f.Filter == nil || len(f.Sort) != len(sort) { + return true + } + + for i := 0; i < len(sort) && i < len(f.Sort); i++ { + if !reflect.DeepEqual(sort[i], f.Sort[i]) { + return true + } + } + + return !reflect.DeepEqual(filter, f.Filter) +} diff --git a/worker/jmap/cache/gob.go b/worker/jmap/cache/gob.go new file mode 100644 index 00000000..589bd954 --- /dev/null +++ b/worker/jmap/cache/gob.go @@ -0,0 +1,35 @@ +package cache + +import ( + "bytes" + "encoding/gob" + + "git.sr.ht/~rockorager/go-jmap" + "git.sr.ht/~rockorager/go-jmap/mail/email" + "git.sr.ht/~rockorager/go-jmap/mail/mailbox" +) + +type jmapObject interface { + *jmap.Session | + *email.Email | + *email.QueryResponse | + *mailbox.Mailbox | + *FolderContents | + *IDList +} + +func marshal[T jmapObject](obj T) ([]byte, error) { + buf := bytes.NewBuffer(nil) + encoder := gob.NewEncoder(buf) + err := encoder.Encode(obj) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func unmarshal[T jmapObject](data []byte, obj T) error { + buf := bytes.NewBuffer(data) + decoder := gob.NewDecoder(buf) + return decoder.Decode(obj) +} diff --git a/worker/jmap/cache/mailbox.go b/worker/jmap/cache/mailbox.go new file mode 100644 index 00000000..44388778 --- /dev/null +++ b/worker/jmap/cache/mailbox.go @@ -0,0 +1,35 @@ +package cache + +import ( + "git.sr.ht/~rockorager/go-jmap" + "git.sr.ht/~rockorager/go-jmap/mail/mailbox" +) + +func (c *JMAPCache) GetMailbox(id jmap.ID) (*mailbox.Mailbox, error) { + buf, err := c.get(mailboxKey(id)) + if err != nil { + return nil, err + } + m := new(mailbox.Mailbox) + err = unmarshal(buf, m) + if err != nil { + return nil, err + } + return m, nil +} + +func (c *JMAPCache) PutMailbox(id jmap.ID, m *mailbox.Mailbox) error { + buf, err := marshal(m) + if err != nil { + return err + } + return c.put(mailboxKey(id), buf) +} + +func (c *JMAPCache) DeleteMailbox(id jmap.ID) error { + return c.delete(mailboxKey(id)) +} + +func mailboxKey(id jmap.ID) string { + return "mailbox/" + string(id) +} diff --git a/worker/jmap/cache/mailbox_list.go b/worker/jmap/cache/mailbox_list.go new file mode 100644 index 00000000..fb9bd3ea --- /dev/null +++ b/worker/jmap/cache/mailbox_list.go @@ -0,0 +1,32 @@ +package cache + +import ( + "git.sr.ht/~rockorager/go-jmap" +) + +type IDList struct { + IDs []jmap.ID +} + +func (c *JMAPCache) GetMailboxList() ([]jmap.ID, error) { + buf, err := c.get(mailboxListKey) + if err != nil { + return nil, err + } + var list IDList + err = unmarshal(buf, &list) + if err != nil { + return nil, err + } + return list.IDs, nil +} + +func (c *JMAPCache) PutMailboxList(list []jmap.ID) error { + buf, err := marshal(&IDList{IDs: list}) + if err != nil { + return err + } + return c.put(mailboxListKey, buf) +} + +const mailboxListKey = "mailbox/list" diff --git a/worker/jmap/cache/session.go b/worker/jmap/cache/session.go new file mode 100644 index 00000000..7126041f --- /dev/null +++ b/worker/jmap/cache/session.go @@ -0,0 +1,32 @@ +package cache + +import ( + "git.sr.ht/~rockorager/go-jmap" +) + +func (c *JMAPCache) GetSession() (*jmap.Session, error) { + buf, err := c.get(sessionKey) + if err != nil { + return nil, err + } + s := new(jmap.Session) + err = unmarshal(buf, s) + if err != nil { + return nil, err + } + return s, nil +} + +func (c *JMAPCache) PutSession(s *jmap.Session) error { + buf, err := marshal(s) + if err != nil { + return err + } + return c.put(sessionKey, buf) +} + +func (c *JMAPCache) DeleteSession() error { + return c.delete(sessionKey) +} + +const sessionKey = "session" diff --git a/worker/jmap/cache/state.go b/worker/jmap/cache/state.go new file mode 100644 index 00000000..5fec5034 --- /dev/null +++ b/worker/jmap/cache/state.go @@ -0,0 +1,30 @@ +package cache + +func (c *JMAPCache) GetMailboxState() (string, error) { + buf, err := c.get(mailboxStateKey) + if err != nil { + return "", err + } + return string(buf), nil +} + +func (c *JMAPCache) PutMailboxState(state string) error { + return c.put(mailboxStateKey, []byte(state)) +} + +func (c *JMAPCache) GetEmailState() (string, error) { + buf, err := c.get(emailStateKey) + if err != nil { + return "", err + } + return string(buf), nil +} + +func (c *JMAPCache) PutEmailState(state string) error { + return c.put(emailStateKey, []byte(state)) +} + +const ( + mailboxStateKey = "state/mailbox" + emailStateKey = "state/email" +) |