hydroxide-push/imap/database/mailbox.go

198 lines
3.7 KiB
Go

package database
import (
"bytes"
"encoding/binary"
"errors"
"github.com/boltdb/bolt"
"github.com/0ranki/hydroxide-push/protonmail"
)
func serializeUID(uid uint32) []byte {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, uid)
return b
}
func unserializeUID(b []byte) uint32 {
return binary.BigEndian.Uint32(b)
}
func mailboxCreateMessage(b *bolt.Bucket, apiID string) (seqNum uint32, err error) {
want := []byte(apiID)
c := b.Cursor()
var n uint32 = 1
for k, v := c.First(); k != nil; k, v = c.Next() {
if bytes.Equal(v, want) {
return n, nil
}
n++
}
id, _ := b.NextSequence()
uid := uint32(id)
return n, b.Put(serializeUID(uid), want)
}
func mailboxDeleteMessage(b *bolt.Bucket, apiID string) (seqNum uint32, err error) {
want := []byte(apiID)
c := b.Cursor()
var n uint32 = 1
for k, v := c.First(); k != nil; k, v = c.Next() {
if bytes.Equal(v, want) {
return n, b.Delete(k)
}
n++
}
return 0, nil
}
type Mailbox struct {
labelID string
u *User
}
func (mbox *Mailbox) bucket(tx *bolt.Tx) (*bolt.Bucket, error) {
b := tx.Bucket(mailboxesBucket)
if b == nil {
return nil, errors.New("cannot find mailboxes bucket")
}
b = b.Bucket([]byte(mbox.labelID))
if b == nil {
return nil, errors.New("cannot find mailbox bucket")
}
return b, nil
}
func (mbox *Mailbox) Sync(messages []*protonmail.Message) error {
return mbox.u.db.Update(func(tx *bolt.Tx) error {
b, err := mbox.bucket(tx)
if err != nil {
return err
}
for _, msg := range messages {
if _, err := mailboxCreateMessage(b, msg.ID); err != nil {
return err
}
}
return userSync(tx, messages)
})
}
func (mbox *Mailbox) UidNext() (uint32, error) {
var uid uint32
err := mbox.u.db.View(func(tx *bolt.Tx) error {
b, err := mbox.bucket(tx)
if err != nil {
return err
}
uid = uint32(b.Sequence() + 1)
return nil
})
return uid, err
}
func (mbox *Mailbox) FromUid(uid uint32) (apiID string, err error) {
err = mbox.u.db.View(func(tx *bolt.Tx) error {
b, err := mbox.bucket(tx)
if err != nil {
return err
}
k := serializeUID(uid)
v := b.Get(k)
if v == nil {
return ErrNotFound
}
apiID = string(v)
return nil
})
return
}
func (mbox *Mailbox) FromSeqNum(seqNum uint32) (apiID string, err error) {
err = mbox.u.db.View(func(tx *bolt.Tx) error {
b, err := mbox.bucket(tx)
if err != nil {
return err
}
c := b.Cursor()
var n uint32 = 1
for k, v := c.First(); k != nil; k, v = c.Next() {
if seqNum == n {
apiID = string(v)
return nil
}
n++
}
return ErrNotFound
})
return
}
func (mbox *Mailbox) FromApiID(apiID string) (seqNum uint32, uid uint32, err error) {
err = mbox.u.db.View(func(tx *bolt.Tx) error {
b, err := mbox.bucket(tx)
if err != nil {
return err
}
want := []byte(apiID)
c := b.Cursor()
var n uint32 = 1
for k, v := c.First(); k != nil; k, v = c.Next() {
if bytes.Equal(v, want) {
seqNum = n
uid = unserializeUID(k)
return nil
}
n++
}
return ErrNotFound
})
return
}
func (mbox *Mailbox) ForEach(f func(seqNum, uid uint32, apiID string) error) error {
return mbox.u.db.View(func(tx *bolt.Tx) error {
b, err := mbox.bucket(tx)
if err != nil {
return err
}
c := b.Cursor()
var n uint32 = 1
for k, v := c.First(); k != nil; k, v = c.Next() {
if err := f(n, unserializeUID(k), string(v)); err != nil {
return err
}
n++
}
return nil
})
}
func (mbox *Mailbox) Reset() error {
return mbox.u.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(mailboxesBucket)
if b == nil {
return errors.New("cannot find mailboxes bucket")
}
k := []byte(mbox.labelID)
if err := b.DeleteBucket(k); err != nil {
return err
}
_, err := b.CreateBucket(k)
return err
})
}