Files
m8sh/database/leveldb/emails.go
T
2026-06-06 19:20:38 +03:00

170 lines
3.9 KiB
Go

package leveldb
import (
"bytes"
"encoding/binary"
"encoding/gob"
"fmt"
"time"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
"m8sh.su/x/m8sh/database"
)
type email struct {
db *leveldb.DB
}
func (e *email) Put(user string, letter database.Letter) error {
ts := formatTime(letter.Date)
var correspondent string
if letter.To == user {
correspondent = letter.From
} else {
correspondent = letter.To
}
metaKey := key(user, prefixEmailMeta, ts, correspondent)
bodyKey := key(user, prefixEmailBody, ts, correspondent)
var metaBuf bytes.Buffer
if err := gob.NewEncoder(&metaBuf).Encode(letter); err != nil {
return fmt.Errorf("encode letter for %q: %w", user, err)
}
batch := new(leveldb.Batch)
defer batch.Reset()
batch.Put(metaKey, metaBuf.Bytes())
batch.Put(bodyKey, letter.Body)
if err := e.db.Write(batch, woSync); err != nil {
return fmt.Errorf("store letter for %q: %w", user, err)
}
return nil
}
func (e *email) Get(user, correspondent string, date time.Time) (database.Letter, error) {
ts := formatTime(date)
metaKey := key(user, prefixEmailMeta, ts, correspondent)
bodyKey := key(user, prefixEmailBody, ts, correspondent)
data, err := e.db.Get(metaKey, nil)
if err != nil {
if err == leveldb.ErrNotFound {
return database.Letter{}, database.ErrNotFound
}
return database.Letter{}, fmt.Errorf("read letter meta for %q: %w", user, err)
}
var letter database.Letter
if err = gob.NewDecoder(bytes.NewReader(data)).Decode(&letter); err != nil {
return database.Letter{}, fmt.Errorf("decode letter for %q: %w", user, err)
}
body, err := e.db.Get(bodyKey, nil)
if err == nil {
letter.Body = make([]byte, len(body))
copy(letter.Body, body)
}
return letter, nil
}
func (e *email) List(user string, limit int, before time.Time) ([]database.Letter, error) {
prefix := key(user, prefixEmailMeta)
iter := e.db.NewIterator(util.BytesPrefix(prefix), nil)
defer iter.Release()
var letters []database.Letter
for iter.Next() {
var letter database.Letter
if err := gob.NewDecoder(bytes.NewReader(iter.Value())).Decode(&letter); err != nil {
return nil, fmt.Errorf("decode letter for %q: %w", user, err)
}
if !before.IsZero() && !letter.Date.Before(before) {
continue
}
letters = append(letters, letter)
if len(letters) >= limit {
break
}
}
return letters, nil
}
func (e *email) MarkSeen(user, correspondent string, date time.Time) error {
ts := formatTime(date)
metaKey := key(user, prefixEmailMeta, ts, correspondent)
data, err := e.db.Get(metaKey, nil)
if err != nil {
if err == leveldb.ErrNotFound {
return database.ErrNotFound
}
return fmt.Errorf("read letter meta for %q: %w", user, err)
}
var letter database.Letter
if err = gob.NewDecoder(bytes.NewReader(data)).Decode(&letter); err != nil {
return fmt.Errorf("decode letter for %q: %w", user, err)
}
if letter.Seen {
return nil
}
letter.Seen = true
var buf bytes.Buffer
if err = gob.NewEncoder(&buf).Encode(letter); err != nil {
return fmt.Errorf("encode letter for %q: %w", user, err)
}
if err = e.db.Put(metaKey, buf.Bytes(), woSync); err != nil {
return fmt.Errorf("mark seen for %q: %w", user, err)
}
return nil
}
func (e *email) Delete(user, correspondent string, date time.Time) error {
ts := formatTime(date)
metaKey := key(user, prefixEmailMeta, ts, correspondent)
_, err := e.db.Get(metaKey, nil)
if err != nil {
if err == leveldb.ErrNotFound {
return database.ErrNotFound
}
return fmt.Errorf("check letter for %q: %w", user, err)
}
bodyKey := key(user, prefixEmailBody, ts, correspondent)
batch := new(leveldb.Batch)
defer batch.Reset()
batch.Delete(metaKey)
batch.Delete(bodyKey)
if err = e.db.Write(batch, woSync); err != nil {
return fmt.Errorf("delete letter for %q: %w", user, err)
}
return nil
}
func formatTime(t time.Time) []byte {
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], uint64(t.UnixNano()))
return buf[:]
}