2018-01-08 00:38:13 +02:00
|
|
|
package database
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
"github.com/boltdb/bolt"
|
|
|
|
|
|
|
|
"github.com/emersion/hydroxide/protonmail"
|
|
|
|
)
|
|
|
|
|
|
|
|
var ErrNotFound = errors.New("message not found in local database")
|
|
|
|
|
|
|
|
var (
|
|
|
|
mailboxesBucket = []byte("mailboxes")
|
|
|
|
messagesBucket = []byte("messages")
|
|
|
|
)
|
|
|
|
|
2018-01-11 15:40:05 +02:00
|
|
|
func userMessage(b *bolt.Bucket, apiID string) (*protonmail.Message, error) {
|
|
|
|
k := []byte(apiID)
|
|
|
|
v := b.Get(k)
|
|
|
|
if v == nil {
|
|
|
|
return nil, ErrNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := &protonmail.Message{}
|
|
|
|
err := json.Unmarshal(v, msg)
|
|
|
|
return msg, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func userCreateMessage(b *bolt.Bucket, msg *protonmail.Message) error {
|
|
|
|
k := []byte(msg.ID)
|
|
|
|
v, err := json.Marshal(msg)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return b.Put(k, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
func userSync(tx *bolt.Tx, messages []*protonmail.Message) error {
|
|
|
|
b, err := tx.CreateBucketIfNotExists(messagesBucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, msg := range messages {
|
|
|
|
if err := userCreateMessage(b, msg); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-01-08 00:38:13 +02:00
|
|
|
type User struct {
|
|
|
|
db *bolt.DB
|
|
|
|
}
|
|
|
|
|
2018-01-11 15:40:05 +02:00
|
|
|
func (u *User) Mailbox(labelID string) (*Mailbox, error) {
|
2018-01-08 00:38:13 +02:00
|
|
|
err := u.db.Update(func(tx *bolt.Tx) error {
|
|
|
|
b, err := tx.CreateBucketIfNotExists(mailboxesBucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-01-11 15:40:05 +02:00
|
|
|
_, err = b.CreateBucketIfNotExists([]byte(labelID))
|
2018-01-08 00:38:13 +02:00
|
|
|
return err
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-01-11 15:40:05 +02:00
|
|
|
return &Mailbox{labelID, u}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *User) Message(apiID string) (*protonmail.Message, error) {
|
|
|
|
var msg *protonmail.Message
|
|
|
|
err := u.db.View(func (tx *bolt.Tx) error {
|
|
|
|
b := tx.Bucket(messagesBucket)
|
|
|
|
if b == nil {
|
|
|
|
return ErrNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
msg, err = userMessage(b, apiID)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
return msg, err
|
2018-01-08 00:38:13 +02:00
|
|
|
}
|
|
|
|
|
2018-01-11 15:40:05 +02:00
|
|
|
func (u *User) ResetMessages() error {
|
2018-01-08 00:38:13 +02:00
|
|
|
return u.db.Update(func(tx *bolt.Tx) error {
|
2018-01-11 15:40:05 +02:00
|
|
|
return tx.DeleteBucket(messagesBucket)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *User) CreateMessage(msg *protonmail.Message) error {
|
|
|
|
return u.db.Update(func(tx *bolt.Tx) error {
|
|
|
|
messages, err := tx.CreateBucketIfNotExists(messagesBucket)
|
2018-01-08 00:38:13 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-01-11 15:40:05 +02:00
|
|
|
if err := userCreateMessage(messages, msg); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
mailboxes, err := tx.CreateBucketIfNotExists(mailboxesBucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, labelID := range msg.LabelIDs {
|
|
|
|
mbox, err := mailboxes.CreateBucketIfNotExists([]byte(labelID))
|
2018-01-10 00:42:12 +02:00
|
|
|
if err != nil {
|
2018-01-08 00:38:13 +02:00
|
|
|
return err
|
|
|
|
}
|
2018-01-11 15:40:05 +02:00
|
|
|
|
|
|
|
if err := mailboxCreateMessage(mbox, msg.ID); err != nil {
|
2018-01-08 00:38:13 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-01-12 14:20:17 +02:00
|
|
|
func (u *User) UpdateMessage(apiID string, update *protonmail.EventMessageUpdate) error {
|
2018-01-11 15:40:05 +02:00
|
|
|
return u.db.Update(func(tx *bolt.Tx) error {
|
|
|
|
messages := tx.Bucket(messagesBucket)
|
|
|
|
if messages == nil {
|
|
|
|
return errors.New("cannot update message in local DB: messages bucket doesn't exist")
|
2018-01-08 00:38:13 +02:00
|
|
|
}
|
|
|
|
|
2018-01-12 14:20:17 +02:00
|
|
|
msg, err := userMessage(messages, apiID)
|
2018-01-11 15:40:05 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
addedLabels, removedLabels := update.DiffLabelIDs(msg.LabelIDs)
|
|
|
|
|
|
|
|
mailboxes, err := tx.CreateBucketIfNotExists(mailboxesBucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, labelID := range addedLabels {
|
|
|
|
mbox, err := mailboxes.CreateBucketIfNotExists([]byte(labelID))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-01-12 14:20:17 +02:00
|
|
|
if err := mailboxCreateMessage(mbox, apiID); err != nil {
|
2018-01-11 15:40:05 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, labelID := range removedLabels {
|
|
|
|
mbox := mailboxes.Bucket([]byte(labelID))
|
|
|
|
if mbox == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-01-12 14:20:17 +02:00
|
|
|
if err := mailboxDeleteMessage(mbox, apiID); err != nil {
|
2018-01-11 15:40:05 +02:00
|
|
|
return err
|
|
|
|
}
|
2018-01-08 00:38:13 +02:00
|
|
|
}
|
|
|
|
|
2018-01-11 15:40:05 +02:00
|
|
|
update.Patch(msg)
|
|
|
|
return userCreateMessage(messages, msg)
|
2018-01-08 00:38:13 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-01-11 15:40:05 +02:00
|
|
|
func (u *User) DeleteMessage(apiID string) error {
|
2018-01-11 13:39:32 +02:00
|
|
|
return u.db.Update(func(tx *bolt.Tx) error {
|
2018-01-11 15:40:05 +02:00
|
|
|
messages:= tx.Bucket(messagesBucket)
|
|
|
|
if messages == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
msg, err := userMessage(messages, apiID)
|
|
|
|
if err == ErrNotFound {
|
|
|
|
return nil
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := messages.Delete([]byte(apiID)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
mailboxes := tx.Bucket(mailboxesBucket)
|
|
|
|
if mailboxes == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
for _, labelID := range msg.LabelIDs {
|
|
|
|
mbox := mailboxes.Bucket([]byte(labelID))
|
|
|
|
if mbox == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := mailboxDeleteMessage(mbox, msg.ID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2018-01-11 13:39:32 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *User) Close() error {
|
|
|
|
return u.db.Close()
|
|
|
|
}
|
|
|
|
|
2018-01-08 00:38:13 +02:00
|
|
|
func Open(path string) (*User, error) {
|
|
|
|
db, err := bolt.Open(path, 0700, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &User{db}, nil
|
|
|
|
}
|