Receive events, fixes #8

This commit is contained in:
emersion 2017-09-09 18:23:14 +02:00
parent 907e88f759
commit d33218dc7c
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
3 changed files with 149 additions and 14 deletions

View File

@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"os" "os"
"strings" "strings"
"sync"
"time" "time"
"github.com/emersion/hydroxide/protonmail" "github.com/emersion/hydroxide/protonmail"
@ -86,6 +87,7 @@ func (ao *addressObject) Stat() (os.FileInfo, error) {
type addressBook struct { type addressBook struct {
c *protonmail.Client c *protonmail.Client
cache map[string]*addressObject cache map[string]*addressObject
locker sync.Mutex
total int total int
} }
@ -98,15 +100,34 @@ func (ab *addressBook) Info() (*carddav.AddressBookInfo, error) {
} }
func (ab *addressBook) cacheComplete() bool { func (ab *addressBook) cacheComplete() bool {
ab.locker.Lock()
defer ab.locker.Unlock()
return ab.total >= 0 && len(ab.cache) == ab.total return ab.total >= 0 && len(ab.cache) == ab.total
} }
func (ab *addressBook) addressObject(id string) (*addressObject, bool) {
ab.locker.Lock()
defer ab.locker.Unlock()
ao, ok := ab.cache[id]
return ao, ok
}
func (ab *addressBook) cacheAddressObject(ao *addressObject) {
ab.locker.Lock()
defer ab.locker.Unlock()
ab.cache[ao.contact.ID] = ao
}
func (ab *addressBook) ListAddressObjects() ([]carddav.AddressObject, error) { func (ab *addressBook) ListAddressObjects() ([]carddav.AddressObject, error) {
if ab.cacheComplete() { if ab.cacheComplete() {
ab.locker.Lock()
defer ab.locker.Unlock()
aos := make([]carddav.AddressObject, 0, len(ab.cache)) aos := make([]carddav.AddressObject, 0, len(ab.cache))
for _, ao := range ab.cache { for _, ao := range ab.cache {
aos = append(aos, ao) aos = append(aos, ao)
} }
return aos, nil return aos, nil
} }
@ -116,14 +137,16 @@ func (ab *addressBook) ListAddressObjects() ([]carddav.AddressObject, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
ab.locker.Lock()
ab.total = total ab.total = total
ab.locker.Unlock()
for _, contact := range contacts { for _, contact := range contacts {
if _, ok := ab.cache[contact.ID]; !ok { if _, ok := ab.addressObject(contact.ID); !ok {
ab.cache[contact.ID] = &addressObject{ ab.cacheAddressObject(&addressObject{
c: ab.c, c: ab.c,
contact: contact, contact: contact,
} })
} }
} }
@ -131,31 +154,30 @@ func (ab *addressBook) ListAddressObjects() ([]carddav.AddressObject, error) {
var aos []carddav.AddressObject var aos []carddav.AddressObject
page := 0 page := 0
for { for {
total, contacts, err := ab.c.ListContactsExport(page, 0) _, contacts, err := ab.c.ListContactsExport(page, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ab.total = total
if aos == nil { if aos == nil {
aos = make([]carddav.AddressObject, 0, total) aos = make([]carddav.AddressObject, 0, total)
} }
for _, contact := range contacts { for _, contact := range contacts {
ao, ok := ab.cache[contact.ID] ao, ok := ab.addressObject(contact.ID)
if !ok { if !ok {
ao = &addressObject{ ao = &addressObject{
c: ab.c, c: ab.c,
contact: &protonmail.Contact{ID: contact.ID}, contact: &protonmail.Contact{ID: contact.ID},
} }
ab.cache[contact.ID] = ao ab.cacheAddressObject(ao)
} }
ao.contact.Cards = contact.Cards ao.contact.Cards = contact.Cards
aos = append(aos, ao) aos = append(aos, ao)
} }
if len(aos) == total || len(contacts) == 0 { if len(aos) >= total || len(contacts) == 0 {
break break
} }
page++ page++
@ -165,7 +187,7 @@ func (ab *addressBook) ListAddressObjects() ([]carddav.AddressObject, error) {
} }
func (ab *addressBook) GetAddressObject(id string) (carddav.AddressObject, error) { func (ab *addressBook) GetAddressObject(id string) (carddav.AddressObject, error) {
if ao, ok := ab.cache[id]; ok { if ao, ok := ab.addressObject(id); ok {
return ao, nil return ao, nil
} else if ab.cacheComplete() { } else if ab.cacheComplete() {
return nil, carddav.ErrNotFound return nil, carddav.ErrNotFound
@ -180,14 +202,47 @@ func (ab *addressBook) GetAddressObject(id string) (carddav.AddressObject, error
c: ab.c, c: ab.c,
contact: contact, contact: contact,
} }
ab.cache[id] = ao ab.cacheAddressObject(ao)
return ao, nil return ao, nil
} }
func NewHandler(c *protonmail.Client) http.Handler { func (ab *addressBook) receiveEvents(events <-chan *protonmail.Event) {
return carddav.NewHandler(&addressBook{ for event := range events {
ab.locker.Lock()
if event.Refresh == 1 {
ab.cache = make(map[string]*addressObject)
ab.total = -1
} else if len(event.Contacts) > 0 {
for _, eventContact := range event.Contacts {
switch eventContact.Action {
case protonmail.EventCreate:
ab.total++
fallthrough
case protonmail.EventUpdate:
ab.cache[eventContact.ID] = &addressObject{
c: ab.c,
contact: eventContact.Contact,
}
case protonmail.EventDelete:
delete(ab.cache, eventContact.ID)
ab.total--
}
}
}
ab.locker.Unlock()
}
}
func NewHandler(c *protonmail.Client, events <-chan *protonmail.Event) http.Handler {
ab := &addressBook{
c: c, c: c,
cache: make(map[string]*addressObject), cache: make(map[string]*addressObject),
total: -1, total: -1,
}) }
if events != nil {
go ab.receiveEvents(events)
}
return carddav.NewHandler(ab)
} }

View File

@ -12,6 +12,7 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"time"
"github.com/emersion/hydroxide/carddav" "github.com/emersion/hydroxide/carddav"
"github.com/emersion/hydroxide/protonmail" "github.com/emersion/hydroxide/protonmail"
@ -122,6 +123,26 @@ func authenticate(c *protonmail.Client, cachedAuth *cachedAuth) error {
return err return err
} }
func receiveEvents(c *protonmail.Client, last string, ch chan<- *protonmail.Event) {
t := time.NewTicker(time.Minute)
defer t.Stop()
for range t.C {
event, err := c.GetEvent(last)
if err != nil {
log.Println("Cannot receive event:", err)
continue
}
if event.ID == last {
continue
}
last = event.ID
ch <- event
}
}
type session struct { type session struct {
h http.Handler h http.Handler
hashedSecretKey []byte hashedSecretKey []byte
@ -286,7 +307,9 @@ func main() {
return return
} }
h = carddav.NewHandler(c) events := make(chan *protonmail.Event)
go receiveEvents(c, cachedAuth.EventID, events)
h = carddav.NewHandler(c, events)
sessions[username] = &session{ sessions[username] = &session{
h: h, h: h,

57
protonmail/events.go Normal file
View File

@ -0,0 +1,57 @@
package protonmail
import (
"net/http"
)
type Event struct {
ID string `json:"EventID"`
Refresh int
//Messages
Contacts []*EventContact
//ContactEmails
//Labels
//User
//Members
//Domains
//Organization
//MessageCounts
//ConversationCounts
//UsedSpace
Notices []string
}
type EventAction int
const (
EventDelete EventAction = iota
EventCreate
EventUpdate
)
type EventContact struct {
ID string
Action EventAction
Contact *Contact
}
func (c *Client) GetEvent(last string) (*Event, error) {
if last == "" {
last = "latest"
}
req, err := c.newRequest(http.MethodGet, "/events/"+last, nil)
if err != nil {
return nil, err
}
var respData struct {
resp
*Event
}
if err := c.doJSON(req, &respData); err != nil {
return nil, err
}
return respData.Event, nil
}