|
- package alps
-
- import (
- "encoding/json"
- "fmt"
- "reflect"
- "sync"
-
- imapmetadata "github.com/emersion/go-imap-metadata"
- imapclient "github.com/emersion/go-imap/client"
- "github.com/labstack/echo/v4"
- )
-
- // ErrNoStoreEntry is returned by Store.Get when the entry doesn't exist.
- var ErrNoStoreEntry = fmt.Errorf("alps: no such entry in store")
-
- // Store allows storing per-user persistent data.
- //
- // Store shouldn't be used from inside Session.DoIMAP.
- type Store interface {
- Get(key string, out interface{}) error
- Put(key string, v interface{}) error
- }
-
- var warnedTransientStore = false
-
- func newStore(session *Session, logger echo.Logger) (Store, error) {
- s, err := newIMAPStore(session)
- if err == nil {
- return s, nil
- } else if err != errIMAPMetadataUnsupported {
- return nil, err
- }
- if !warnedTransientStore {
- logger.Print("Upstream IMAP server doesn't support the METADATA extension, using transient store instead")
- warnedTransientStore = true
- }
- return newMemoryStore(), nil
- }
-
- type memoryStore struct {
- locker sync.RWMutex
- entries map[string]interface{}
- }
-
- func newMemoryStore() *memoryStore {
- return &memoryStore{entries: make(map[string]interface{})}
- }
-
- func (s *memoryStore) Get(key string, out interface{}) error {
- s.locker.RLock()
- defer s.locker.RUnlock()
-
- v, ok := s.entries[key]
- if !ok {
- return ErrNoStoreEntry
- }
-
- reflect.ValueOf(out).Elem().Set(reflect.ValueOf(v).Elem())
- return nil
- }
-
- func (s *memoryStore) Put(key string, v interface{}) error {
- s.locker.Lock()
- s.entries[key] = v
- s.locker.Unlock()
- return nil
- }
-
- type imapStore struct {
- session *Session
- cache *memoryStore
- }
-
- var errIMAPMetadataUnsupported = fmt.Errorf("alps: IMAP server doesn't support METADATA extension")
-
- func newIMAPStore(session *Session) (*imapStore, error) {
- err := session.DoIMAP(func(c *imapclient.Client) error {
- mc := imapmetadata.NewClient(c)
- ok, err := mc.SupportMetadata()
- if err != nil {
- return fmt.Errorf("alps: failed to check for IMAP METADATA support: %v", err)
- }
- if !ok {
- return errIMAPMetadataUnsupported
- }
- return nil
- })
- if err != nil {
- return nil, err
- }
- return &imapStore{session, newMemoryStore()}, nil
- }
-
- func (s *imapStore) key(key string) string {
- return "/private/vendor/alps/" + key
- }
-
- func (s *imapStore) Get(key string, out interface{}) error {
- if err := s.cache.Get(key, out); err != ErrNoStoreEntry {
- return err
- }
-
- var entries map[string]string
- err := s.session.DoIMAP(func(c *imapclient.Client) error {
- mc := imapmetadata.NewClient(c)
- var err error
- entries, err = mc.GetMetadata("", []string{s.key(key)}, nil)
- return err
- })
- if err != nil {
- return fmt.Errorf("alps: failed to fetch IMAP store entry %q: %v", key, err)
- }
- v, ok := entries[s.key(key)]
- if !ok {
- return ErrNoStoreEntry
- }
- if err := json.Unmarshal([]byte(v), out); err != nil {
- return fmt.Errorf("alps: failed to unmarshal IMAP store entry %q: %v", key, err)
- }
- return s.cache.Put(key, out)
- }
-
- func (s *imapStore) Put(key string, v interface{}) error {
- b, err := json.Marshal(v)
- if err != nil {
- return fmt.Errorf("alps: failed to marshal IMAP store entry %q: %v", key, err)
- }
- entries := map[string]string{
- s.key(key): string(b),
- }
- err = s.session.DoIMAP(func(c *imapclient.Client) error {
- mc := imapmetadata.NewClient(c)
- return mc.SetMetadata("", entries)
- })
- if err != nil {
- return fmt.Errorf("alps: failed to put IMAP store entry %q: %v", key, err)
- }
-
- return s.cache.Put(key, v)
- }
|