2020-06-27 13:25:16 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2023-02-25 14:00:48 +00:00
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2020-06-27 13:25:16 +00:00
|
|
|
"os"
|
|
|
|
"path"
|
2023-02-25 14:00:48 +00:00
|
|
|
"strings"
|
2020-06-27 13:25:16 +00:00
|
|
|
|
2023-02-25 14:00:48 +00:00
|
|
|
"github.com/go-redis/redis/v8"
|
2020-06-27 13:25:16 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
|
|
)
|
|
|
|
|
2023-02-25 14:00:48 +00:00
|
|
|
const redisKeyPrefix = "io.luzifer.automail"
|
|
|
|
|
|
|
|
type (
|
|
|
|
fileStorage struct {
|
|
|
|
LastUID uint32
|
|
|
|
filename string
|
|
|
|
}
|
|
|
|
|
|
|
|
redisStorage struct {
|
|
|
|
LastUID uint32
|
|
|
|
client *redis.Client
|
|
|
|
}
|
|
|
|
|
|
|
|
storage interface {
|
|
|
|
GetLastUID() uint32
|
|
|
|
Load() error
|
|
|
|
Save() error
|
|
|
|
SetUID(uint32)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func newStorage(sType, dsn string) (storage, error) {
|
|
|
|
switch sType {
|
|
|
|
case "file":
|
|
|
|
return &fileStorage{filename: dsn}, nil
|
|
|
|
|
|
|
|
case "redis":
|
|
|
|
return newRedisStorage(dsn)
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, errors.Errorf("invalid storage type %q", sType)
|
|
|
|
}
|
2020-06-27 13:25:16 +00:00
|
|
|
}
|
|
|
|
|
2023-02-25 14:00:48 +00:00
|
|
|
// --- Storage implementation: File
|
|
|
|
|
|
|
|
func (f fileStorage) GetLastUID() uint32 { return f.LastUID }
|
2020-06-27 13:25:16 +00:00
|
|
|
|
2023-02-25 14:00:48 +00:00
|
|
|
func (f *fileStorage) Load() error {
|
|
|
|
if _, err := os.Stat(f.filename); os.IsNotExist(err) {
|
|
|
|
return nil
|
2020-06-27 13:25:16 +00:00
|
|
|
}
|
|
|
|
|
2023-02-25 14:00:48 +00:00
|
|
|
sf, err := os.Open(f.filename)
|
2020-06-27 13:25:16 +00:00
|
|
|
if err != nil {
|
2023-02-25 14:00:48 +00:00
|
|
|
return errors.Wrap(err, "opening storage file")
|
2020-06-27 13:25:16 +00:00
|
|
|
}
|
2023-02-25 14:00:48 +00:00
|
|
|
defer sf.Close()
|
2020-06-27 13:25:16 +00:00
|
|
|
|
2023-02-25 14:00:48 +00:00
|
|
|
return errors.Wrap(yaml.NewDecoder(sf).Decode(f), "decoding storage file")
|
2020-06-27 13:25:16 +00:00
|
|
|
}
|
|
|
|
|
2023-02-25 14:00:48 +00:00
|
|
|
func (f fileStorage) Save() error {
|
|
|
|
if err := os.MkdirAll(path.Dir(f.filename), 0o700); err != nil {
|
|
|
|
return errors.Wrap(err, "ensuring directory for storage file")
|
|
|
|
}
|
|
|
|
|
|
|
|
sf, err := os.Create(f.filename)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "creating storage file")
|
2020-06-27 13:25:16 +00:00
|
|
|
}
|
2023-02-25 14:00:48 +00:00
|
|
|
defer sf.Close()
|
|
|
|
|
|
|
|
return errors.Wrap(yaml.NewEncoder(sf).Encode(f), "encoding storage file")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fileStorage) SetUID(uid uint32) { f.LastUID = uid }
|
|
|
|
|
|
|
|
// --- Storage implementation: Redis
|
2020-06-27 13:25:16 +00:00
|
|
|
|
2023-02-25 14:00:48 +00:00
|
|
|
func newRedisStorage(dsn string) (*redisStorage, error) {
|
|
|
|
opts, err := redis.ParseURL(dsn)
|
2020-06-27 13:25:16 +00:00
|
|
|
if err != nil {
|
2023-02-25 14:00:48 +00:00
|
|
|
return nil, errors.Wrap(err, "parsing storage DSN")
|
|
|
|
}
|
|
|
|
|
|
|
|
out := &redisStorage{}
|
|
|
|
out.client = redis.NewClient(opts)
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r redisStorage) GetLastUID() uint32 { return r.LastUID }
|
|
|
|
|
|
|
|
func (r *redisStorage) Load() error {
|
|
|
|
data, err := r.client.Get(context.Background(), r.key()).Bytes()
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, redis.Nil) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.Wrap(err, "loading persistent data from redis")
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.Wrap(json.Unmarshal(data, r), "decoding storage object")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r redisStorage) Save() error {
|
|
|
|
data, err := json.Marshal(r)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "marshalling storage object")
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.Wrap(
|
|
|
|
r.client.Set(context.Background(), r.key(), data, 0).Err(),
|
|
|
|
"saving persistent data to redis",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *redisStorage) SetUID(uid uint32) { r.LastUID = uid }
|
|
|
|
|
|
|
|
func (redisStorage) key() string {
|
|
|
|
prefix := redisKeyPrefix
|
|
|
|
if v := os.Getenv("REDIS_KEY_PREFIX"); v != "" {
|
|
|
|
prefix = v
|
2020-06-27 13:25:16 +00:00
|
|
|
}
|
|
|
|
|
2023-02-25 14:00:48 +00:00
|
|
|
return strings.Join([]string{prefix, "store"}, ":")
|
2020-06-27 13:25:16 +00:00
|
|
|
}
|