Receive events, fixes #8
This commit is contained in:
parent
907e88f759
commit
d33218dc7c
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue