Merge lp:~chipaca/account-polld/intrainvocatory-coalescence into lp:~ubuntu-push-hackers/account-polld/trunk

Proposed by John Lenton
Status: Merged
Approved by: John Lenton
Approved revision: 98
Merged at revision: 101
Proposed branch: lp:~chipaca/account-polld/intrainvocatory-coalescence
Merge into: lp:~ubuntu-push-hackers/account-polld/trunk
Diff against target: 792 lines (+284/-177)
9 files modified
cmd/account-polld/account_manager.go (+4/-4)
cmd/account-polld/main.go (+62/-14)
plugins/facebook/facebook.go (+47/-56)
plugins/facebook/facebook_test.go (+52/-24)
plugins/gmail/api.go (+1/-1)
plugins/gmail/gmail.go (+31/-22)
plugins/plugins.go (+16/-1)
plugins/twitter/twitter.go (+65/-53)
plugins/twitter/twitter_test.go (+6/-2)
To merge this branch: bzr merge lp:~chipaca/account-polld/intrainvocatory-coalescence
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Roberto Alsina (community) Approve
Review via email: mp+240133@code.launchpad.net

Commit message

first pass at grouping notifications by plugin (next pass: make gmail track threads, not messages).

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Roberto Alsina (ralsina) :
review: Approve
98. By John Lenton

fixes lp:1400749

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/account-polld/account_manager.go'
2--- cmd/account-polld/account_manager.go 2014-10-02 13:58:49 +0000
3+++ cmd/account-polld/account_manager.go 2014-12-09 15:20:23 +0000
4@@ -120,7 +120,7 @@
5 return
6 }
7
8- if n, err := a.plugin.Poll(&a.authData); err != nil {
9+ if bs, err := a.plugin.Poll(&a.authData); err != nil {
10 log.Print("Error while polling ", a.authData.AccountId, ": ", err)
11
12 // If the error indicates that the authentication
13@@ -132,10 +132,10 @@
14 }
15 a.doneChan <- err
16 } else {
17- log.Println("Account", a.authData.AccountId, "has", len(n), "updates to report")
18- if len(n) > 0 {
19- a.postWatch <- &PostWatch{messages: n, appId: a.plugin.ApplicationId()}
20+ for _, b := range bs {
21+ log.Println("Account", a.authData.AccountId, "has", len(b.Messages), b.Tag, "updates to report")
22 }
23+ a.postWatch <- &PostWatch{batches: bs, appId: a.plugin.ApplicationId()}
24 a.doneChan <- nil
25 }
26 }
27
28=== modified file 'cmd/account-polld/main.go'
29--- cmd/account-polld/main.go 2014-10-02 13:58:49 +0000
30+++ cmd/account-polld/main.go 2014-12-09 15:20:23 +0000
31@@ -37,8 +37,8 @@
32 )
33
34 type PostWatch struct {
35- appId plugins.ApplicationId
36- messages []plugins.PushMessage
37+ appId plugins.ApplicationId
38+ batches []*plugins.PushMessageBatch
39 }
40
41 /* Use identifiers and API keys provided by the respective webapps which are the official
42@@ -152,18 +152,66 @@
43
44 func postOffice(bus *dbus.Connection, postWatch chan *PostWatch) {
45 for post := range postWatch {
46- for _, n := range post.messages {
47- var pushMessage string
48- if out, err := json.Marshal(n); err == nil {
49- pushMessage = string(out)
50- } else {
51- log.Printf("Cannot marshall %#v to json: %s", n, err)
52- continue
53- }
54- obj := bus.Object(POSTAL_SERVICE, pushObjectPath(post.appId))
55- if _, err := obj.Call(POSTAL_INTERFACE, "Post", post.appId, pushMessage); err != nil {
56- log.Println("Cannot call the Post Office:", err)
57- log.Println("Message missed posting:", pushMessage)
58+ obj := bus.Object(POSTAL_SERVICE, pushObjectPath(post.appId))
59+ pers, err := obj.Call(POSTAL_INTERFACE, "ListPersistent", post.appId)
60+ if err != nil {
61+ log.Println("Could not list previous messages:", err)
62+ continue
63+ }
64+ var tags []string
65+ tagmap := make(map[string]int)
66+ if err := pers.Args(&tags); err != nil {
67+ log.Println("Could not get tags:", err)
68+ continue
69+ }
70+ log.Printf("Previous messages: %#v\n", tags)
71+ for _, tag := range tags {
72+ tagmap[tag]++
73+ }
74+
75+ for _, batch := range post.batches {
76+ // add individual notifications upto the batch limit
77+ // (minus currently presented notifications). If
78+ // overflowed and no overflow present, present that.
79+ var notifs []*plugins.PushMessage
80+ add := batch.Limit - tagmap[batch.Tag]
81+ if add > 0 {
82+ // there are less notifications presented than
83+ // the limit; we can show some
84+ if len(batch.Messages) < add {
85+ notifs = batch.Messages
86+ } else {
87+ notifs = batch.Messages[:add]
88+ }
89+ }
90+ for _, n := range notifs {
91+ n.Notification.Tag = batch.Tag
92+ }
93+ ofTag := batch.Tag + "-overflow"
94+ if len(notifs) < len(batch.Messages) {
95+ // overflow
96+ n := batch.OverflowHandler(batch.Messages[len(notifs):])
97+ n.Notification.Tag = ofTag
98+ if tagmap[ofTag] != 0 {
99+ // sneakily, don't replace the overflow card,
100+ // but do play the sound & etc
101+ n.Notification.Card = nil
102+ }
103+ notifs = append(notifs, n)
104+ }
105+
106+ for _, n := range notifs {
107+ var pushMessage string
108+ if out, err := json.Marshal(n); err == nil {
109+ pushMessage = string(out)
110+ } else {
111+ log.Printf("Cannot marshall %#v to json: %s", n, err)
112+ continue
113+ }
114+ if _, err := obj.Call(POSTAL_INTERFACE, "Post", post.appId, pushMessage); err != nil {
115+ log.Println("Cannot call the Post Office:", err)
116+ log.Println("Message missed posting:", pushMessage)
117+ }
118 }
119 }
120 }
121
122=== modified file 'plugins/facebook/facebook.go'
123--- plugins/facebook/facebook.go 2014-09-25 14:02:14 +0000
124+++ plugins/facebook/facebook.go 2014-12-09 15:20:23 +0000
125@@ -148,36 +148,20 @@
126 return validNotifications
127 }
128
129-func (p *fbPlugin) buildPushMessages(notifications []Notification, doc Document, max int, consolidatedIndexStart int) []plugins.PushMessage {
130- pushMsg := []plugins.PushMessage{}
131- for _, n := range notifications {
132- msg := n.buildPushMessage()
133- pushMsg = append(pushMsg, *msg)
134- if len(pushMsg) == max {
135- break
136- }
137- }
138- // Now we consolidate the remaining statuses
139- if len(notifications) > len(pushMsg) && len(notifications) >= consolidatedIndexStart {
140- usernamesMap := doc.getConsolidatedMessagesUsernames(consolidatedIndexStart)
141- usernames := []string{}
142- for _, v := range usernamesMap {
143- usernames = append(usernames, v)
144- // we don't too many usernames listed, this is a hard number
145- if len(usernames) > 10 {
146- usernames = append(usernames, "...")
147- break
148- }
149- }
150- if len(usernames) > 0 {
151- consolidatedMsg := doc.getConsolidatedMessage(usernames)
152- pushMsg = append(pushMsg, *consolidatedMsg)
153- }
154- }
155- return pushMsg
156+func (p *fbPlugin) buildPushMessages(notifications []Notification, doc Document, max int, consolidatedIndexStart int) *plugins.PushMessageBatch {
157+ pushMsg := make([]*plugins.PushMessage, len(notifications))
158+ for i, n := range notifications {
159+ pushMsg[i] = n.buildPushMessage()
160+ }
161+ return &plugins.PushMessageBatch{
162+ Messages: pushMsg,
163+ Limit: max,
164+ OverflowHandler: doc.handleOverflow,
165+ Tag: doc.getTag(),
166+ }
167 }
168
169-func (p *fbPlugin) parseResponse(resp *http.Response) ([]plugins.PushMessage, error) {
170+func (p *fbPlugin) parseResponse(resp *http.Response) (*plugins.PushMessageBatch, error) {
171 var result notificationDoc
172 if err := p.decodeResponse(resp, &result); err != nil {
173 return nil, err
174@@ -188,7 +172,7 @@
175 return pushMsgs, nil
176 }
177
178-func (p *fbPlugin) parseInboxResponse(resp *http.Response) ([]plugins.PushMessage, error) {
179+func (p *fbPlugin) parseInboxResponse(resp *http.Response) (*plugins.PushMessageBatch, error) {
180 var result inboxDoc
181 if err := p.decodeResponse(resp, &result); err != nil {
182 return nil, err
183@@ -198,7 +182,7 @@
184 return pushMsgs, nil
185 }
186
187-func (p *fbPlugin) getNotifications(authData *accounts.AuthData) ([]plugins.PushMessage, error) {
188+func (p *fbPlugin) getNotifications(authData *accounts.AuthData) (*plugins.PushMessageBatch, error) {
189 resp, err := doRequest(authData, "me/notifications")
190 if err != nil {
191 log.Println("facebook plugin: notifications poll failed: ", err)
192@@ -212,7 +196,7 @@
193 return notifications, nil
194 }
195
196-func (p *fbPlugin) getInbox(authData *accounts.AuthData) ([]plugins.PushMessage, error) {
197+func (p *fbPlugin) getInbox(authData *accounts.AuthData) (*plugins.PushMessageBatch, error) {
198 resp, err := doRequest(authData, "me/inbox?fields=unread,unseen,comments.limit(1)")
199 if err != nil {
200 log.Println("facebook plugin: inbox poll failed: ", err)
201@@ -226,7 +210,7 @@
202 return inbox, nil
203 }
204
205-func (p *fbPlugin) Poll(authData *accounts.AuthData) ([]plugins.PushMessage, error) {
206+func (p *fbPlugin) Poll(authData *accounts.AuthData) ([]*plugins.PushMessageBatch, error) {
207 // This envvar check is to ease testing.
208 if token := os.Getenv("ACCOUNT_POLLD_TOKEN_FACEBOOK"); token != "" {
209 authData.AccessToken = token
210@@ -237,8 +221,13 @@
211 if notifErr != nil && inboxErr != nil {
212 return nil, fmt.Errorf("poll failed with '%s' and '%s'", notifErr, inboxErr)
213 }
214- messages := append(notifications, inbox...)
215- return messages, nil
216+ if notifErr != nil {
217+ return []*plugins.PushMessageBatch{inbox}, nil
218+ }
219+ if inboxErr != nil {
220+ return []*plugins.PushMessageBatch{notifications}, nil
221+ }
222+ return []*plugins.PushMessageBatch{notifications, inbox}, nil
223 }
224
225 func toEpoch(stamp timeStamp) int64 {
226@@ -350,19 +339,19 @@
227 Message string `json:message`
228 }
229
230-func (doc *inboxDoc) getConsolidatedMessagesUsernames(idxStart int) map[string]string {
231- usernamesMap := make(map[string]string)
232- for _, t := range doc.Data[idxStart-1:] {
233- message := t.Comments.Data[0]
234- userId := message.From.Id
235- if _, ok := usernamesMap[userId]; !ok {
236- usernamesMap[userId] = message.From.Name
237+func (doc *inboxDoc) getTag() string {
238+ return "inbox"
239+}
240+
241+func (doc *inboxDoc) handleOverflow(pushMsg []*plugins.PushMessage) *plugins.PushMessage {
242+ usernames := []string{}
243+ for _, m := range pushMsg {
244+ usernames = append(usernames, m.Notification.Card.Summary)
245+ if len(usernames) > 10 {
246+ usernames[10] = "…"
247+ break
248 }
249 }
250- return usernamesMap
251-}
252-
253-func (doc *inboxDoc) getConsolidatedMessage(usernames []string) *plugins.PushMessage {
254 // TRANSLATORS: This represents a message summary about more facebook messages
255 summary := gettext.Gettext("Multiple more messages")
256 // TRANSLATORS: This represents a message body with the comma separated facebook usernames
257@@ -401,17 +390,19 @@
258 return fmt.Sprintf("id: %s, dated: %s, unread: %d, unseen: %d", t.Id, t.UpdatedTime, t.Unread, t.Unseen)
259 }
260
261-func (doc *notificationDoc) getConsolidatedMessagesUsernames(idxStart int) map[string]string {
262- usernamesMap := make(map[string]string)
263- for _, n := range doc.Data[idxStart:] {
264- if _, ok := usernamesMap[n.From.Id]; !ok {
265- usernamesMap[n.From.Id] = n.From.Name
266+func (doc *notificationDoc) getTag() string {
267+ return "notification"
268+}
269+
270+func (doc *notificationDoc) handleOverflow(pushMsg []*plugins.PushMessage) *plugins.PushMessage {
271+ usernames := []string{}
272+ for _, m := range pushMsg {
273+ usernames = append(usernames, m.Notification.Card.Summary)
274+ if len(usernames) > 10 {
275+ usernames[10] = "…"
276+ break
277 }
278 }
279- return usernamesMap
280-}
281-
282-func (doc *notificationDoc) getConsolidatedMessage(usernames []string) *plugins.PushMessage {
283 // TRANSLATORS: This represents a notification summary about more facebook notifications
284 summary := gettext.Gettext("Multiple more notifications")
285 // TRANSLATORS: This represents a notification body with the comma separated facebook usernames
286@@ -447,8 +438,8 @@
287 }
288
289 type Document interface {
290- getConsolidatedMessage([]string) *plugins.PushMessage
291- getConsolidatedMessagesUsernames(int) map[string]string
292+ getTag() string
293+ handleOverflow([]*plugins.PushMessage) *plugins.PushMessage
294 size() int
295 notification(int) Notification
296 }
297
298=== modified file 'plugins/facebook/facebook_test.go'
299--- plugins/facebook/facebook_test.go 2014-09-02 18:32:07 +0000
300+++ plugins/facebook/facebook_test.go 2014-12-09 15:20:23 +0000
301@@ -354,8 +354,10 @@
302 Body: closeWrapper{bytes.NewReader([]byte(notificationsBody))},
303 }
304 p := &fbPlugin{}
305- messages, err := p.parseResponse(resp)
306+ batch, err := p.parseResponse(resp)
307 c.Assert(err, IsNil)
308+ c.Assert(batch, NotNil)
309+ messages := batch.Messages
310 c.Assert(len(messages), Equals, 2)
311 c.Check(messages[0].Notification.Card.Summary, Equals, "Sender")
312 c.Check(messages[0].Notification.Card.Body, Equals, "Sender posted on your timeline: \"The message...\"")
313@@ -370,15 +372,18 @@
314 Body: closeWrapper{bytes.NewReader([]byte(largeNotificationsBody))},
315 }
316 p := &fbPlugin{}
317- messages, err := p.parseResponse(resp)
318+ batch, err := p.parseResponse(resp)
319 c.Assert(err, IsNil)
320- c.Assert(len(messages), Equals, 3)
321+ c.Assert(batch, NotNil)
322+ messages := batch.Messages
323+ c.Assert(len(messages), Equals, 4)
324 c.Check(messages[0].Notification.Card.Summary, Equals, "Sender")
325 c.Check(messages[0].Notification.Card.Body, Equals, "Sender posted on your timeline: \"The message...\"")
326 c.Check(messages[1].Notification.Card.Summary, Equals, "Sender2")
327 c.Check(messages[1].Notification.Card.Body, Equals, "Sender2's birthday was on July 7.")
328- c.Check(messages[2].Notification.Card.Summary, Equals, "Multiple more notifications")
329- c.Check(messages[2].Notification.Card.Body, Equals, "From Sender3, Sender2")
330+ ofMsg := batch.OverflowHandler(messages[2:])
331+ c.Check(ofMsg.Notification.Card.Summary, Equals, "Multiple more notifications")
332+ c.Check(ofMsg.Notification.Card.Body, Equals, "From Sender3, Sender2")
333 c.Check(p.state.LastUpdate, Equals, timeStamp("2014-07-12T09:51:57+0000"))
334 }
335
336@@ -388,8 +393,10 @@
337 Body: closeWrapper{bytes.NewReader([]byte(notificationsBody))},
338 }
339 p := &fbPlugin{state: fbState{LastUpdate: "2014-07-08T06:17:52+0000"}}
340- messages, err := p.parseResponse(resp)
341+ batch, err := p.parseResponse(resp)
342 c.Assert(err, IsNil)
343+ c.Assert(batch, NotNil)
344+ messages := batch.Messages
345 c.Assert(len(messages), Equals, 1)
346 c.Check(messages[0].Notification.Card.Summary, Equals, "Sender")
347 c.Check(messages[0].Notification.Card.Body, Equals, "Sender posted on your timeline: \"The message...\"")
348@@ -456,15 +463,20 @@
349 Body: closeWrapper{bytes.NewReader([]byte(inboxBody))},
350 }
351 p := &fbPlugin{}
352- messages, err := p.parseInboxResponse(resp)
353+ batch, err := p.parseInboxResponse(resp)
354 c.Assert(err, IsNil)
355+ c.Assert(batch, NotNil)
356+ messages := batch.Messages
357 c.Assert(len(messages), Equals, 3)
358 c.Check(messages[0].Notification.Card.Summary, Equals, "Pollod Magnifico")
359 c.Check(messages[0].Notification.Card.Body, Equals, "Hola mundo!")
360 c.Check(messages[1].Notification.Card.Summary, Equals, "Pollitod Magnifico")
361 c.Check(messages[1].Notification.Card.Body, Equals, "Hola!")
362- c.Check(messages[2].Notification.Card.Summary, Equals, "Multiple more messages")
363- c.Check(messages[2].Notification.Card.Body, Equals, "From Pollitod Magnifico, A Friend")
364+
365+ ofMsg := batch.OverflowHandler(messages[batch.Limit:])
366+
367+ c.Check(ofMsg.Notification.Card.Summary, Equals, "Multiple more messages")
368+ c.Check(ofMsg.Notification.Card.Body, Equals, "From A Friend")
369 c.Check(p.state.LastInboxUpdate, Equals, timeStamp("2014-08-25T18:39:32+0000"))
370 }
371
372@@ -518,8 +530,9 @@
373 var doc inboxDoc
374 p.decodeResponse(resp, &doc)
375 notifications := p.filterNotifications(&doc, &p.state.LastInboxUpdate)
376- pushMsgs := p.buildPushMessages(notifications, &doc, doc.size(), doc.size())
377- c.Check(pushMsgs, HasLen, doc.size())
378+ batch := p.buildPushMessages(notifications, &doc, doc.size(), doc.size())
379+ c.Assert(batch, NotNil)
380+ c.Check(batch.Messages, HasLen, doc.size())
381 }
382
383 func (s *S) TestBuildPushMessagesConsolidate(c *C) {
384@@ -533,9 +546,11 @@
385 p.decodeResponse(resp, &doc)
386 notifications := p.filterNotifications(&doc, &p.state.LastInboxUpdate)
387 max := doc.size() - 2
388- pushMsgs := p.buildPushMessages(notifications, &doc, max, max)
389- // we should get max + 1 messages
390- c.Check(pushMsgs, HasLen, max+1)
391+ batch := p.buildPushMessages(notifications, &doc, max, max)
392+ c.Assert(batch, NotNil)
393+ // we should get all the messages in a batch with Limit=max
394+ c.Check(batch.Messages, HasLen, len(notifications))
395+ c.Check(batch.Limit, Equals, max)
396 }
397
398 func (s *S) TestStateFromStorageInitialState(c *C) {
399@@ -639,8 +654,10 @@
400 authData := accounts.AuthData{}
401 authData.AccessToken = "foo"
402 p := &fbPlugin{state: state, accountId: 32}
403- msgs, err := p.getInbox(&authData)
404- c.Check(err, IsNil)
405+ batch, err := p.getInbox(&authData)
406+ c.Assert(err, IsNil)
407+ c.Assert(batch, NotNil)
408+ msgs := batch.Messages
409 c.Check(msgs, HasLen, 3)
410 }
411
412@@ -685,8 +702,10 @@
413 authData := accounts.AuthData{}
414 authData.AccessToken = "foo"
415 p := &fbPlugin{state: state, accountId: 32}
416- msgs, err := p.getNotifications(&authData)
417- c.Check(err, IsNil)
418+ batch, err := p.getNotifications(&authData)
419+ c.Assert(err, IsNil)
420+ c.Assert(batch, NotNil)
421+ msgs := batch.Messages
422 c.Check(msgs, HasLen, 2)
423 }
424
425@@ -735,9 +754,16 @@
426 authData := accounts.AuthData{}
427 authData.AccessToken = "foo"
428 p := &fbPlugin{state: state, accountId: 32}
429- msgs, err := p.Poll(&authData)
430- c.Check(err, IsNil)
431- c.Check(msgs, HasLen, 5)
432+ batches, err := p.Poll(&authData)
433+ c.Assert(err, IsNil)
434+ c.Assert(batches, NotNil)
435+ c.Assert(batches, HasLen, 2)
436+ c.Assert(batches[0], NotNil)
437+ c.Assert(batches[1], NotNil)
438+ c.Check(batches[0].Tag, Equals, "notification")
439+ c.Check(batches[0].Messages, HasLen, 2)
440+ c.Check(batches[1].Tag, Equals, "inbox")
441+ c.Check(batches[1].Messages, HasLen, 3)
442 }
443
444 func (s *S) TestPollSingleError(c *C) {
445@@ -755,9 +781,11 @@
446 authData := accounts.AuthData{}
447 authData.AccessToken = "foo"
448 p := &fbPlugin{state: state, accountId: 32}
449- msgs, err := p.Poll(&authData)
450- c.Check(err, IsNil)
451- c.Check(msgs, HasLen, 3)
452+ batches, err := p.Poll(&authData)
453+ c.Assert(err, IsNil)
454+ c.Assert(batches, HasLen, 1)
455+ c.Assert(batches[0], NotNil)
456+ c.Check(batches[0].Messages, HasLen, 3)
457 }
458
459 func (s *S) TestPollError(c *C) {
460
461=== modified file 'plugins/gmail/api.go'
462--- plugins/gmail/api.go 2014-08-05 18:30:24 +0000
463+++ plugins/gmail/api.go 2014-12-09 15:20:23 +0000
464@@ -26,7 +26,7 @@
465
466 const gmailTime = "Mon, 2 Jan 2006 15:04:05 -0700"
467
468-type pushes map[string]plugins.PushMessage
469+type pushes map[string]*plugins.PushMessage
470 type headers map[string]string
471
472 // messageList holds a response to call to Users.messages: list
473
474=== modified file 'plugins/gmail/gmail.go'
475--- plugins/gmail/gmail.go 2014-08-23 20:00:19 +0000
476+++ plugins/gmail/gmail.go 2014-12-09 15:20:23 +0000
477@@ -100,7 +100,7 @@
478 return plugins.ApplicationId(APP_ID)
479 }
480
481-func (p *GmailPlugin) Poll(authData *accounts.AuthData) ([]plugins.PushMessage, error) {
482+func (p *GmailPlugin) Poll(authData *accounts.AuthData) ([]*plugins.PushMessageBatch, error) {
483 // This envvar check is to ease testing.
484 if token := os.Getenv("ACCOUNT_POLLD_TOKEN_GMAIL"); token != "" {
485 authData.AccessToken = token
486@@ -126,7 +126,18 @@
487 return nil, err
488 }
489 }
490- return p.createNotifications(messages)
491+ notif, err := p.createNotifications(messages)
492+ if err != nil {
493+ return nil, err
494+ }
495+ return []*plugins.PushMessageBatch{
496+ &plugins.PushMessageBatch{
497+ Messages: notif,
498+ Limit: individualNotificationsLimit,
499+ OverflowHandler: p.handleOverflow,
500+ Tag: "gmail",
501+ }}, nil
502+
503 }
504
505 func (p *GmailPlugin) reported(id string) bool {
506@@ -134,7 +145,7 @@
507 return ok
508 }
509
510-func (p *GmailPlugin) createNotifications(messages []message) ([]plugins.PushMessage, error) {
511+func (p *GmailPlugin) createNotifications(messages []message) ([]*plugins.PushMessage, error) {
512 timestamp := time.Now()
513 pushMsgMap := make(pushes)
514
515@@ -163,32 +174,30 @@
516 // fmt with label personal and threadId
517 action := fmt.Sprintf(gmailDispatchUrl, "personal", msg.ThreadId)
518 epoch := hdr.getEpoch()
519- pushMsgMap[msg.ThreadId] = *plugins.NewStandardPushMessage(summary, body, action, avatarPath, epoch)
520+ pushMsgMap[msg.ThreadId] = plugins.NewStandardPushMessage(summary, body, action, avatarPath, epoch)
521 } else {
522 log.Print("gmail plugin ", p.accountId, ": skipping message id ", msg.Id, " with date ", msgStamp, " older than ", timeDelta)
523 }
524 }
525- var pushMsg []plugins.PushMessage
526+ pushMsg := make([]*plugins.PushMessage, 0, len(pushMsgMap))
527 for _, v := range pushMsgMap {
528 pushMsg = append(pushMsg, v)
529- if len(pushMsg) == individualNotificationsLimit {
530- break
531- }
532- }
533- if len(pushMsgMap) > individualNotificationsLimit {
534- // TRANSLATORS: This represents a notification summary about more unread emails
535- summary := gettext.Gettext("More unread emails available")
536- // TODO it would probably be better to grab the estimate that google returns in the message list.
537- approxUnreadMessages := len(pushMsgMap) - individualNotificationsLimit
538- // TRANSLATORS: the first %d refers to approximate additionl email message count
539- body := fmt.Sprintf(gettext.Gettext("You have an approximate of %d additional unread messages"), approxUnreadMessages)
540- // fmt with label personal and no threadId
541- action := fmt.Sprintf(gmailDispatchUrl, "personal")
542- epoch := time.Now().Unix()
543- pushMsg = append(pushMsg, *plugins.NewStandardPushMessage(summary, body, action, "", epoch))
544- }
545-
546+ }
547 return pushMsg, nil
548+
549+}
550+func (p *GmailPlugin) handleOverflow(pushMsg []*plugins.PushMessage) *plugins.PushMessage {
551+ // TRANSLATORS: This represents a notification summary about more unread emails
552+ summary := gettext.Gettext("More unread emails available")
553+ // TODO it would probably be better to grab the estimate that google returns in the message list.
554+ approxUnreadMessages := len(pushMsg)
555+ // TRANSLATORS: the first %d refers to approximate additionl email message count
556+ body := fmt.Sprintf(gettext.Gettext("You have an approximate of %d additional unread messages"), approxUnreadMessages)
557+ // fmt with label personal and no threadId
558+ action := fmt.Sprintf(gmailDispatchUrl, "personal")
559+ epoch := time.Now().Unix()
560+
561+ return plugins.NewStandardPushMessage(summary, body, action, "", epoch)
562 }
563
564 func (p *GmailPlugin) parseMessageListResponse(resp *http.Response) ([]message, error) {
565
566=== modified file 'plugins/plugins.go'
567--- plugins/plugins.go 2014-08-27 23:08:59 +0000
568+++ plugins/plugins.go 2014-12-09 15:20:23 +0000
569@@ -44,7 +44,7 @@
570 // ApplicationId returns the APP_ID of the delivery target for Post Office.
571 type Plugin interface {
572 ApplicationId() ApplicationId
573- Poll(*accounts.AuthData) ([]PushMessage, error)
574+ Poll(*accounts.AuthData) ([]*PushMessageBatch, error)
575 }
576
577 // AuthTokens is a map with tokens the plugins are to use to make requests.
578@@ -76,6 +76,21 @@
579 return pm
580 }
581
582+// PushMessageBatch represents a logical grouping of PushMessages that
583+// have a limit on the number of their notifications that want to be
584+// presented to the user at the same time, and a way to handle the
585+// overflow. All Notifications that are part of a Batch share the same
586+// tag (Tag). ${Tag}-overflow is the overflow notification tag.
587+//
588+// TODO: support notifications sharing just the prefix (so the app can
589+// tell them apart by tag).
590+type PushMessageBatch struct {
591+ Messages []*PushMessage
592+ Limit int
593+ OverflowHandler func([]*PushMessage) *PushMessage
594+ Tag string
595+}
596+
597 // PushMessage represents a data structure to be sent over to the
598 // Post Office. It consists of a Notification and a Message.
599 type PushMessage struct {
600
601=== modified file 'plugins/twitter/twitter.go'
602--- plugins/twitter/twitter.go 2014-08-05 20:32:03 +0000
603+++ plugins/twitter/twitter.go 2014-12-09 15:20:23 +0000
604@@ -78,7 +78,7 @@
605 return client.Get(http.DefaultClient, token, u.String(), query)
606 }
607
608-func (p *twitterPlugin) parseStatuses(resp *http.Response) ([]plugins.PushMessage, error) {
609+func (p *twitterPlugin) parseStatuses(resp *http.Response) (*plugins.PushMessageBatch, error) {
610 defer resp.Body.Close()
611 decoder := json.NewDecoder(resp.Body)
612
613@@ -107,35 +107,38 @@
614 }
615 p.lastMentionId = statuses[0].Id
616
617- pushMsg := []plugins.PushMessage{}
618- for _, s := range statuses {
619+ pushMsg := make([]*plugins.PushMessage, len(statuses))
620+ for i, s := range statuses {
621 // TRANSLATORS: The first %s refers to the twitter user's Name, the second %s to the username.
622 summary := fmt.Sprintf(gettext.Gettext("%s. @%s"), s.User.Name, s.User.ScreenName)
623 action := fmt.Sprintf("%s/%s/statuses/%d", twitterDispatchUrlBase, s.User.ScreenName, s.Id)
624 epoch := toEpoch(s.CreatedAt)
625- pushMsg = append(pushMsg, *plugins.NewStandardPushMessage(summary, s.Text, action, s.User.Image, epoch))
626- if len(pushMsg) == maxIndividualStatuses {
627- break
628- }
629- }
630- // Now we consolidate the remaining statuses
631- if len(statuses) > len(pushMsg) {
632- var screennames []string
633- for _, s := range statuses[consolidatedStatusIndexStart:] {
634- screennames = append(screennames, "@"+s.User.ScreenName)
635- }
636- // TRANSLATORS: This represents a notification summary about more twitter mentions available
637- summary := gettext.Gettext("Multiple more mentions")
638- // TRANSLATORS: This represents a notification body with the comma separated twitter usernames
639- body := fmt.Sprintf(gettext.Gettext("From %s"), strings.Join(screennames, ", "))
640- action := fmt.Sprintf("%s/i/connect", twitterDispatchUrlBase)
641- epoch := time.Now().Unix()
642- pushMsg = append(pushMsg, *plugins.NewStandardPushMessage(summary, body, action, "", epoch))
643- }
644- return pushMsg, nil
645-}
646-
647-func (p *twitterPlugin) parseDirectMessages(resp *http.Response) ([]plugins.PushMessage, error) {
648+ pushMsg[i] = plugins.NewStandardPushMessage(summary, s.Text, action, s.User.Image, epoch)
649+ }
650+ return &plugins.PushMessageBatch{
651+ Messages: pushMsg,
652+ Limit: maxIndividualStatuses,
653+ OverflowHandler: p.consolidateStatuses,
654+ Tag: "status",
655+ }, nil
656+}
657+
658+func (p *twitterPlugin) consolidateStatuses(pushMsg []*plugins.PushMessage) *plugins.PushMessage {
659+ screennames := make([]string, len(pushMsg))
660+ for i, m := range pushMsg {
661+ screennames[i] = m.Notification.Card.Summary
662+ }
663+ // TRANSLATORS: This represents a notification summary about more twitter mentions available
664+ summary := gettext.Gettext("Multiple more mentions")
665+ // TRANSLATORS: This represents a notification body with the comma separated twitter usernames
666+ body := fmt.Sprintf(gettext.Gettext("From %s"), strings.Join(screennames, ", "))
667+ action := fmt.Sprintf("%s/i/connect", twitterDispatchUrlBase)
668+ epoch := time.Now().Unix()
669+
670+ return plugins.NewStandardPushMessage(summary, body, action, "", epoch)
671+}
672+
673+func (p *twitterPlugin) parseDirectMessages(resp *http.Response) (*plugins.PushMessageBatch, error) {
674 defer resp.Body.Close()
675 decoder := json.NewDecoder(resp.Body)
676
677@@ -164,35 +167,39 @@
678 }
679 p.lastDirectMessageId = dms[0].Id
680
681- pushMsg := []plugins.PushMessage{}
682- for _, m := range dms {
683+ pushMsg := make([]*plugins.PushMessage, len(dms))
684+ for i, m := range dms {
685 // TRANSLATORS: The first %s refers to the twitter user's Name, the second %s to the username.
686 summary := fmt.Sprintf(gettext.Gettext("%s. @%s"), m.Sender.Name, m.Sender.ScreenName)
687 action := fmt.Sprintf("%s/%s/messages", twitterDispatchUrlBase, m.Sender.ScreenName)
688 epoch := toEpoch(m.CreatedAt)
689- pushMsg = append(pushMsg, *plugins.NewStandardPushMessage(summary, m.Text, action, m.Sender.Image, epoch))
690- if len(pushMsg) == maxIndividualDirectMessages {
691- break
692- }
693- }
694- // Now we consolidate the remaining messages
695- if len(dms) > len(pushMsg) {
696- var senders []string
697- for _, m := range dms[consolidatedDirectMessageIndexStart:] {
698- senders = append(senders, "@"+m.Sender.ScreenName)
699- }
700- // TRANSLATORS: This represents a notification summary about more twitter direct messages available
701- summary := gettext.Gettext("Multiple direct messages available")
702- // TRANSLATORS: This represents a notification body with the comma separated twitter usernames
703- body := fmt.Sprintf(gettext.Gettext("From %s"), strings.Join(senders, ", "))
704- action := fmt.Sprintf("%s/messages", twitterDispatchUrlBase)
705- epoch := time.Now().Unix()
706- pushMsg = append(pushMsg, *plugins.NewStandardPushMessage(summary, body, action, "", epoch))
707- }
708- return pushMsg, nil
709-}
710-
711-func (p *twitterPlugin) Poll(authData *accounts.AuthData) (messages []plugins.PushMessage, err error) {
712+ pushMsg[i] = plugins.NewStandardPushMessage(summary, m.Text, action, m.Sender.Image, epoch)
713+ }
714+
715+ return &plugins.PushMessageBatch{
716+ Messages: pushMsg,
717+ Limit: maxIndividualDirectMessages,
718+ OverflowHandler: p.consolidateDirectMessages,
719+ Tag: "direct-message",
720+ }, nil
721+}
722+
723+func (p *twitterPlugin) consolidateDirectMessages(pushMsg []*plugins.PushMessage) *plugins.PushMessage {
724+ senders := make([]string, len(pushMsg))
725+ for i, m := range pushMsg {
726+ senders[i] = m.Notification.Card.Summary
727+ }
728+ // TRANSLATORS: This represents a notification summary about more twitter direct messages available
729+ summary := gettext.Gettext("Multiple direct messages available")
730+ // TRANSLATORS: This represents a notification body with the comma separated twitter usernames
731+ body := fmt.Sprintf(gettext.Gettext("From %s"), strings.Join(senders, ", "))
732+ action := fmt.Sprintf("%s/messages", twitterDispatchUrlBase)
733+ epoch := time.Now().Unix()
734+
735+ return plugins.NewStandardPushMessage(summary, body, action, "", epoch)
736+}
737+
738+func (p *twitterPlugin) Poll(authData *accounts.AuthData) (batches []*plugins.PushMessageBatch, err error) {
739 url := "statuses/mentions_timeline.json"
740 if p.lastMentionId > 0 {
741 url = fmt.Sprintf("%s?since_id=%d", url, p.lastMentionId)
742@@ -201,7 +208,7 @@
743 if err != nil {
744 return
745 }
746- messages, err = p.parseStatuses(resp)
747+ statuses, err := p.parseStatuses(resp)
748 if err != nil {
749 return
750 }
751@@ -222,7 +229,12 @@
752 p.bootstrap = true
753 return nil, nil
754 }
755- messages = append(messages, dms...)
756+ if statuses != nil && len(statuses.Messages) > 0 {
757+ batches = append(batches, statuses)
758+ }
759+ if dms != nil && len(dms.Messages) > 0 {
760+ batches = append(batches, dms)
761+ }
762 return
763 }
764
765
766=== modified file 'plugins/twitter/twitter_test.go'
767--- plugins/twitter/twitter_test.go 2014-08-05 20:32:03 +0000
768+++ plugins/twitter/twitter_test.go 2014-12-09 15:20:23 +0000
769@@ -411,8 +411,10 @@
770 Body: closeWrapper{bytes.NewReader([]byte(statusesBody))},
771 }
772 p := &twitterPlugin{}
773- messages, err := p.parseStatuses(resp)
774+ batch, err := p.parseStatuses(resp)
775 c.Assert(err, IsNil)
776+ c.Assert(batch, NotNil)
777+ messages := batch.Messages
778 c.Assert(len(messages), Equals, 2)
779 c.Check(messages[0].Notification.Card.Summary, Equals, "Andrew Spode Miller. @spode")
780 c.Check(messages[0].Notification.Card.Body, Equals, "@jasoncosta @themattharris Hey! Going to be in Frisco in October. Was hoping to have a meeting to talk about @thinkwall if you're around?")
781@@ -459,8 +461,10 @@
782 Body: closeWrapper{bytes.NewReader([]byte(directMessagesBody))},
783 }
784 p := &twitterPlugin{}
785- messages, err := p.parseDirectMessages(resp)
786+ batch, err := p.parseDirectMessages(resp)
787 c.Assert(err, IsNil)
788+ c.Assert(batch, NotNil)
789+ messages := batch.Messages
790 c.Assert(len(messages), Equals, 1)
791 c.Check(messages[0].Notification.Card.Summary, Equals, "Sean Cook. @theSeanCook")
792 c.Check(messages[0].Notification.Card.Body, Equals, "booyakasha")

Subscribers

People subscribed via source and target branches