From 38af52e8573dcccd4834f03a6f6756258f94ec5e Mon Sep 17 00:00:00 2001 From: emersion Date: Thu, 11 Jan 2018 12:39:32 +0100 Subject: [PATCH] imap: add User.receiveEvents --- carddav/carddav.go | 2 +- imap/database/mailbox.go | 15 +++++++ imap/database/user.go | 10 +++++ imap/mailbox.go | 9 ++++ imap/user.go | 94 ++++++++++++++++++++++++++++++++++++---- protonmail/events.go | 22 ++++++++-- 6 files changed, 139 insertions(+), 13 deletions(-) diff --git a/carddav/carddav.go b/carddav/carddav.go index dd8c3a0..fe2913b 100644 --- a/carddav/carddav.go +++ b/carddav/carddav.go @@ -335,7 +335,7 @@ func (ab *addressBook) CreateAddressObject(card vcard.Card) (carddav.AddressObje func (ab *addressBook) receiveEvents(events <-chan *protonmail.Event) { for event := range events { ab.locker.Lock() - if event.Refresh == 1 { + if event.Refresh&protonmail.EventRefreshContacts != 0 { ab.cache = make(map[string]*addressObject) ab.total = -1 } else if len(event.Contacts) > 0 { diff --git a/imap/database/mailbox.go b/imap/database/mailbox.go index 63bdd2f..5040af9 100644 --- a/imap/database/mailbox.go +++ b/imap/database/mailbox.go @@ -174,3 +174,18 @@ func (mbox *Mailbox) ForEach(f func(seqNum, uid uint32, apiID string) error) err 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.name) + if err := b.DeleteBucket(k); err != nil { + return err + } + _, err := b.CreateBucket(k) + return err + }) +} diff --git a/imap/database/user.go b/imap/database/user.go index 27af30c..43b419d 100644 --- a/imap/database/user.go +++ b/imap/database/user.go @@ -78,6 +78,16 @@ func (u *User) Message(apiID string) (*protonmail.Message, error) { return msg, err } +func (u *User) ResetMessages() error { + return u.db.Update(func(tx *bolt.Tx) error { + return tx.DeleteBucket(messagesBucket) + }) +} + +func (u *User) Close() error { + return u.db.Close() +} + func Open(path string) (*User, error) { db, err := bolt.Open(path, 0700, nil) if err != nil { diff --git a/imap/mailbox.go b/imap/mailbox.go index 75ad27c..cb2d0d6 100644 --- a/imap/mailbox.go +++ b/imap/mailbox.go @@ -130,6 +130,15 @@ func (mbox *mailbox) init() error { return nil } +func (mbox *mailbox) reset() error { + mbox.initializedLock.Lock() + defer mbox.initializedLock.Unlock() + + mbox.initialized = false + + return mbox.db.Reset() +} + func (mbox *mailbox) fetchMessage(isUid bool, id uint32, items []imap.FetchItem) (*imap.Message, error) { var apiID string var err error diff --git a/imap/user.go b/imap/user.go index ab72ded..458f925 100644 --- a/imap/user.go +++ b/imap/user.go @@ -1,6 +1,9 @@ package imap import ( + "log" + "sync" + "golang.org/x/crypto/openpgp" "github.com/emersion/go-imap" imapbackend "github.com/emersion/go-imap/backend" @@ -31,6 +34,8 @@ type user struct { privateKeys openpgp.EntityList db *database.User + + locker sync.Mutex mailboxes map[string]*mailbox } @@ -39,7 +44,6 @@ func newUser(c *protonmail.Client, u *protonmail.User, privateKeys openpgp.Entit c: c, u: u, privateKeys: privateKeys, - mailboxes: make(map[string]*mailbox), } db, err := database.Open(u.Name+".db") @@ -48,34 +52,49 @@ func newUser(c *protonmail.Client, u *protonmail.User, privateKeys openpgp.Entit } uu.db = db + if err := uu.initMailboxes(); err != nil { + return nil, err + } + + // TODO: go uu.receiveEvents(events) + + return uu, nil +} + +func (u *user) initMailboxes() error { + u.locker.Lock() + defer u.locker.Unlock() + + u.mailboxes = make(map[string]*mailbox) + for _, data := range systemMailboxes { - mboxDB, err := db.Mailbox(data.label) + mboxDB, err := u.db.Mailbox(data.label) if err != nil { - return nil, err + return err } - uu.mailboxes[data.label] = &mailbox{ + u.mailboxes[data.label] = &mailbox{ name: data.name, label: data.label, flags: data.flags, - u: uu, + u: u, db: mboxDB, } } - counts, err := c.CountMessages("") + counts, err := u.c.CountMessages("") if err != nil { - return nil, err + return err } for _, count := range counts { - if mbox, ok := uu.mailboxes[count.LabelID]; ok { + if mbox, ok := u.mailboxes[count.LabelID]; ok { mbox.total = count.Total mbox.unread = count.Unread } } - return uu, nil + return nil } func (u *user) Username() string { @@ -83,6 +102,9 @@ func (u *user) Username() string { } func (u *user) ListMailboxes(subscribed bool) ([]imapbackend.Mailbox, error) { + u.locker.Lock() + defer u.locker.Unlock() + list := make([]imapbackend.Mailbox, 0, len(u.mailboxes)) for _, mbox := range u.mailboxes { list = append(list, mbox) @@ -91,6 +113,9 @@ func (u *user) ListMailboxes(subscribed bool) ([]imapbackend.Mailbox, error) { } func (u *user) GetMailbox(name string) (imapbackend.Mailbox, error) { + u.locker.Lock() + defer u.locker.Unlock() + for _, mbox := range u.mailboxes { if mbox.name == name { return mbox, nil @@ -112,8 +137,59 @@ func (u *user) RenameMailbox(existingName, newName string) error { } func (u *user) Logout() error { + if err := u.db.Close(); err != nil { + return err + } + u.c = nil u.u = nil u.privateKeys = nil return nil } + +func (u *user) receiveEvents(events <-chan *protonmail.Event) { + for event := range events { + if event.Refresh&protonmail.EventRefreshMail != 0 { + log.Println("Reinitializing the whole IMAP database") + + u.locker.Lock() + for _, mbox := range u.mailboxes { + if err := mbox.reset(); err != nil { + log.Printf("cannot reset mailbox %s: %v", mbox.name, err) + } + } + u.locker.Unlock() + + if err := u.db.ResetMessages(); err != nil { + log.Printf("cannot reset user: %v", err) + } + + if err := u.initMailboxes(); err != nil { + log.Printf("cannot reinitialize mailboxes: %v", err) + } + } else { + u.locker.Lock() + for _, count := range event.MessageCounts { + if mbox, ok := u.mailboxes[count.LabelID]; ok { + mbox.total = count.Total + mbox.unread = count.Unread + // TODO: send update + } + } + u.locker.Unlock() + + for _, eventMessage := range event.Messages { + switch eventMessage.Action { + case protonmail.EventCreate: + // TODO + case protonmail.EventUpdate: + // TODO + case protonmail.EventUpdateFlags: + // TODO + case protonmail.EventDelete: + // TODO + } + } + } + } +} diff --git a/protonmail/events.go b/protonmail/events.go index 7585a8e..d7576ed 100644 --- a/protonmail/events.go +++ b/protonmail/events.go @@ -4,10 +4,17 @@ import ( "net/http" ) +type EventRefresh int + +const ( + EventRefreshMail EventRefresh = 1 << iota + EventRefreshContacts +) + type Event struct { ID string `json:"EventID"` - Refresh int - //Messages + Refresh EventRefresh + Messages []*EventMessage Contacts []*EventContact //ContactEmails //Labels @@ -15,7 +22,7 @@ type Event struct { //Members //Domains //Organization - //MessageCounts + MessageCounts []*MessageCount //ConversationCounts //UsedSpace Notices []string @@ -27,8 +34,17 @@ const ( EventDelete EventAction = iota EventCreate EventUpdate + + // For messages + EventUpdateFlags ) +type EventMessage struct { + ID string + Action EventAction + Message *Message +} + type EventContact struct { ID string Action EventAction