-
Notifications
You must be signed in to change notification settings - Fork 2
/
events.go
247 lines (211 loc) · 6.59 KB
/
events.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// Copyright (c) 2022 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package pmapi
import (
"context"
"encoding/json"
"net/mail"
"github.com/go-resty/resty/v2"
)
// Event represents changes since the last check.
type Event struct {
// The current event ID.
EventID string
// If set to one, all cached data must be fetched again.
Refresh int
// If set to one, fetch more events.
More Boolean
// Changes applied to messages.
Messages []*EventMessage
// Counts of messages per labels.
MessageCounts []*MessagesCount
// Changes applied to labels.
Labels []*EventLabel
// Current user status.
User *User
// Changes to addresses.
Addresses []*EventAddress
// Messages to show to the user.
Notices []string
// Update of used user space
UsedSpace *int64
}
// EventAction is the action that created a change.
type EventAction int
const (
EventDelete EventAction = iota // Item has been deleted.
EventCreate // Item has been created.
EventUpdate // Item has been updated.
EventUpdateFlags // For messages: flags have been updated.
)
// Flags for event refresh.
const (
EventRefreshMail = 1
EventRefreshContact = 2
EventRefreshAll = 255
)
// maxNumberOfMergedEvents limits how many events are merged into one. It means
// when GetEvent is called and event returns there is more events, it will
// automatically fetch next one and merge it up to this number of events.
const maxNumberOfMergedEvents = 50
// EventItem is an item that has changed.
type EventItem struct {
ID string
Action EventAction
}
// EventMessage is a message that has changed.
type EventMessage struct {
EventItem
// If the message has been created, the new message.
Created *Message `json:"-"`
// If the message has been updated, the updated fields.
Updated *EventMessageUpdated `json:"-"`
}
// eventMessage defines a new type to prevent MarshalJSON/UnmarshalJSON infinite loops.
type eventMessage EventMessage
type rawEventMessage struct {
eventMessage
// This will be parsed depending on the action.
Message json.RawMessage `json:",omitempty"`
}
func (em *EventMessage) UnmarshalJSON(b []byte) (err error) {
var raw rawEventMessage
if err := json.Unmarshal(b, &raw); err != nil {
return err
}
*em = EventMessage(raw.eventMessage)
switch em.Action {
case EventCreate:
em.Created = &Message{ID: raw.ID}
return json.Unmarshal(raw.Message, em.Created)
case EventUpdate, EventUpdateFlags:
em.Updated = &EventMessageUpdated{ID: raw.ID}
return json.Unmarshal(raw.Message, em.Updated)
case EventDelete:
return nil
}
return nil
}
func (em *EventMessage) MarshalJSON() ([]byte, error) {
var raw rawEventMessage
raw.eventMessage = eventMessage(*em)
var err error
switch em.Action {
case EventCreate:
raw.Message, err = json.Marshal(em.Created)
case EventUpdate, EventUpdateFlags:
raw.Message, err = json.Marshal(em.Updated)
case EventDelete:
}
if err != nil {
return nil, err
}
return json.Marshal(raw)
}
// EventMessageUpdated contains changed fields for an updated message.
type EventMessageUpdated struct {
ID string
Subject *string
Unread *Boolean
Flags *int64
Sender *mail.Address
ToList *[]*mail.Address
CCList *[]*mail.Address
BCCList *[]*mail.Address
Time int64
// Fields only present for EventUpdateFlags.
LabelIDs []string
LabelIDsAdded []string
LabelIDsRemoved []string
}
// EventLabel is a label that has changed.
type EventLabel struct {
EventItem
Label *Label
}
// EventAddress is an address that has changed.
type EventAddress struct {
EventItem
Address *Address
}
// GetEvent returns a summary of events that occurred since last. To get the latest event,
// provide an empty last value. The latest event is always empty.
func (c *client) GetEvent(ctx context.Context, eventID string) (*Event, error) {
return c.getEvent(ctx, eventID, 1)
}
func (c *client) getEvent(ctx context.Context, eventID string, numberOfMergedEvents int) (*Event, error) {
if eventID == "" {
eventID = "latest"
}
var event *Event
if _, err := c.do(ctx, func(r *resty.Request) (*resty.Response, error) {
return r.SetResult(&event).Get("/events/" + eventID)
}); err != nil {
return nil, err
}
// API notifies about used space two ways:
// - by `event.User.UsedSpace`
// - by `event.UsedSpace`
//
// Because event merging is implemented for User object we copy the
// value from event.UsedSpace to event.User.UsedSpace and continue with
// user.
if event.UsedSpace != nil {
if event.User == nil {
event.User = &User{UsedSpace: event.UsedSpace}
} else {
event.User.UsedSpace = event.UsedSpace
}
}
if event.More && numberOfMergedEvents < maxNumberOfMergedEvents {
nextEvent, err := c.getEvent(ctx, event.EventID, numberOfMergedEvents+1)
if err != nil {
return nil, err
}
event = mergeEvents(event, nextEvent)
}
return event, nil
}
// mergeEvents combines an old events and a new events object.
// This is not as simple as just blindly joining the two because some things should only be taken from the new events.
func mergeEvents(eventsOld *Event, eventsNew *Event) (mergedEvents *Event) {
return &Event{
EventID: eventsNew.EventID,
Refresh: eventsOld.Refresh | eventsNew.Refresh,
More: eventsNew.More,
Messages: append(eventsOld.Messages, eventsNew.Messages...),
MessageCounts: append(eventsOld.MessageCounts, eventsNew.MessageCounts...),
Labels: append(eventsOld.Labels, eventsNew.Labels...),
User: mergeUserEvents(eventsOld.User, eventsNew.User),
Addresses: append(eventsOld.Addresses, eventsNew.Addresses...),
Notices: append(eventsOld.Notices, eventsNew.Notices...),
}
}
func mergeUserEvents(userOld, userNew *User) *User {
if userNew == nil {
return userOld
}
if userOld != nil {
if userNew.MaxSpace == nil {
userNew.MaxSpace = userOld.MaxSpace
}
if userNew.UsedSpace == nil {
userNew.UsedSpace = userOld.UsedSpace
}
}
return userNew
}