aboutsummaryrefslogtreecommitdiffstats
path: root/worker/jmap/cache
diff options
context:
space:
mode:
Diffstat (limited to 'worker/jmap/cache')
-rw-r--r--worker/jmap/cache/blob.go45
-rw-r--r--worker/jmap/cache/cache.go79
-rw-r--r--worker/jmap/cache/email.go35
-rw-r--r--worker/jmap/cache/folder_contents.go61
-rw-r--r--worker/jmap/cache/gob.go35
-rw-r--r--worker/jmap/cache/mailbox.go35
-rw-r--r--worker/jmap/cache/mailbox_list.go32
-rw-r--r--worker/jmap/cache/session.go32
-rw-r--r--worker/jmap/cache/state.go30
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"
+)