153 lines
3.4 KiB
Go
153 lines
3.4 KiB
Go
package leveldb
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/gob"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/syndtr/goleveldb/leveldb"
|
|
"github.com/syndtr/goleveldb/leveldb/util"
|
|
"m8sh.su/x/m8sh/database"
|
|
)
|
|
|
|
type posts struct {
|
|
db *leveldb.DB
|
|
}
|
|
|
|
func (p *posts) Create(post database.Post) error {
|
|
ts := formatTime(post.Date)
|
|
|
|
metaKey := key(post.Author, prefixPostMeta, ts)
|
|
bodyKey := key(post.Author, prefixPostBody, ts)
|
|
|
|
var metaBuf bytes.Buffer
|
|
if err := gob.NewEncoder(&metaBuf).Encode(post); err != nil {
|
|
return fmt.Errorf("encode post for %q: %w", post.Author, err)
|
|
}
|
|
|
|
batch := new(leveldb.Batch)
|
|
defer batch.Reset()
|
|
|
|
batch.Put(metaKey, metaBuf.Bytes())
|
|
batch.Put(bodyKey, []byte(post.Body))
|
|
|
|
if err := p.db.Write(batch, woSync); err != nil {
|
|
return fmt.Errorf("store post for %q: %w", post.Author, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *posts) Get(author string, date time.Time) (database.Post, error) {
|
|
ts := formatTime(date)
|
|
metaKey := key(author, prefixPostMeta, ts)
|
|
bodyKey := key(author, prefixPostBody, ts)
|
|
|
|
data, err := p.db.Get(metaKey, nil)
|
|
if err != nil {
|
|
if err == leveldb.ErrNotFound {
|
|
return database.Post{}, database.ErrNotFound
|
|
}
|
|
return database.Post{}, fmt.Errorf("read post meta for %q: %w", author, err)
|
|
}
|
|
|
|
var post database.Post
|
|
if err = gob.NewDecoder(bytes.NewReader(data)).Decode(&post); err != nil {
|
|
return database.Post{}, fmt.Errorf("decode post for %q: %w", author, err)
|
|
}
|
|
|
|
body, err := p.db.Get(bodyKey, nil)
|
|
if err == nil {
|
|
post.Body = string(body)
|
|
}
|
|
|
|
return post, nil
|
|
}
|
|
|
|
func (p *posts) List(author string, limit, offset int) ([]database.Post, error) {
|
|
prefix := key(author, prefixPostMeta)
|
|
|
|
iter := p.db.NewIterator(util.BytesPrefix(prefix), nil)
|
|
defer iter.Release()
|
|
|
|
var posts []database.Post
|
|
skipped := 0
|
|
for iter.Next() {
|
|
var post database.Post
|
|
if err := gob.NewDecoder(bytes.NewReader(iter.Value())).Decode(&post); err != nil {
|
|
return nil, fmt.Errorf("decode post for %q: %w", author, err)
|
|
}
|
|
|
|
if skipped < offset {
|
|
skipped++
|
|
continue
|
|
}
|
|
|
|
posts = append(posts, post)
|
|
if len(posts) >= limit {
|
|
break
|
|
}
|
|
}
|
|
|
|
return posts, nil
|
|
}
|
|
|
|
func (p *posts) Update(post database.Post) error {
|
|
ts := formatTime(post.Date)
|
|
metaKey := key(post.Author, prefixPostMeta, ts)
|
|
|
|
_, err := p.db.Get(metaKey, nil)
|
|
if err != nil {
|
|
if err == leveldb.ErrNotFound {
|
|
return database.ErrNotFound
|
|
}
|
|
return fmt.Errorf("check post for %q: %w", post.Author, err)
|
|
}
|
|
|
|
bodyKey := key(post.Author, prefixPostBody, ts)
|
|
|
|
var metaBuf bytes.Buffer
|
|
if err = gob.NewEncoder(&metaBuf).Encode(post); err != nil {
|
|
return fmt.Errorf("encode post for %q: %w", post.Author, err)
|
|
}
|
|
|
|
batch := new(leveldb.Batch)
|
|
defer batch.Reset()
|
|
|
|
batch.Put(metaKey, metaBuf.Bytes())
|
|
batch.Put(bodyKey, []byte(post.Body))
|
|
|
|
if err = p.db.Write(batch, woSync); err != nil {
|
|
return fmt.Errorf("update post for %q: %w", post.Author, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *posts) Delete(author string, date time.Time) error {
|
|
ts := formatTime(date)
|
|
metaKey := key(author, prefixPostMeta, ts)
|
|
bodyKey := key(author, prefixPostBody, ts)
|
|
|
|
_, err := p.db.Get(metaKey, nil)
|
|
if err != nil {
|
|
if err == leveldb.ErrNotFound {
|
|
return database.ErrNotFound
|
|
}
|
|
return fmt.Errorf("check post for %q: %w", author, err)
|
|
}
|
|
|
|
batch := new(leveldb.Batch)
|
|
defer batch.Reset()
|
|
|
|
batch.Delete(metaKey)
|
|
batch.Delete(bodyKey)
|
|
|
|
if err = p.db.Write(batch, woSync); err != nil {
|
|
return fmt.Errorf("delete post for %q: %w", author, err)
|
|
}
|
|
|
|
return nil
|
|
}
|