Merge lp:~chipaca/ubuntu-push/the-push-automatic into lp:ubuntu-push

Proposed by John Lenton
Status: Merged
Approved by: John Lenton
Approved revision: no longer in the source branch.
Merged at revision: 113
Proposed branch: lp:~chipaca/ubuntu-push/the-push-automatic
Merge into: lp:ubuntu-push
Diff against target: 2976 lines (+1434/-486)
26 files modified
bus/emblemcounter/emblemcounter.go (+30/-9)
bus/emblemcounter/emblemcounter_test.go (+27/-7)
bus/haptic/haptic.go (+10/-6)
bus/haptic/haptic_test.go (+9/-2)
bus/notifications/raw.go (+26/-12)
bus/notifications/raw_test.go (+25/-56)
bus/urldispatcher/urldispatcher.go (+22/-3)
bus/urldispatcher/urldispatcher_test.go (+68/-6)
click/click.go (+7/-0)
click/click_test.go (+3/-0)
client/client.go (+9/-17)
client/client_test.go (+6/-28)
client/service/postal.go (+119/-28)
client/service/postal_test.go (+301/-170)
debian/changelog (+21/-0)
launch_helper/helper_output.go (+1/-0)
launch_helper/helper_test.go (+2/-2)
launch_helper/kindpool.go (+75/-8)
launch_helper/kindpool_test.go (+244/-87)
messaging/cmessaging/cmessaging.go (+29/-1)
messaging/cmessaging/cmessaging_c.go (+31/-1)
messaging/messaging.go (+136/-25)
messaging/messaging_test.go (+220/-12)
messaging/reply/reply.go (+3/-0)
sounds/sounds.go (+9/-5)
sounds/sounds_test.go (+1/-1)
To merge this branch: bzr merge lp:~chipaca/ubuntu-push/the-push-automatic
Reviewer Review Type Date Requested Status
Ubuntu Push Hackers Pending
Review via email: mp+228526@code.launchpad.net

Commit message

Merge from automatic.

Description of the change

Merge from automatic.

To post a comment you must log in.
113. By John Lenton

Merge from automatic.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bus/emblemcounter/emblemcounter.go'
--- bus/emblemcounter/emblemcounter.go 2014-07-09 10:22:09 +0000
+++ bus/emblemcounter/emblemcounter.go 2014-07-29 16:56:22 +0000
@@ -49,16 +49,37 @@
4949
50// Look for an EmblemCounter section in a Notification and, if50// Look for an EmblemCounter section in a Notification and, if
51// present, presents it to the user.51// present, presents it to the user.
52func (ctr *EmblemCounter) Present(app *click.AppId, nid string, notification *launch_helper.Notification) {52func (ctr *EmblemCounter) Present(app *click.AppId, nid string, notification *launch_helper.Notification) bool {
53 if notification == nil || notification.EmblemCounter == nil {53 if notification == nil {
54 ctr.log.Debugf("[%s] no notification or no EmblemCounter in the notification; doing nothing: %#v", nid, notification)54 panic("please check notification is not nil before calling present")
55 return
56 }55 }
56
57 ec := notification.EmblemCounter57 ec := notification.EmblemCounter
58
59 if ec == nil {
60 ctr.log.Debugf("[%s] notification has no EmblemCounter: %#v", nid, ec)
61 return false
62 }
58 ctr.log.Debugf("[%s] setting emblem counter for %s to %d (visible: %t)", nid, app.Base(), ec.Count, ec.Visible)63 ctr.log.Debugf("[%s] setting emblem counter for %s to %d (visible: %t)", nid, app.Base(), ec.Count, ec.Visible)
5964 return ctr.SetCounter(app, ec.Count, ec.Visible)
60 quoted := string(nih.Quote([]byte(app.Base())))65}
6166
62 ctr.bus.SetProperty("count", "/"+quoted, dbus.Variant{ec.Count})67// SetCounter sets an emblem counter on the launcher for app to count (if
63 ctr.bus.SetProperty("countVisible", "/"+quoted, dbus.Variant{ec.Visible})68// visible is true), or clears it (if count is 0 or visible is false).
69func (ctr *EmblemCounter) SetCounter(app *click.AppId, count int32, visible bool) bool {
70 base := app.Base()
71 quoted := string(nih.Quote([]byte(base)))
72
73 err := ctr.bus.SetProperty("count", "/"+quoted, dbus.Variant{count})
74 if err != nil {
75 ctr.log.Errorf("call to set count failed: %v", err)
76 return false
77 }
78 err = ctr.bus.SetProperty("countVisible", "/"+quoted, dbus.Variant{visible})
79 if err != nil {
80 ctr.log.Errorf("call to set countVisible failed: %v", err)
81 return false
82 }
83
84 return true
64}85}
6586
=== modified file 'bus/emblemcounter/emblemcounter_test.go'
--- bus/emblemcounter/emblemcounter_test.go 2014-07-11 19:42:57 +0000
+++ bus/emblemcounter/emblemcounter_test.go 2014-07-29 16:56:22 +0000
@@ -45,6 +45,21 @@
45 ecs.app = clickhelp.MustParseAppId("com.example.test_test-app_0")45 ecs.app = clickhelp.MustParseAppId("com.example.test_test-app_0")
46}46}
4747
48// checks that SetCounter() actually calls SetProperty on the launcher
49func (ecs *ecSuite) TestSetCounterSetsTheCounter(c *C) {
50 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
51 quoted := string(nih.Quote([]byte(ecs.app.Base())))
52
53 ec := New(endp, ecs.log)
54 c.Check(ec.SetCounter(ecs.app, 42, true), Equals, true)
55 callArgs := testibus.GetCallArgs(endp)
56 c.Assert(callArgs, HasLen, 2)
57 c.Check(callArgs[0].Member, Equals, "::SetProperty")
58 c.Check(callArgs[1].Member, Equals, "::SetProperty")
59 c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", "/" + quoted, dbus.Variant{Value: int32(42)}})
60 c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", "/" + quoted, dbus.Variant{Value: true}})
61}
62
48// checks that Present() actually calls SetProperty on the launcher63// checks that Present() actually calls SetProperty on the launcher
49func (ecs *ecSuite) TestPresentPresents(c *C) {64func (ecs *ecSuite) TestPresentPresents(c *C) {
50 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))65 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
@@ -52,7 +67,7 @@
5267
53 ec := New(endp, ecs.log)68 ec := New(endp, ecs.log)
54 notif := launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{Count: 42, Visible: true}}69 notif := launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{Count: 42, Visible: true}}
55 ec.Present(ecs.app, "nid", &notif)70 c.Check(ec.Present(ecs.app, "nid", &notif), Equals, true)
56 callArgs := testibus.GetCallArgs(endp)71 callArgs := testibus.GetCallArgs(endp)
57 c.Assert(callArgs, HasLen, 2)72 c.Assert(callArgs, HasLen, 2)
58 c.Check(callArgs[0].Member, Equals, "::SetProperty")73 c.Check(callArgs[0].Member, Equals, "::SetProperty")
@@ -67,16 +82,12 @@
67 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))82 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
68 ec := New(endp, ecs.log)83 ec := New(endp, ecs.log)
6984
70 // nothing happens if nil Notification
71 ec.Present(ecs.app, "nid", nil)
72 c.Assert(testibus.GetCallArgs(endp), HasLen, 0)
73
74 // nothing happens if no EmblemCounter in Notification85 // nothing happens if no EmblemCounter in Notification
75 ec.Present(ecs.app, "nid", &launch_helper.Notification{})86 c.Check(ec.Present(ecs.app, "nid", &launch_helper.Notification{}), Equals, false)
76 c.Assert(testibus.GetCallArgs(endp), HasLen, 0)87 c.Assert(testibus.GetCallArgs(endp), HasLen, 0)
7788
78 // but an empty EmblemCounter is acted on89 // but an empty EmblemCounter is acted on
79 ec.Present(ecs.app, "nid", &launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{}})90 c.Check(ec.Present(ecs.app, "nid", &launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{}}), Equals, true)
80 callArgs := testibus.GetCallArgs(endp)91 callArgs := testibus.GetCallArgs(endp)
81 c.Assert(callArgs, HasLen, 2)92 c.Assert(callArgs, HasLen, 2)
82 c.Check(callArgs[0].Member, Equals, "::SetProperty")93 c.Check(callArgs[0].Member, Equals, "::SetProperty")
@@ -84,3 +95,12 @@
84 c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", "/" + quoted, dbus.Variant{Value: int32(0)}})95 c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", "/" + quoted, dbus.Variant{Value: int32(0)}})
85 c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", "/" + quoted, dbus.Variant{Value: false}})96 c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", "/" + quoted, dbus.Variant{Value: false}})
86}97}
98
99// check that Present() panics if the notification is nil
100func (ecs *ecSuite) TestPanicsIfNil(c *C) {
101 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
102 ec := New(endp, ecs.log)
103
104 // nothing happens if no EmblemCounter in Notification
105 c.Check(func() { ec.Present(ecs.app, "nid", nil) }, Panics, `please check notification is not nil before calling present`)
106}
87107
=== modified file 'bus/haptic/haptic.go'
--- bus/haptic/haptic.go 2014-07-15 15:02:33 +0000
+++ bus/haptic/haptic.go 2014-07-29 16:56:22 +0000
@@ -44,9 +44,13 @@
44}44}
4545
46// Present presents the notification via a vibrate pattern46// Present presents the notification via a vibrate pattern
47func (haptic *Haptic) Present(_ *click.AppId, _ string, notification *launch_helper.Notification) bool {47func (haptic *Haptic) Present(_ *click.AppId, nid string, notification *launch_helper.Notification) bool {
48 if notification == nil || notification.Vibrate == nil {48 if notification == nil {
49 haptic.log.Debugf("no notification or no Vibrate in the notification; doing nothing: %#v", notification)49 panic("please check notification is not nil before calling present")
50 }
51
52 if notification.Vibrate == nil {
53 haptic.log.Debugf("[%s] notification has no Vibrate: %#v", nid, notification.Vibrate)
50 return false54 return false
51 }55 }
52 pattern := notification.Vibrate.Pattern56 pattern := notification.Vibrate.Pattern
@@ -58,13 +62,13 @@
58 pattern = []uint32{notification.Vibrate.Duration}62 pattern = []uint32{notification.Vibrate.Duration}
59 }63 }
60 if len(pattern) == 0 {64 if len(pattern) == 0 {
61 haptic.log.Debugf("not enough information in the notification's Vibrate section to create a pattern")65 haptic.log.Debugf("[%s] not enough information in the Vibrate to create a pattern", nid)
62 return false66 return false
63 }67 }
64 haptic.log.Debugf("vibrating %d times to the tune of %v", repeat, pattern)68 haptic.log.Debugf("[%s] vibrating %d times to the tune of %v", nid, repeat, pattern)
65 err := haptic.bus.Call("VibratePattern", bus.Args(pattern, repeat))69 err := haptic.bus.Call("VibratePattern", bus.Args(pattern, repeat))
66 if err != nil {70 if err != nil {
67 haptic.log.Errorf("VibratePattern call returned %v", err)71 haptic.log.Errorf("[%s] call to VibratePattern returned %v", nid, err)
68 return false72 return false
69 }73 }
70 return true74 return true
7175
=== modified file 'bus/haptic/haptic_test.go'
--- bus/haptic/haptic_test.go 2014-07-11 19:42:57 +0000
+++ bus/haptic/haptic_test.go 2014-07-29 16:56:22 +0000
@@ -106,10 +106,17 @@
106 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))106 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
107107
108 ec := New(endp, hs.log)108 ec := New(endp, hs.log)
109 // no notification at all
110 c.Check(ec.Present(hs.app, "", nil), Equals, false)
111 // no Vibration in the notificaton109 // no Vibration in the notificaton
112 c.Check(ec.Present(hs.app, "", &launch_helper.Notification{}), Equals, false)110 c.Check(ec.Present(hs.app, "", &launch_helper.Notification{}), Equals, false)
113 // empty Vibration111 // empty Vibration
114 c.Check(ec.Present(hs.app, "", &launch_helper.Notification{Vibrate: &launch_helper.Vibration{}}), Equals, false)112 c.Check(ec.Present(hs.app, "", &launch_helper.Notification{Vibrate: &launch_helper.Vibration{}}), Equals, false)
115}113}
114
115// check that Present() panics if the notification is nil
116func (hs *hapticSuite) TestPanicsIfNil(c *C) {
117 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
118
119 ec := New(endp, hs.log)
120 // no notification at all
121 c.Check(func() { ec.Present(hs.app, "", nil) }, Panics, `please check notification is not nil before calling present`)
122}
116123
=== modified file 'bus/notifications/raw.go'
--- bus/notifications/raw.go 2014-07-17 22:43:17 +0000
+++ bus/notifications/raw.go 2014-07-29 16:56:22 +0000
@@ -131,14 +131,18 @@
131// If card.Actions has 1 action, it's an interactive notification.131// If card.Actions has 1 action, it's an interactive notification.
132// If card.Actions has 2 actions, it will show as a snap decision.132// If card.Actions has 2 actions, it will show as a snap decision.
133// If it has more actions, who knows (good luck).133// If it has more actions, who knows (good luck).
134func (raw *RawNotifications) Present(app *click.AppId, nid string, notification *launch_helper.Notification) (uint32, error) {134func (raw *RawNotifications) Present(app *click.AppId, nid string, notification *launch_helper.Notification) bool {
135 if notification == nil || notification.Card == nil || !notification.Card.Popup || notification.Card.Summary == "" {135 if notification == nil {
136 raw.log.Debugf("[%s] skipping notification: nil, or nil card, or not popup, or no summary: %#v", nid, notification)136 panic("please check notification is not nil before calling present")
137 return 0, nil
138 }137 }
139138
140 card := notification.Card139 card := notification.Card
141140
141 if card == nil || !card.Popup || card.Summary == "" {
142 raw.log.Debugf("[%s] notification has no popup card: %#v", nid, card)
143 return false
144 }
145
142 hints := make(map[string]*dbus.Variant)146 hints := make(map[string]*dbus.Variant)
143 hints["x-canonical-secondary-icon"] = &dbus.Variant{app.Icon()}147 hints["x-canonical-secondary-icon"] = &dbus.Variant{app.Icon()}
144148
@@ -152,7 +156,8 @@
152 Action: action,156 Action: action,
153 })157 })
154 if err != nil {158 if err != nil {
155 return 0, err159 raw.log.Errorf("[%s] while marshaling %#v to json: %v", nid, action, err)
160 return false
156 }161 }
157 actions[2*i] = string(act)162 actions[2*i] = string(act)
158 actions[2*i+1] = action163 actions[2*i+1] = action
@@ -160,13 +165,22 @@
160 switch len(card.Actions) {165 switch len(card.Actions) {
161 case 0:166 case 0:
162 // nothing167 // nothing
168 default:
169 raw.log.Errorf("[%s] don't know what to do with %d actions; ignoring the rest", nid, len(card.Actions))
170 actions = actions[:2]
171 fallthrough
163 case 1:172 case 1:
164 hints["x-canonical-switch-to-application"] = &dbus.Variant{"true"}173 hints["x-canonical-switch-to-application"] = &dbus.Variant{"true"}
165 case 2:174 }
166 hints["x-canonical-snap-decisions"] = &dbus.Variant{"true"}175
167 hints["x-canonical-private-button-tint"] = &dbus.Variant{"true"}176 raw.log.Debugf("[%s] creating popup (or snap decision) for %s (summary: %s)", nid, app.Base(), card.Summary)
168 default:177
169 raw.log.Debugf("[%s] don't know what to do with %d actions; no hints set", nid, len(card.Actions))178 _, err := raw.Notify(appId, 0, card.Icon, card.Summary, card.Body, actions, hints, 30*1000)
170 }179
171 return raw.Notify(appId, 0, card.Icon, card.Summary, card.Body, actions, hints, 30*1000)180 if err != nil {
181 raw.log.Errorf("[%s] call to Notify failed: %v", nid, err)
182 return false
183 }
184
185 return true
172}186}
173187
=== modified file 'bus/notifications/raw_test.go'
--- bus/notifications/raw_test.go 2014-07-17 22:57:16 +0000
+++ bus/notifications/raw_test.go 2014-07-29 16:56:22 +0000
@@ -160,16 +160,15 @@
160func (s *RawSuite) TestPresentNotifies(c *C) {160func (s *RawSuite) TestPresentNotifies(c *C) {
161 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))161 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
162 raw := Raw(endp, s.log)162 raw := Raw(endp, s.log)
163 nid, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true}})163 worked := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true}})
164 c.Check(err, IsNil)164 c.Check(worked, Equals, true)
165 c.Check(nid, Equals, uint32(1))
166}165}
167166
168func (s *RawSuite) TestPresentOneAction(c *C) {167func (s *RawSuite) TestPresentOneAction(c *C) {
169 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))168 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
170 raw := Raw(endp, s.log)169 raw := Raw(endp, s.log)
171 _, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true, Actions: []string{"Yes"}}})170 worked := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true, Actions: []string{"Yes"}}})
172 c.Check(err, IsNil)171 c.Check(worked, Equals, true)
173 callArgs := testibus.GetCallArgs(endp)172 callArgs := testibus.GetCallArgs(endp)
174 c.Assert(callArgs, HasLen, 1)173 c.Assert(callArgs, HasLen, 1)
175 c.Assert(callArgs[0].Member, Equals, "Notify")174 c.Assert(callArgs[0].Member, Equals, "Notify")
@@ -192,78 +191,48 @@
192func (s *RawSuite) TestPresentTwoActions(c *C) {191func (s *RawSuite) TestPresentTwoActions(c *C) {
193 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))192 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
194 raw := Raw(endp, s.log)193 raw := Raw(endp, s.log)
195 _, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true, Actions: []string{"Yes", "No"}}})194 worked := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true, Actions: []string{"Yes", "No"}}})
196 c.Check(err, IsNil)195 c.Check(worked, Equals, true)
197 callArgs := testibus.GetCallArgs(endp)196 callArgs := testibus.GetCallArgs(endp)
198 c.Assert(callArgs, HasLen, 1)197 c.Assert(callArgs, HasLen, 1)
199 c.Assert(callArgs[0].Member, Equals, "Notify")198 c.Assert(callArgs[0].Member, Equals, "Notify")
200 c.Assert(len(callArgs[0].Args), Equals, 8)199 c.Assert(len(callArgs[0].Args), Equals, 8)
201 actions, ok := callArgs[0].Args[5].([]string)200 actions, ok := callArgs[0].Args[5].([]string)
202 c.Assert(ok, Equals, true)201 c.Assert(ok, Equals, true)
203 c.Assert(actions, HasLen, 4)202 c.Assert(actions, HasLen, 2)
204 c.Check(actions[0], Equals, `{"app":"com.example.test_test-app_0","act":"Yes","nid":"notifId"}`)203 c.Check(actions[0], Equals, `{"app":"com.example.test_test-app_0","act":"Yes","nid":"notifId"}`)
205 c.Check(actions[1], Equals, "Yes")204 c.Check(actions[1], Equals, "Yes")
206 c.Check(actions[2], Equals, `{"app":"com.example.test_test-app_0","act":"No","aid":1,"nid":"notifId"}`)205 // note that the rest are ignored.
207 c.Check(actions[3], Equals, "No")206 hints, ok := callArgs[0].Args[6].(map[string]*dbus.Variant)
208 hints, ok := callArgs[0].Args[6].(map[string]*dbus.Variant)207 c.Assert(ok, Equals, true)
209 c.Assert(ok, Equals, true)208 c.Assert(hints, HasLen, 2)
210 // with two actions, there should be 3 hints set:209 c.Check(hints["x-canonical-switch-to-application"], NotNil)
211 c.Assert(hints, HasLen, 3)210 c.Check(hints["x-canonical-secondary-icon"], NotNil)
212 c.Check(hints["x-canonical-switch-to-application"], IsNil)211}
213 c.Check(hints["x-canonical-secondary-icon"], NotNil)212
214 c.Check(hints["x-canonical-snap-decisions"], NotNil)213func (s *RawSuite) TestPresentNoNotificationPanics(c *C) {
215 c.Check(hints["x-canonical-private-button-tint"], NotNil)214 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
216 c.Check(hints["x-canonical-non-shaped-icon"], IsNil) // checking just in case215 raw := Raw(endp, s.log)
217}216 c.Check(func() { raw.Present(s.app, "notifId", nil) }, Panics, `please check notification is not nil before calling present`)
218
219func (s *RawSuite) TestPresentThreeActions(c *C) {
220 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
221 raw := Raw(endp, s.log)
222 _, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true, Actions: []string{"Yes", "No", "What"}}})
223 c.Check(err, IsNil)
224 callArgs := testibus.GetCallArgs(endp)
225 c.Assert(callArgs, HasLen, 1)
226 c.Assert(callArgs[0].Member, Equals, "Notify")
227 c.Assert(len(callArgs[0].Args), Equals, 8)
228 actions, ok := callArgs[0].Args[5].([]string)
229 c.Assert(ok, Equals, true)
230 c.Assert(actions, HasLen, 6)
231 hints, ok := callArgs[0].Args[6].(map[string]*dbus.Variant)
232 c.Assert(ok, Equals, true)
233 // with two actions, there should be 3 hints set:
234 c.Check(hints, HasLen, 1)
235 c.Check(hints["x-canonical-secondary-icon"], NotNil)
236 c.Check(s.log.Captured(), Matches, `(?ms).* no hints set$`)
237}
238
239func (s *RawSuite) TestPresentNoNotificationDoesNotNotify(c *C) {
240 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
241 raw := Raw(endp, s.log)
242 nid, err := raw.Present(s.app, "notifId", nil)
243 c.Check(err, IsNil)
244 c.Check(nid, Equals, uint32(0))
245}217}
246218
247func (s *RawSuite) TestPresentNoCardDoesNotNotify(c *C) {219func (s *RawSuite) TestPresentNoCardDoesNotNotify(c *C) {
248 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))220 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
249 raw := Raw(endp, s.log)221 raw := Raw(endp, s.log)
250 nid, err := raw.Present(s.app, "notifId", &launch_helper.Notification{})222 worked := raw.Present(s.app, "notifId", &launch_helper.Notification{})
251 c.Check(err, IsNil)223 c.Check(worked, Equals, false)
252 c.Check(nid, Equals, uint32(0))
253}224}
254225
255func (s *RawSuite) TestPresentNoSummaryDoesNotNotify(c *C) {226func (s *RawSuite) TestPresentNoSummaryDoesNotNotify(c *C) {
256 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))227 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
257 raw := Raw(endp, s.log)228 raw := Raw(endp, s.log)
258 nid, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{}})229 worked := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{}})
259 c.Check(err, IsNil)230 c.Check(worked, Equals, false)
260 c.Check(nid, Equals, uint32(0))
261}231}
262232
263func (s *RawSuite) TestPresentNoPopupNoNotify(c *C) {233func (s *RawSuite) TestPresentNoPopupNoNotify(c *C) {
264 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))234 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
265 raw := Raw(endp, s.log)235 raw := Raw(endp, s.log)
266 nid, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary"}})236 worked := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary"}})
267 c.Check(err, IsNil)237 c.Check(worked, Equals, false)
268 c.Check(nid, Equals, uint32(0))
269}238}
270239
=== modified file 'bus/urldispatcher/urldispatcher.go'
--- bus/urldispatcher/urldispatcher.go 2014-04-02 08:23:15 +0000
+++ bus/urldispatcher/urldispatcher.go 2014-07-29 16:56:22 +0000
@@ -19,6 +19,7 @@
1919
20import (20import (
21 "launchpad.net/ubuntu-push/bus"21 "launchpad.net/ubuntu-push/bus"
22 "launchpad.net/ubuntu-push/click"
22 "launchpad.net/ubuntu-push/logger"23 "launchpad.net/ubuntu-push/logger"
23)24)
2425
@@ -32,7 +33,8 @@
32// A URLDispatcher is a simple beast, with a single method that does what it33// A URLDispatcher is a simple beast, with a single method that does what it
33// says on the box.34// says on the box.
34type URLDispatcher interface {35type URLDispatcher interface {
35 DispatchURL(string) error36 DispatchURL(string, *click.AppId) error
37 TestURL(*click.AppId, []string) bool
36}38}
3739
38type urlDispatcher struct {40type urlDispatcher struct {
@@ -47,11 +49,28 @@
4749
48var _ URLDispatcher = &urlDispatcher{} // ensures it conforms50var _ URLDispatcher = &urlDispatcher{} // ensures it conforms
4951
50func (ud *urlDispatcher) DispatchURL(url string) error {52func (ud *urlDispatcher) DispatchURL(url string, app *click.AppId) error {
51 ud.log.Debugf("Dispatching %s", url)53 ud.log.Debugf("Dispatching %s", url)
52 err := ud.endp.Call("DispatchURL", bus.Args(url))54 err := ud.endp.Call("DispatchURL", bus.Args(url, app.DispatchPackage()))
53 if err != nil {55 if err != nil {
54 ud.log.Errorf("Dispatch to %s failed with %s", url, err)56 ud.log.Errorf("Dispatch to %s failed with %s", url, err)
55 }57 }
56 return err58 return err
57}59}
60
61func (ud *urlDispatcher) TestURL(app *click.AppId, urls []string) bool {
62 ud.log.Debugf("TestURL: %s", urls)
63 var appIds []string
64 err := ud.endp.Call("TestURL", bus.Args(urls), &appIds)
65 if err != nil {
66 ud.log.Errorf("TestURL for %s failed with %s", urls, err)
67 return false
68 }
69 for _, appId := range appIds {
70 if appId != app.Versioned() {
71 ud.log.Debugf("Notification skipped because of different appid for actions: %v - %s != %s", urls, appId, app.Versioned())
72 return false
73 }
74 }
75 return true
76}
5877
=== modified file 'bus/urldispatcher/urldispatcher_test.go'
--- bus/urldispatcher/urldispatcher_test.go 2014-02-05 18:17:26 +0000
+++ bus/urldispatcher/urldispatcher_test.go 2014-07-29 16:56:22 +0000
@@ -19,7 +19,7 @@
19import (19import (
20 . "launchpad.net/gocheck"20 . "launchpad.net/gocheck"
21 testibus "launchpad.net/ubuntu-push/bus/testing"21 testibus "launchpad.net/ubuntu-push/bus/testing"
22 "launchpad.net/ubuntu-push/logger"22 clickhelp "launchpad.net/ubuntu-push/click/testing"
23 helpers "launchpad.net/ubuntu-push/testing"23 helpers "launchpad.net/ubuntu-push/testing"
24 "launchpad.net/ubuntu-push/testing/condition"24 "launchpad.net/ubuntu-push/testing/condition"
25 "testing"25 "testing"
@@ -29,7 +29,7 @@
29func TestUrldispatcher(t *testing.T) { TestingT(t) }29func TestUrldispatcher(t *testing.T) { TestingT(t) }
3030
31type UDSuite struct {31type UDSuite struct {
32 log logger.Logger32 log *helpers.TestLogger
33}33}
3434
35var _ = Suite(&UDSuite{})35var _ = Suite(&UDSuite{})
@@ -38,16 +38,78 @@
38 s.log = helpers.NewTestLogger(c, "debug")38 s.log = helpers.NewTestLogger(c, "debug")
39}39}
4040
41func (s *UDSuite) TestWorks(c *C) {41func (s *UDSuite) TestDispatchURLWorks(c *C) {
42 endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{})42 endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{})
43 ud := New(endp, s.log)43 ud := New(endp, s.log)
44 err := ud.DispatchURL("this")44 appId := clickhelp.MustParseAppId("com.example.test_app_0.99")
45 err := ud.DispatchURL("this", appId)
45 c.Check(err, IsNil)46 c.Check(err, IsNil)
46}47}
4748
48func (s *UDSuite) TestFailsIfCallFails(c *C) {49func (s *UDSuite) TestDispatchURLFailsIfCallFails(c *C) {
49 endp := testibus.NewTestingEndpoint(nil, condition.Work(false))50 endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
50 ud := New(endp, s.log)51 ud := New(endp, s.log)
51 err := ud.DispatchURL("this")52 appId := clickhelp.MustParseAppId("com.example.test_app_0.99")
53 err := ud.DispatchURL("this", appId)
52 c.Check(err, NotNil)54 c.Check(err, NotNil)
53}55}
56
57func (s *UDSuite) TestTestURLWorks(c *C) {
58 endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{[]string{"com.example.test_app_0.99"}})
59 ud := New(endp, s.log)
60 appId := clickhelp.MustParseAppId("com.example.test_app_0.99")
61 c.Check(ud.TestURL(appId, []string{"this"}), Equals, true)
62 c.Check(s.log.Captured(), Matches, `(?sm).*TestURL: \[this\].*`)
63}
64
65func (s *UDSuite) TestTestURLFailsIfCallFails(c *C) {
66 endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
67 ud := New(endp, s.log)
68 appId := clickhelp.MustParseAppId("com.example.test_app_0.99")
69 c.Check(ud.TestURL(appId, []string{"this"}), Equals, false)
70}
71
72func (s *UDSuite) TestTestURLMultipleURLs(c *C) {
73 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []string{"com.example.test_app_0.99", "com.example.test_app_0.99"})
74 ud := New(endp, s.log)
75 appId := clickhelp.MustParseAppId("com.example.test_app_0.99")
76 urls := []string{"potato://test-app", "potato_a://foo"}
77 c.Check(ud.TestURL(appId, urls), Equals, true)
78 c.Check(s.log.Captured(), Matches, `(?sm).*TestURL: \[potato://test-app potato_a://foo\].*`)
79}
80
81func (s *UDSuite) TestTestURLWrongApp(c *C) {
82 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []string{"com.example.test_test-app_0.1"})
83 ud := New(endp, s.log)
84 appId := clickhelp.MustParseAppId("com.example.test_app_0.99")
85 urls := []string{"potato://test-app"}
86 c.Check(ud.TestURL(appId, urls), Equals, false)
87 c.Check(s.log.Captured(), Matches, `(?sm).*Notification skipped because of different appid for actions: \[potato://test-app\] - com.example.test_test-app_0.1 != com.example.test_app_0.99`)
88}
89
90func (s *UDSuite) TestTestURLOneWrongApp(c *C) {
91 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []string{"com.example.test_test-app_0", "com.example.test_test-app1"})
92 ud := New(endp, s.log)
93 appId := clickhelp.MustParseAppId("com.example.test_test-app_0")
94 urls := []string{"potato://test-app", "potato_a://foo"}
95 c.Check(ud.TestURL(appId, urls), Equals, false)
96 c.Check(s.log.Captured(), Matches, `(?sm).*Notification skipped because of different appid for actions: \[potato://test-app potato_a://foo\] - com.example.test_test-app1 != com.example.test_test-app.*`)
97}
98
99func (s *UDSuite) TestTestURLInvalidURL(c *C) {
100 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false), []string{"com.example.test_test-app_0.1"})
101 ud := New(endp, s.log)
102 appId := clickhelp.MustParseAppId("com.example.test_app_0.2")
103 urls := []string{"notsupported://test-app"}
104 c.Check(ud.TestURL(appId, urls), Equals, false)
105 c.Check(s.log.Captured(), Matches, `(?sm).*TestURL for \[notsupported://test-app\] failed with no way.*`)
106}
107
108func (s *UDSuite) TestTestURLLegacyApp(c *C) {
109 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []string{"ubuntu-system-settings"})
110 ud := New(endp, s.log)
111 appId := clickhelp.MustParseAppId("_ubuntu-system-settings")
112 urls := []string{"settings://test-app"}
113 c.Check(ud.TestURL(appId, urls), Equals, true)
114 c.Check(s.log.Captured(), Matches, `(?sm).*TestURL: \[settings://test-app\].*`)
115}
54116
=== modified file 'click/click.go'
--- click/click.go 2014-07-18 20:45:21 +0000
+++ click/click.go 2014-07-29 16:56:22 +0000
@@ -98,6 +98,13 @@
98 return app.Package == pkgname98 return app.Package == pkgname
99}99}
100100
101func (app *AppId) DispatchPackage() string {
102 if app.Click {
103 return app.Package
104 }
105 return app.Application
106}
107
101func (app *AppId) Original() string {108func (app *AppId) Original() string {
102 return app.original109 return app.original
103}110}
104111
=== modified file 'click/click_test.go'
--- click/click_test.go 2014-07-18 20:45:21 +0000
+++ click/click_test.go 2014-07-29 16:56:22 +0000
@@ -42,6 +42,7 @@
42 c.Check(app.Click, Equals, true)42 c.Check(app.Click, Equals, true)
43 c.Check(app.Original(), Equals, "com.ubuntu.clock_clock")43 c.Check(app.Original(), Equals, "com.ubuntu.clock_clock")
44 c.Check(fmt.Sprintf("%s", app), Equals, "com.ubuntu.clock_clock")44 c.Check(fmt.Sprintf("%s", app), Equals, "com.ubuntu.clock_clock")
45 c.Check(app.DispatchPackage(), Equals, "com.ubuntu.clock")
4546
46 app, err = ParseAppId("com.ubuntu.clock_clock_10")47 app, err = ParseAppId("com.ubuntu.clock_clock_10")
47 c.Assert(err, IsNil)48 c.Assert(err, IsNil)
@@ -55,6 +56,7 @@
55 c.Check(app.Versioned(), Equals, "com.ubuntu.clock_clock_10")56 c.Check(app.Versioned(), Equals, "com.ubuntu.clock_clock_10")
56 c.Check(app.Base(), Equals, "com.ubuntu.clock_clock")57 c.Check(app.Base(), Equals, "com.ubuntu.clock_clock")
57 c.Check(app.DesktopId(), Equals, "com.ubuntu.clock_clock_10.desktop")58 c.Check(app.DesktopId(), Equals, "com.ubuntu.clock_clock_10.desktop")
59 c.Check(app.DispatchPackage(), Equals, "com.ubuntu.clock")
5860
59 for _, s := range []string{"com.ubuntu.clock_clock_10_4", "com.ubuntu.clock", ""} {61 for _, s := range []string{"com.ubuntu.clock_clock_10_4", "com.ubuntu.clock", ""} {
60 app, err = ParseAppId(s)62 app, err = ParseAppId(s)
@@ -81,6 +83,7 @@
81 c.Check(app.Versioned(), Equals, "python3.4")83 c.Check(app.Versioned(), Equals, "python3.4")
82 c.Check(app.Base(), Equals, "python3.4")84 c.Check(app.Base(), Equals, "python3.4")
83 c.Check(app.DesktopId(), Equals, "python3.4.desktop")85 c.Check(app.DesktopId(), Equals, "python3.4.desktop")
86 c.Check(app.DispatchPackage(), Equals, "python3.4")
8487
85 for _, s := range []string{"_.foo", "_foo/", "_/foo"} {88 for _, s := range []string{"_.foo", "_foo/", "_/foo"} {
86 app, err = ParseAppId(s)89 app, err = ParseAppId(s)
8790
=== modified file 'client/client.go'
--- client/client.go 2014-07-21 01:18:16 +0000
+++ client/client.go 2014-07-29 16:56:22 +0000
@@ -32,8 +32,6 @@
32 "os/exec"32 "os/exec"
33 "strings"33 "strings"
3434
35 "code.google.com/p/go-uuid/uuid"
36
37 "launchpad.net/ubuntu-push/bus"35 "launchpad.net/ubuntu-push/bus"
38 "launchpad.net/ubuntu-push/bus/connectivity"36 "launchpad.net/ubuntu-push/bus/connectivity"
39 "launchpad.net/ubuntu-push/bus/networkmanager"37 "launchpad.net/ubuntu-push/bus/networkmanager"
@@ -85,7 +83,7 @@
85 // Post converts a push message into a presentable notification83 // Post converts a push message into a presentable notification
86 // and a postal message, presents the former and stores the84 // and a postal message, presents the former and stores the
87 // latter in the application's mailbox.85 // latter in the application's mailbox.
88 Post(app *click.AppId, nid string, payload json.RawMessage) error86 Post(app *click.AppId, nid string, payload json.RawMessage)
89 // IsRunning() returns whether the service is running87 // IsRunning() returns whether the service is running
90 IsRunning() bool88 IsRunning() bool
91 // Stop() stops the service89 // Stop() stops the service
@@ -398,29 +396,23 @@
398 }396 }
399 // marshal the last decoded msg to json397 // marshal the last decoded msg to json
400 payload, err := json.Marshal(msg.Decoded[len(msg.Decoded)-1])398 payload, err := json.Marshal(msg.Decoded[len(msg.Decoded)-1])
401 if err == nil {
402 appId, _ := click.ParseAppId("_ubuntu-system-settings")
403 err = client.postalService.Post(appId, uuid.New(), payload)
404 }
405 if err != nil {399 if err != nil {
406 client.log.Errorf("while posting broadcast notification %d: %v", msg.TopLevel, err)400 client.log.Errorf("while posting broadcast notification %d: %v", msg.TopLevel, err)
407 } else {401 return err
408 client.log.Debugf("posted broadcast notification %d.", msg.TopLevel)
409 }402 }
410 return err403 appId, _ := click.ParseAppId("_ubuntu-system-settings")
404 client.postalService.Post(appId, "", payload)
405 client.log.Debugf("posted broadcast notification %d.", msg.TopLevel)
406 return nil
411}407}
412408
413// handleUnicastNotification deals with receiving a unicast notification409// handleUnicastNotification deals with receiving a unicast notification
414func (client *PushClient) handleUnicastNotification(anotif session.AddressedNotification) error {410func (client *PushClient) handleUnicastNotification(anotif session.AddressedNotification) error {
415 app := anotif.To411 app := anotif.To
416 msg := anotif.Notification412 msg := anotif.Notification
417 err := client.postalService.Post(app, msg.MsgId, msg.Payload)413 client.postalService.Post(app, msg.MsgId, msg.Payload)
418 if err != nil {414 client.log.Debugf("posted unicast notification %s for %s.", msg.MsgId, msg.AppId)
419 client.log.Errorf("while posting unicast notification %s for %s: %v", msg.MsgId, msg.AppId, err)415 return nil
420 } else {
421 client.log.Debugf("posted unicast notification %s for %s.", msg.MsgId, msg.AppId)
422 }
423 return err
424}416}
425417
426// doLoop connects events with their handlers418// doLoop connects events with their handlers
427419
=== modified file 'client/client_test.go'
--- client/client_test.go 2014-07-21 01:18:16 +0000
+++ client/client_test.go 2014-07-29 16:56:22 +0000
@@ -107,13 +107,12 @@
107 postArgs []postArgs107 postArgs []postArgs
108}108}
109109
110func (d *dumbPostal) Post(app *click.AppId, nid string, payload json.RawMessage) error {110func (d *dumbPostal) Post(app *click.AppId, nid string, payload json.RawMessage) {
111 d.postCount++111 d.postCount++
112 if app.Application == "ubuntu-system-settings" {112 if app.Application == "ubuntu-system-settings" {
113 d.bcastCount++113 d.bcastCount++
114 }114 }
115 d.postArgs = append(d.postArgs, postArgs{app, nid, payload})115 d.postArgs = append(d.postArgs, postArgs{app, nid, payload})
116 return d.err
117}116}
118117
119var _ PostalService = (*dumbPostal)(nil)118var _ PostalService = (*dumbPostal)(nil)
@@ -786,6 +785,9 @@
786 positiveBroadcastNotification = &session.BroadcastNotification{785 positiveBroadcastNotification = &session.BroadcastNotification{
787 Decoded: []map[string]interface{}{786 Decoded: []map[string]interface{}{
788 map[string]interface{}{787 map[string]interface{}{
788 "daily/mako": []interface{}{float64(102), "tubular"},
789 },
790 map[string]interface{}{
789 "daily/mako": []interface{}{float64(103), "tubular"},791 "daily/mako": []interface{}{float64(103), "tubular"},
790 },792 },
791 },793 },
@@ -811,7 +813,8 @@
811 c.Assert(d.postArgs, HasLen, 1)813 c.Assert(d.postArgs, HasLen, 1)
812 expectedApp, _ := click.ParseAppId("_ubuntu-system-settings")814 expectedApp, _ := click.ParseAppId("_ubuntu-system-settings")
813 c.Check(d.postArgs[0].app, DeepEquals, expectedApp)815 c.Check(d.postArgs[0].app, DeepEquals, expectedApp)
814 expectedData, _ := json.Marshal(positiveBroadcastNotification.Decoded[0])816 c.Check(d.postArgs[0].nid, Equals, "")
817 expectedData, _ := json.Marshal(positiveBroadcastNotification.Decoded[1])
815 c.Check([]byte(d.postArgs[0].payload), DeepEquals, expectedData)818 c.Check([]byte(d.postArgs[0].payload), DeepEquals, expectedData)
816}819}
817820
@@ -826,18 +829,6 @@
826 c.Check(d.bcastCount, Equals, 0)829 c.Check(d.bcastCount, Equals, 0)
827}830}
828831
829func (cs *clientSuite) TestHandleBroadcastNotificationFail(c *C) {
830 cli := NewPushClient(cs.configPath, cs.leveldbPath)
831 cli.systemImageInfo = siInfoRes
832 cli.log = cs.log
833 d := new(dumbPostal)
834 err := errors.New("potato")
835 d.err = err
836 cli.postalService = d
837 c.Check(cli.handleBroadcastNotification(positiveBroadcastNotification), Equals, err)
838 c.Check(d.bcastCount, Equals, 1)
839}
840
841/*****************************************************************832/*****************************************************************
842 handleUnicastNotification tests833 handleUnicastNotification tests
843******************************************************************/834******************************************************************/
@@ -860,19 +851,6 @@
860 c.Check(d.postArgs[0].payload, DeepEquals, notif.Payload)851 c.Check(d.postArgs[0].payload, DeepEquals, notif.Payload)
861}852}
862853
863func (cs *clientSuite) TestHandleUcastNotificationError(c *C) {
864 cli := NewPushClient(cs.configPath, cs.leveldbPath)
865 cli.log = cs.log
866 d := new(dumbPostal)
867 cli.postalService = d
868 fail := errors.New("fail")
869 d.err = fail
870
871 c.Check(cli.handleUnicastNotification(session.AddressedNotification{appHello, notif}), Equals, fail)
872 // check we sent the notification
873 c.Check(d.postCount, Equals, 1)
874}
875
876/*****************************************************************854/*****************************************************************
877 handleUnregister tests855 handleUnregister tests
878******************************************************************/856******************************************************************/
879857
=== modified file 'client/service/postal.go'
--- client/service/postal.go 2014-07-21 01:18:16 +0000
+++ client/service/postal.go 2014-07-29 16:56:22 +0000
@@ -39,7 +39,21 @@
39 "launchpad.net/ubuntu-push/util"39 "launchpad.net/ubuntu-push/util"
40)40)
4141
42type messageHandler func(*click.AppId, string, *launch_helper.HelperOutput) error42type messageHandler func(*click.AppId, string, *launch_helper.HelperOutput) bool
43
44// a Presenter is something that knows how to present a Notification
45type Presenter interface {
46 Present(*click.AppId, string, *launch_helper.Notification) bool
47}
48
49type notificationCentre interface {
50 Presenter
51 GetCh() chan *reply.MMActionReply
52 RemoveNotification(string, bool)
53 StartCleanupLoop()
54 Tags(*click.AppId) []string
55 Clear(*click.AppId, ...string) int
56}
4357
44// PostalService is the dbus api58// PostalService is the dbus api
45type PostalService struct {59type PostalService struct {
@@ -48,7 +62,7 @@
48 msgHandler messageHandler62 msgHandler messageHandler
49 launchers map[string]launch_helper.HelperLauncher63 launchers map[string]launch_helper.HelperLauncher
50 HelperPool launch_helper.HelperPool64 HelperPool launch_helper.HelperPool
51 messagingMenu *messaging.MessagingMenu65 messagingMenu notificationCentre
52 // the endpoints are only exposed for testing from client66 // the endpoints are only exposed for testing from client
53 // XXX: uncouple some more so this isn't necessary67 // XXX: uncouple some more so this isn't necessary
54 EmblemCounterEndp bus.Endpoint68 EmblemCounterEndp bus.Endpoint
@@ -57,6 +71,7 @@
57 URLDispatcherEndp bus.Endpoint71 URLDispatcherEndp bus.Endpoint
58 WindowStackEndp bus.Endpoint72 WindowStackEndp bus.Endpoint
59 // presenters:73 // presenters:
74 Presenters []Presenter
60 emblemCounter *emblemcounter.EmblemCounter75 emblemCounter *emblemcounter.EmblemCounter
61 haptic *haptic.Haptic76 haptic *haptic.Haptic
62 notifications *notifications.RawNotifications77 notifications *notifications.RawNotifications
@@ -112,8 +127,11 @@
112// Start() dials the bus, grab the name, and listens for method calls.127// Start() dials the bus, grab the name, and listens for method calls.
113func (svc *PostalService) Start() error {128func (svc *PostalService) Start() error {
114 err := svc.DBusService.Start(bus.DispatchMap{129 err := svc.DBusService.Start(bus.DispatchMap{
115 "PopAll": svc.popAll,130 "PopAll": svc.popAll,
116 "Post": svc.post,131 "Post": svc.post,
132 "ListPersistent": svc.listPersistent,
133 "ClearPersistent": svc.clearPersistent,
134 "SetCounter": svc.setCounter,
117 }, PostalServiceBusAddress)135 }, PostalServiceBusAddress)
118 if err != nil {136 if err != nil {
119 return err137 return err
@@ -128,6 +146,13 @@
128 svc.haptic = haptic.New(svc.HapticEndp, svc.Log)146 svc.haptic = haptic.New(svc.HapticEndp, svc.Log)
129 svc.sound = sounds.New(svc.Log)147 svc.sound = sounds.New(svc.Log)
130 svc.messagingMenu = messaging.New(svc.Log)148 svc.messagingMenu = messaging.New(svc.Log)
149 svc.Presenters = []Presenter{
150 svc.notifications,
151 svc.emblemCounter,
152 svc.haptic,
153 svc.sound,
154 svc.messagingMenu,
155 }
131 if useTrivialHelper {156 if useTrivialHelper {
132 svc.HelperPool = launch_helper.NewTrivialHelperPool(svc.Log)157 svc.HelperPool = launch_helper.NewTrivialHelperPool(svc.Log)
133 } else {158 } else {
@@ -136,7 +161,8 @@
136 svc.windowStack = windowstack.New(svc.WindowStackEndp, svc.Log)161 svc.windowStack = windowstack.New(svc.WindowStackEndp, svc.Log)
137162
138 go svc.consumeHelperResults(svc.HelperPool.Start())163 go svc.consumeHelperResults(svc.HelperPool.Start())
139 go svc.handleActions(actionsCh, svc.messagingMenu.Ch)164 go svc.handleActions(actionsCh, svc.messagingMenu.GetCh())
165 svc.messagingMenu.StartCleanupLoop()
140 return nil166 return nil
141}167}
142168
@@ -155,8 +181,10 @@
155 svc.Log.Debugf("handleActions got nil action; ignoring")181 svc.Log.Debugf("handleActions got nil action; ignoring")
156 } else {182 } else {
157 url := action.Action183 url := action.Action
184 // remove the notification from the messaging menu
185 svc.messagingMenu.RemoveNotification(action.Nid, true)
158 // this ignores the error (it's been logged already)186 // this ignores the error (it's been logged already)
159 svc.urlDispatcher.DispatchURL(url)187 svc.urlDispatcher.DispatchURL(url, action.App)
160 }188 }
161 case mmuAction, ok := <-mmuActionsCh:189 case mmuAction, ok := <-mmuActionsCh:
162 if !ok {190 if !ok {
@@ -168,9 +196,9 @@
168 svc.Log.Debugf("handleActions (MMU) got: %v", mmuAction)196 svc.Log.Debugf("handleActions (MMU) got: %v", mmuAction)
169 url := mmuAction.Action197 url := mmuAction.Action
170 // remove the notification from the messagingmenu map198 // remove the notification from the messagingmenu map
171 svc.messagingMenu.RemoveNotification(mmuAction.Notification)199 svc.messagingMenu.RemoveNotification(mmuAction.Notification, false)
172 // this ignores the error (it's been logged already)200 // this ignores the error (it's been logged already)
173 svc.urlDispatcher.DispatchURL(url)201 svc.urlDispatcher.DispatchURL(url, mmuAction.App)
174 }202 }
175203
176 }204 }
@@ -209,6 +237,54 @@
209 return notifications.Raw(svc.NotificationsEndp, svc.Log).WatchActions()237 return notifications.Raw(svc.NotificationsEndp, svc.Log).WatchActions()
210}238}
211239
240func (svc *PostalService) listPersistent(path string, args, _ []interface{}) ([]interface{}, error) {
241 app, err := svc.grabDBusPackageAndAppId(path, args, 0)
242 if err != nil {
243 return nil, err
244 }
245
246 tagmap := svc.messagingMenu.Tags(app)
247 return []interface{}{tagmap}, nil
248}
249
250func (svc *PostalService) clearPersistent(path string, args, _ []interface{}) ([]interface{}, error) {
251 if len(args) == 0 {
252 return nil, ErrBadArgCount
253 }
254 app, err := svc.grabDBusPackageAndAppId(path, args[:1], 0)
255 if err != nil {
256 return nil, err
257 }
258 tags := make([]string, len(args)-1)
259 for i, itag := range args[1:] {
260 tag, ok := itag.(string)
261 if !ok {
262 return nil, ErrBadArgType
263 }
264 tags[i] = tag
265 }
266 return []interface{}{uint32(svc.messagingMenu.Clear(app, tags...))}, nil
267}
268
269func (svc *PostalService) setCounter(path string, args, _ []interface{}) ([]interface{}, error) {
270 app, err := svc.grabDBusPackageAndAppId(path, args, 2)
271 if err != nil {
272 return nil, err
273 }
274
275 count, ok := args[1].(int32)
276 if !ok {
277 return nil, ErrBadArgType
278 }
279 visible, ok := args[2].(bool)
280 if !ok {
281 return nil, ErrBadArgType
282 }
283
284 svc.emblemCounter.SetCounter(app, count, visible)
285 return nil, nil
286}
287
212func (svc *PostalService) popAll(path string, args, _ []interface{}) ([]interface{}, error) {288func (svc *PostalService) popAll(path string, args, _ []interface{}) ([]interface{}, error) {
213 app, err := svc.grabDBusPackageAndAppId(path, args, 0)289 app, err := svc.grabDBusPackageAndAppId(path, args, 0)
214 if err != nil {290 if err != nil {
@@ -250,14 +326,16 @@
250 return nil, ErrBadJSON326 return nil, ErrBadJSON
251 }327 }
252328
253 nid := newNid()329 svc.Post(app, "", rawJSON)
254330 return nil, nil
255 return nil, svc.Post(app, nid, rawJSON)
256}331}
257332
258// Post() signals to an application over dbus that a notification333// Post() signals to an application over dbus that a notification
259// has arrived.334// has arrived. If nid is "" generate one.
260func (svc *PostalService) Post(app *click.AppId, nid string, payload json.RawMessage) error {335func (svc *PostalService) Post(app *click.AppId, nid string, payload json.RawMessage) {
336 if nid == "" {
337 nid = newNid()
338 }
261 arg := launch_helper.HelperInput{339 arg := launch_helper.HelperInput{
262 App: app,340 App: app,
263 NotificationId: nid,341 NotificationId: nid,
@@ -270,7 +348,6 @@
270 kind = "legacy"348 kind = "legacy"
271 }349 }
272 svc.HelperPool.Run(kind, &arg)350 svc.HelperPool.Run(kind, &arg)
273 return nil
274}351}
275352
276func (svc *PostalService) consumeHelperResults(ch chan *launch_helper.HelperResult) {353func (svc *PostalService) consumeHelperResults(ch chan *launch_helper.HelperResult) {
@@ -299,27 +376,41 @@
299 box.Append(output.Message, nid)376 box.Append(output.Message, nid)
300377
301 if svc.msgHandler != nil {378 if svc.msgHandler != nil {
302 err := svc.msgHandler(app, nid, &output)379 b := svc.msgHandler(app, nid, &output)
303 if err != nil {380 if !b {
304 svc.DBusService.Log.Errorf("msgHandler returned %v", err)381 svc.Log.Debugf("msgHandler did not present the notification")
305 return
306 }382 }
307 svc.DBusService.Log.Debugf("call to msgHandler successful")
308 }383 }
309384
310 svc.Bus.Signal("Post", "/"+string(nih.Quote([]byte(app.Package))), []interface{}{appId})385 svc.Bus.Signal("Post", "/"+string(nih.Quote([]byte(app.Package))), []interface{}{appId})
311}386}
312387
313func (svc *PostalService) messageHandler(app *click.AppId, nid string, output *launch_helper.HelperOutput) error {388func (svc *PostalService) validateActions(app *click.AppId, notif *launch_helper.Notification) bool {
389 if notif.Card == nil || len(notif.Card.Actions) == 0 {
390 return true
391 }
392 return svc.urlDispatcher.TestURL(app, notif.Card.Actions)
393}
394
395func (svc *PostalService) messageHandler(app *click.AppId, nid string, output *launch_helper.HelperOutput) bool {
396 if output == nil || output.Notification == nil {
397 svc.Log.Debugf("skipping notification: nil.")
398 return false
399 }
400 // validate actions
401 if !svc.validateActions(app, output.Notification) {
402 // no need to log, (it's been logged already)
403 return false
404 }
314 if !svc.windowStack.IsAppFocused(app) {405 if !svc.windowStack.IsAppFocused(app) {
315 svc.messagingMenu.Present(app, nid, output.Notification)406 b := false
316 _, err := svc.notifications.Present(app, nid, output.Notification)407 for _, p := range svc.Presenters {
317 svc.emblemCounter.Present(app, nid, output.Notification)408 // we don't want this to shortcut :)
318 svc.haptic.Present(app, nid, output.Notification)409 b = p.Present(app, nid, output.Notification) || b
319 svc.sound.Present(app, nid, output.Notification)410 }
320 return err411 return b
321 } else {412 } else {
322 svc.Log.Debugf("Notification skipped because app is focused.")413 svc.Log.Debugf("notification skipped because app is focused.")
323 return nil414 return false
324 }415 }
325}416}
326417
=== modified file 'client/service/postal_test.go'
--- client/service/postal_test.go 2014-07-21 10:38:55 +0000
+++ client/service/postal_test.go 2014-07-29 16:56:22 +0000
@@ -18,15 +18,14 @@
1818
19import (19import (
20 "encoding/json"20 "encoding/json"
21 "errors"21 "fmt"
22 "io/ioutil"22 "io/ioutil"
23 "os"23 "os"
24 "path/filepath"24 "path/filepath"
25 "sort"25 "sort"
26 "time"26 "time"
2727
28 "code.google.com/p/go-uuid/uuid"28 "launchpad.net/go-dbus/v1"
29
30 . "launchpad.net/gocheck"29 . "launchpad.net/gocheck"
3130
32 "launchpad.net/ubuntu-push/bus"31 "launchpad.net/ubuntu-push/bus"
@@ -37,6 +36,7 @@
37 clickhelp "launchpad.net/ubuntu-push/click/testing"36 clickhelp "launchpad.net/ubuntu-push/click/testing"
38 "launchpad.net/ubuntu-push/launch_helper"37 "launchpad.net/ubuntu-push/launch_helper"
39 "launchpad.net/ubuntu-push/messaging/reply"38 "launchpad.net/ubuntu-push/messaging/reply"
39 "launchpad.net/ubuntu-push/nih"
40 helpers "launchpad.net/ubuntu-push/testing"40 helpers "launchpad.net/ubuntu-push/testing"
41 "launchpad.net/ubuntu-push/testing/condition"41 "launchpad.net/ubuntu-push/testing/condition"
42)42)
@@ -80,16 +80,16 @@
80 }80 }
81}81}
8282
83func installTickMessageHandler(svc *PostalService) chan error {83func installTickMessageHandler(svc *PostalService) chan bool {
84 ch := make(chan error)84 ch := make(chan bool)
85 msgHandler := svc.GetMessageHandler()85 msgHandler := svc.GetMessageHandler()
86 svc.SetMessageHandler(func(app *click.AppId, nid string, output *launch_helper.HelperOutput) error {86 svc.SetMessageHandler(func(app *click.AppId, nid string, output *launch_helper.HelperOutput) bool {
87 var err error87 var b bool
88 if msgHandler != nil {88 if msgHandler != nil {
89 err = msgHandler(app, nid, output)89 b = msgHandler(app, nid, output)
90 }90 }
91 ch <- err91 ch <- b
92 return err92 return b
93 })93 })
94 return ch94 return ch
95}95}
@@ -270,6 +270,60 @@
270}270}
271271
272//272//
273// post() tests
274
275func (ps *postalSuite) TestPostHappyPath(c *C) {
276 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
277 svc.msgHandler = nil
278 ch := installTickMessageHandler(svc)
279 svc.launchers = map[string]launch_helper.HelperLauncher{
280 "click": ps.fakeLauncher,
281 }
282 c.Assert(svc.Start(), IsNil)
283 payload := `{"message": {"world":1}}`
284 rvs, err := svc.post(aPackageOnBus, []interface{}{anAppId, payload}, nil)
285 c.Assert(err, IsNil)
286 c.Check(rvs, IsNil)
287
288 if ps.fakeLauncher.done != nil {
289 // wait for the two posts to "launch"
290 inputData := takeNextBytes(ps.fakeLauncher.ch)
291 c.Check(string(inputData), Equals, payload)
292
293 go ps.fakeLauncher.done("0") // OneDone
294 }
295
296 c.Check(takeNextBool(ch), Equals, false) // one,
297 // xxx here?
298 c.Assert(svc.mbox, HasLen, 1)
299 box, ok := svc.mbox[anAppId]
300 c.Check(ok, Equals, true)
301 msgs := box.AllMessages()
302 c.Assert(msgs, HasLen, 1)
303 c.Check(msgs[0], Equals, `{"world":1}`)
304 c.Check(box.nids[0], Not(Equals), "")
305}
306
307func (ps *postalSuite) TestPostFailsIfBadArgs(c *C) {
308 for i, s := range []struct {
309 args []interface{}
310 errt error
311 }{
312 {nil, ErrBadArgCount},
313 {[]interface{}{}, ErrBadArgCount},
314 {[]interface{}{1}, ErrBadArgCount},
315 {[]interface{}{anAppId, 1}, ErrBadArgType},
316 {[]interface{}{anAppId, "zoom"}, ErrBadJSON},
317 {[]interface{}{1, "hello"}, ErrBadArgType},
318 {[]interface{}{1, 2, 3}, ErrBadArgCount},
319 {[]interface{}{"bar", "hello"}, ErrBadAppId},
320 } {
321 reg, err := new(PostalService).post(aPackageOnBus, s.args, nil)
322 c.Check(reg, IsNil, Commentf("iteration #%d", i))
323 c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
324 }
325}
326
273// Post() tests327// Post() tests
274328
275func (ps *postalSuite) TestPostWorks(c *C) {329func (ps *postalSuite) TestPostWorks(c *C) {
@@ -282,30 +336,31 @@
282 "legacy": fakeLauncher2,336 "legacy": fakeLauncher2,
283 }337 }
284 c.Assert(svc.Start(), IsNil)338 c.Assert(svc.Start(), IsNil)
285 rvs, err := svc.post(aPackageOnBus, []interface{}{anAppId, `{"message":{"world":1}}`}, nil)339
286 c.Assert(err, IsNil)340 app := clickhelp.MustParseAppId(anAppId)
287 c.Check(rvs, IsNil)341 // these two, being for the same app, will be done sequentially.
288 rvs, err = svc.post(aPackageOnBus, []interface{}{anAppId, `{"message":{"moon":1}}`}, nil)342 svc.Post(app, "m1", json.RawMessage(`{"message":{"world":1}}`))
289 c.Assert(err, IsNil)343 svc.Post(app, "m2", json.RawMessage(`{"message":{"moon":1}}`))
290 c.Check(rvs, IsNil)344 classicApp := clickhelp.MustParseAppId("_classic-app")
291 rvs, err = svc.post("_", []interface{}{"_classic-app", `{"message":{"mars":42}}`}, nil)345 svc.Post(classicApp, "m3", json.RawMessage(`{"message":{"mars":42}}`))
292 c.Assert(err, IsNil)
293 c.Check(rvs, IsNil)
294346
295 if ps.fakeLauncher.done != nil {347 if ps.fakeLauncher.done != nil {
296 // wait for the two posts to "launch"348 // wait for the two posts to "launch"
297 takeNextBytes(ps.fakeLauncher.ch)349 takeNextBytes(ps.fakeLauncher.ch)
298 takeNextBytes(ps.fakeLauncher.ch)
299 takeNextBytes(fakeLauncher2.ch)350 takeNextBytes(fakeLauncher2.ch)
300
301 go ps.fakeLauncher.done("0") // OneDone351 go ps.fakeLauncher.done("0") // OneDone
352 go fakeLauncher2.done("0")
353
354 inputData := takeNextBytes(ps.fakeLauncher.ch)
355
356 c.Check(string(inputData), Equals, `{"message":{"moon":1}}`)
357
302 go ps.fakeLauncher.done("1") // OneDone358 go ps.fakeLauncher.done("1") // OneDone
303 go fakeLauncher2.done("0")
304 }359 }
305360
306 c.Check(takeNextError(ch), IsNil) // one,361 c.Check(takeNextBool(ch), Equals, false) // one,
307 c.Check(takeNextError(ch), IsNil) // two,362 c.Check(takeNextBool(ch), Equals, false) // two,
308 c.Check(takeNextError(ch), IsNil) // three posts363 c.Check(takeNextBool(ch), Equals, false) // three posts
309 c.Assert(svc.mbox, HasLen, 2)364 c.Assert(svc.mbox, HasLen, 2)
310 box, ok := svc.mbox[anAppId]365 box, ok := svc.mbox[anAppId]
311 c.Check(ok, Equals, true)366 c.Check(ok, Equals, true)
@@ -313,6 +368,7 @@
313 c.Assert(msgs, HasLen, 2)368 c.Assert(msgs, HasLen, 2)
314 c.Check(msgs[0], Equals, `{"world":1}`)369 c.Check(msgs[0], Equals, `{"world":1}`)
315 c.Check(msgs[1], Equals, `{"moon":1}`)370 c.Check(msgs[1], Equals, `{"moon":1}`)
371 c.Check(box.nids, DeepEquals, []string{"m1", "m2"})
316 box, ok = svc.mbox["_classic-app"]372 box, ok = svc.mbox["_classic-app"]
317 c.Assert(ok, Equals, true)373 c.Assert(ok, Equals, true)
318 msgs = box.AllMessages()374 msgs = box.AllMessages()
@@ -320,7 +376,34 @@
320 c.Check(msgs[0], Equals, `{"mars":42}`)376 c.Check(msgs[0], Equals, `{"mars":42}`)
321}377}
322378
323func (ps *postalSuite) TestPostSignal(c *C) {379func (ps *postalSuite) TestPostCallsMessageHandlerDetails(c *C) {
380 ch := make(chan *launch_helper.HelperOutput)
381 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
382 svc.launchers = map[string]launch_helper.HelperLauncher{
383 "click": ps.fakeLauncher,
384 }
385 c.Assert(svc.Start(), IsNil)
386 // check the message handler gets called
387 app := clickhelp.MustParseAppId(anAppId)
388 f := func(app *click.AppId, nid string, s *launch_helper.HelperOutput) bool {
389 c.Check(app.Base(), Equals, anAppId)
390 c.Check(nid, Equals, "m7")
391 ch <- s
392 return true
393 }
394 svc.SetMessageHandler(f)
395 svc.Post(app, "m7", json.RawMessage("{}"))
396
397 if ps.fakeLauncher.done != nil {
398 takeNextBytes(ps.fakeLauncher.ch)
399
400 go ps.fakeLauncher.done("0") // OneDone
401 }
402
403 c.Check(takeNextHelperOutput(ch), DeepEquals, &launch_helper.HelperOutput{})
404}
405
406func (ps *postalSuite) TestAfterMessageHandlerSignal(c *C) {
324 svc := ps.replaceBuses(NewPostalService(nil, ps.log))407 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
325 svc.msgHandler = nil408 svc.msgHandler = nil
326409
@@ -341,106 +424,24 @@
341 c.Check(callArgs[l-1].Args, DeepEquals, []interface{}{"Post", aPackageOnBus, []interface{}{anAppId}})424 c.Check(callArgs[l-1].Args, DeepEquals, []interface{}{"Post", aPackageOnBus, []interface{}{anAppId}})
342}425}
343426
344func (ps *postalSuite) TestPostFailsIfPostFails(c *C) {427func (ps *postalSuite) TestFailingMessageHandlerSurvived(c *C) {
345 bus := testibus.NewTestingEndpoint(condition.Work(true),428 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
346 condition.Work(false))429 svc.SetMessageHandler(func(*click.AppId, string, *launch_helper.HelperOutput) bool {
347 svc := ps.replaceBuses(NewPostalService(nil, ps.log))430 return false
348 svc.Bus = bus431 })
349 svc.SetMessageHandler(func(*click.AppId, string, *launch_helper.HelperOutput) error { return errors.New("fail") })432
350 _, err := svc.post("/hello", []interface{}{"xyzzy"}, nil)433 hInp := &launch_helper.HelperInput{
351 c.Check(err, NotNil)434 App: clickhelp.MustParseAppId(anAppId),
352}435 }
353436 res := &launch_helper.HelperResult{Input: hInp}
354func (ps *postalSuite) TestPostFailsIfBadArgs(c *C) {437
355 for i, s := range []struct {438 svc.handleHelperResult(res)
356 args []interface{}439
357 errt error440 c.Check(ps.log.Captured(), Equals, "DEBUG msgHandler did not present the notification\n")
358 }{441 // we actually want to send a signal even if we didn't do anything
359 {nil, ErrBadArgCount},442 callArgs := testibus.GetCallArgs(ps.bus)
360 {[]interface{}{}, ErrBadArgCount},443 c.Assert(len(callArgs), Equals, 1)
361 {[]interface{}{1}, ErrBadArgCount},444 c.Check(callArgs[0].Member, Equals, "::Signal")
362 {[]interface{}{anAppId, 1}, ErrBadArgType},
363 {[]interface{}{anAppId, "zoom"}, ErrBadJSON},
364 {[]interface{}{1, "hello"}, ErrBadArgType},
365 {[]interface{}{1, 2, 3}, ErrBadArgCount},
366 {[]interface{}{"bar", "hello"}, ErrBadAppId},
367 } {
368 reg, err := new(PostalService).post(aPackageOnBus, s.args, nil)
369 c.Check(reg, IsNil, Commentf("iteration #%d", i))
370 c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
371 }
372}
373
374//
375// Post (Broadcast) tests
376
377func (ps *postalSuite) TestPostBroadcast(c *C) {
378
379 bus := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1))
380 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
381
382 svc.NotificationsEndp = bus
383 svc.launchers = map[string]launch_helper.HelperLauncher{
384 "legacy": ps.fakeLauncher,
385 }
386 c.Assert(svc.Start(), IsNil)
387
388 msgId := uuid.New()
389 svc.SetMessageHandler(func(app *click.AppId, nid string, output *launch_helper.HelperOutput) error {
390 expectedAppId, _ := click.ParseAppId("_ubuntu-system-settings")
391 c.Check(app, DeepEquals, expectedAppId)
392 c.Check(nid, Equals, msgId)
393 return nil
394 })
395 decoded := map[string]interface{}{
396 "daily/mako": []interface{}{float64(102), "tubular"},
397 }
398 // marshal decoded to json
399 payload, _ := json.Marshal(decoded)
400 appId, _ := click.ParseAppId("_ubuntu-system-settings")
401 err := svc.Post(appId, msgId, payload)
402 c.Assert(err, IsNil)
403
404 if ps.fakeLauncher.done != nil {
405 inputData := takeNextBytes(ps.fakeLauncher.ch)
406 expectedData, _ := json.Marshal(decoded)
407 c.Check(inputData, DeepEquals, expectedData)
408 go ps.fakeLauncher.done("0") // OneDone
409 }
410}
411
412func (ps *postalSuite) TestPostBroadcastDoesNotFail(c *C) {
413 bus := testibus.NewTestingEndpoint(condition.Work(true),
414 condition.Work(false))
415 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
416 svc.launchers = map[string]launch_helper.HelperLauncher{
417 "legacy": ps.fakeLauncher,
418 }
419 c.Assert(svc.Start(), IsNil)
420 svc.NotificationsEndp = bus
421 svc.SetMessageHandler(func(*click.AppId, string, *launch_helper.HelperOutput) error {
422 ps.log.Debugf("about to fail")
423 return errors.New("fail")
424 })
425 ch := installTickMessageHandler(svc)
426 decoded := map[string]interface{}{
427 "daily/mako": []interface{}{float64(102), "tubular"},
428 }
429 // marshal decoded to json
430 payload, _ := json.Marshal(decoded)
431 appId, _ := click.ParseAppId("_ubuntu-system-settings")
432 msgId := uuid.New()
433 err := svc.Post(appId, msgId, payload)
434 c.Assert(err, IsNil)
435
436 if ps.fakeLauncher.done != nil {
437 takeNextBytes(ps.fakeLauncher.ch)
438 go ps.fakeLauncher.done("0") // OneDone
439 }
440
441 c.Check(takeNextError(ch), NotNil) // the messagehandler failed
442 c.Check(err, IsNil) // but broadcast was oblivious
443 c.Check(ps.log.Captured(), Matches, `(?sm).*about to fail$`)
444}445}
445446
446//447//
@@ -492,41 +493,15 @@
492 svc := new(PostalService)493 svc := new(PostalService)
493 c.Assert(svc.msgHandler, IsNil)494 c.Assert(svc.msgHandler, IsNil)
494 var ext = &launch_helper.HelperOutput{}495 var ext = &launch_helper.HelperOutput{}
495 e := errors.New("Hello")496 f := func(_ *click.AppId, _ string, s *launch_helper.HelperOutput) bool { ext = s; return false }
496 f := func(_ *click.AppId, _ string, s *launch_helper.HelperOutput) error { ext = s; return e }
497 c.Check(svc.GetMessageHandler(), IsNil)497 c.Check(svc.GetMessageHandler(), IsNil)
498 svc.SetMessageHandler(f)498 svc.SetMessageHandler(f)
499 c.Check(svc.GetMessageHandler(), NotNil)499 c.Check(svc.GetMessageHandler(), NotNil)
500 hOutput := &launch_helper.HelperOutput{[]byte("37"), nil}500 hOutput := &launch_helper.HelperOutput{[]byte("37"), nil}
501 c.Check(svc.msgHandler(nil, "", hOutput), Equals, e)501 c.Check(svc.msgHandler(nil, "", hOutput), Equals, false)
502 c.Check(ext, DeepEquals, hOutput)502 c.Check(ext, DeepEquals, hOutput)
503}503}
504504
505func (ps *postalSuite) TestPostCallsMessageHandler(c *C) {
506 ch := make(chan *launch_helper.HelperOutput)
507 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
508 svc.launchers = map[string]launch_helper.HelperLauncher{
509 "click": ps.fakeLauncher,
510 }
511 c.Assert(svc.Start(), IsNil)
512 // check the message handler gets called
513 f := func(_ *click.AppId, _ string, s *launch_helper.HelperOutput) error { ch <- s; return nil }
514 svc.SetMessageHandler(f)
515 c.Check(svc.Post(&click.AppId{Click: true}, "thing", json.RawMessage("{}")), IsNil)
516
517 if ps.fakeLauncher.done != nil {
518 takeNextBytes(ps.fakeLauncher.ch)
519
520 go ps.fakeLauncher.done("0") // OneDone
521 }
522
523 c.Check(takeNextHelperOutput(ch), DeepEquals, &launch_helper.HelperOutput{})
524 err := errors.New("ouch")
525 svc.SetMessageHandler(func(*click.AppId, string, *launch_helper.HelperOutput) error { return err })
526 // but the error doesn't bubble out
527 c.Check(svc.Post(&click.AppId{}, "", json.RawMessage("{}")), IsNil)
528}
529
530func (ps *postalSuite) TestMessageHandlerPresents(c *C) {505func (ps *postalSuite) TestMessageHandlerPresents(c *C) {
531 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1))506 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1))
532 svc := NewPostalService(nil, ps.log)507 svc := NewPostalService(nil, ps.log)
@@ -544,12 +519,12 @@
544 vib := &launch_helper.Vibration{Duration: 500}519 vib := &launch_helper.Vibration{Duration: 500}
545 emb := &launch_helper.EmblemCounter{Count: 2, Visible: true}520 emb := &launch_helper.EmblemCounter{Count: 2, Visible: true}
546 output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card, EmblemCounter: emb, Vibrate: vib}}521 output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card, EmblemCounter: emb, Vibrate: vib}}
547 err := svc.messageHandler(&click.AppId{}, "", output)522 b := svc.messageHandler(&click.AppId{}, "", output)
548 c.Assert(err, IsNil)523 c.Assert(b, Equals, true)
549 args := testibus.GetCallArgs(endp)524 args := testibus.GetCallArgs(endp)
550 l := len(args)525 l := len(args)
551 if l < 4 {526 if l < 4 {
552 c.Fatal("not enough elements in resposne from GetCallArgs")527 c.Fatal("not enough elements in response from GetCallArgs")
553 }528 }
554 mm := make([]string, 4)529 mm := make([]string, 4)
555 for i, m := range args[l-4:] {530 for i, m := range args[l-4:] {
@@ -561,7 +536,7 @@
561 c.Check(mm, DeepEquals, []string{"::SetProperty", "::SetProperty", "Notify", "VibratePattern"})536 c.Check(mm, DeepEquals, []string{"::SetProperty", "::SetProperty", "Notify", "VibratePattern"})
562 // For the other ones, check the logs537 // For the other ones, check the logs
563 c.Check(ps.log.Captured(), Matches, `(?sm).* no persistable card:.*`)538 c.Check(ps.log.Captured(), Matches, `(?sm).* no persistable card:.*`)
564 c.Check(ps.log.Captured(), Matches, `(?sm).* no Sound in the notification.*`)539 c.Check(ps.log.Captured(), Matches, `(?sm).* notification has no Sound:.*`)
565}540}
566541
567func (ps *postalSuite) TestMessageHandlerReportsFailedNotifies(c *C) {542func (ps *postalSuite) TestMessageHandlerReportsFailedNotifies(c *C) {
@@ -581,18 +556,18 @@
581 svc := ps.replaceBuses(NewPostalService(nil, ps.log))556 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
582 svc.WindowStackEndp = endp557 svc.WindowStackEndp = endp
583 c.Assert(svc.Start(), IsNil)558 c.Assert(svc.Start(), IsNil)
584 output := &launch_helper.HelperOutput{} // Doesn't matter559 output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{}} // Doesn't matter
585 err := svc.messageHandler(clickhelp.MustParseAppId("com.example.test_test-app_0"), "", output)560 b := svc.messageHandler(clickhelp.MustParseAppId("com.example.test_test-app_0"), "", output)
586 c.Check(err, IsNil)561 c.Check(b, Equals, false)
587 c.Check(ps.log.Captured(), Matches, `(?sm).* Notification skipped because app is focused.*`)562 c.Check(ps.log.Captured(), Matches, `(?sm).* notification skipped because app is focused.*`)
588}563}
589564
590func (ps *postalSuite) TestMessageHandlerReportsButIgnoresUnmarshalErrors(c *C) {565func (ps *postalSuite) TestMessageHandlerReportsButIgnoresUnmarshalErrors(c *C) {
591 svc := ps.replaceBuses(NewPostalService(nil, ps.log))566 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
592 c.Assert(svc.Start(), IsNil)567 c.Assert(svc.Start(), IsNil)
593 output := &launch_helper.HelperOutput{[]byte(`broken`), nil}568 output := &launch_helper.HelperOutput{[]byte(`broken`), nil}
594 err := svc.messageHandler(nil, "", output)569 b := svc.messageHandler(nil, "", output)
595 c.Check(err, IsNil)570 c.Check(b, Equals, false)
596 c.Check(ps.log.Captured(), Matches, "(?msi).*skipping notification: nil.*")571 c.Check(ps.log.Captured(), Matches, "(?msi).*skipping notification: nil.*")
597}572}
598573
@@ -602,20 +577,35 @@
602 c.Assert(svc.Start(), IsNil)577 c.Assert(svc.Start(), IsNil)
603 svc.NotificationsEndp = endp578 svc.NotificationsEndp = endp
604 output := &launch_helper.HelperOutput{[]byte(`{}`), nil}579 output := &launch_helper.HelperOutput{[]byte(`{}`), nil}
605 err := svc.messageHandler(nil, "", output)580 b := svc.messageHandler(nil, "", output)
606 c.Assert(err, IsNil)581 c.Assert(b, Equals, false)
607 c.Check(ps.log.Captured(), Matches, "(?msi).*skipping notification: nil.*")582 c.Check(ps.log.Captured(), Matches, "(?msi).*skipping notification: nil.*")
608}583}
609584
585func (ps *postalSuite) TestMessageHandlerInvalidAction(c *C) {
586 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
587 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false), []string{"com.example.test_test-app"})
588 svc.URLDispatcherEndp = endp
589 c.Assert(svc.Start(), IsNil)
590 card := launch_helper.Card{Actions: []string{"notsupported://test-app"}}
591 output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: &card}}
592 b := svc.messageHandler(clickhelp.MustParseAppId("com.example.test_test-app_0"), "", output)
593 c.Check(b, Equals, false)
594 c.Check(ps.log.Captured(), Matches, `(?sm).*TestURL for \[notsupported://test-app\] failed with no way.*`)
595}
596
610func (ps *postalSuite) TestHandleActionsDispatches(c *C) {597func (ps *postalSuite) TestHandleActionsDispatches(c *C) {
611 svc := ps.replaceBuses(NewPostalService(nil, ps.log))598 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
599 fmm := new(fakeMM)
600 app, _ := click.ParseAppId("com.example.test_test-app")
612 c.Assert(svc.Start(), IsNil)601 c.Assert(svc.Start(), IsNil)
602 svc.messagingMenu = fmm
613 aCh := make(chan *notifications.RawAction)603 aCh := make(chan *notifications.RawAction)
614 rCh := make(chan *reply.MMActionReply)604 rCh := make(chan *reply.MMActionReply)
615 bCh := make(chan bool)605 bCh := make(chan bool)
616 go func() {606 go func() {
617 aCh <- nil // just in case?607 aCh <- nil // just in case?
618 aCh <- &notifications.RawAction{Action: "potato://"}608 aCh <- &notifications.RawAction{App: app, Action: "potato://", Nid: "xyzzy"}
619 close(aCh)609 close(aCh)
620 bCh <- true610 bCh <- true
621 }()611 }()
@@ -624,19 +614,22 @@
624 args := testibus.GetCallArgs(ps.urlDispBus)614 args := testibus.GetCallArgs(ps.urlDispBus)
625 c.Assert(args, HasLen, 1)615 c.Assert(args, HasLen, 1)
626 c.Check(args[0].Member, Equals, "DispatchURL")616 c.Check(args[0].Member, Equals, "DispatchURL")
627 c.Assert(args[0].Args, HasLen, 1)617 c.Assert(args[0].Args, HasLen, 2)
628 c.Assert(args[0].Args[0], Equals, "potato://")618 c.Assert(args[0].Args[0], Equals, "potato://")
619 c.Assert(args[0].Args[1], Equals, app.DispatchPackage())
620 c.Check(fmm.calls, DeepEquals, []string{"remove:xyzzy:true"})
629}621}
630622
631func (ps *postalSuite) TestHandleMMUActionsDispatches(c *C) {623func (ps *postalSuite) TestHandleMMUActionsDispatches(c *C) {
632 svc := ps.replaceBuses(NewPostalService(nil, ps.log))624 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
633 c.Assert(svc.Start(), IsNil)625 c.Assert(svc.Start(), IsNil)
626 app, _ := click.ParseAppId("com.example.test_test-app")
634 aCh := make(chan *notifications.RawAction)627 aCh := make(chan *notifications.RawAction)
635 rCh := make(chan *reply.MMActionReply)628 rCh := make(chan *reply.MMActionReply)
636 bCh := make(chan bool)629 bCh := make(chan bool)
637 go func() {630 go func() {
638 rCh <- nil // just in case?631 rCh <- nil // just in case?
639 rCh <- &reply.MMActionReply{Action: "potato://", Notification: "foo.bar"}632 rCh <- &reply.MMActionReply{App: app, Action: "potato://", Notification: "foo.bar"}
640 close(rCh)633 close(rCh)
641 bCh <- true634 bCh <- true
642 }()635 }()
@@ -645,6 +638,144 @@
645 args := testibus.GetCallArgs(ps.urlDispBus)638 args := testibus.GetCallArgs(ps.urlDispBus)
646 c.Assert(args, HasLen, 1)639 c.Assert(args, HasLen, 1)
647 c.Check(args[0].Member, Equals, "DispatchURL")640 c.Check(args[0].Member, Equals, "DispatchURL")
648 c.Assert(args[0].Args, HasLen, 1)641 c.Assert(args[0].Args, HasLen, 2)
649 c.Assert(args[0].Args[0], Equals, "potato://")642 c.Assert(args[0].Args[0], Equals, "potato://")
643 c.Assert(args[0].Args[1], Equals, app.DispatchPackage())
644}
645
646func (ps *postalSuite) TestValidateActions(c *C) {
647 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
648 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []string{"com.example.test_test-app_0"})
649 svc.URLDispatcherEndp = endp
650 c.Assert(svc.Start(), IsNil)
651 card := launch_helper.Card{Actions: []string{"potato://test-app"}}
652 notif := &launch_helper.Notification{Card: &card}
653 b := svc.validateActions(clickhelp.MustParseAppId("com.example.test_test-app_0"), notif)
654 c.Check(b, Equals, true)
655}
656
657func (ps *postalSuite) TestValidateActionsNoActions(c *C) {
658 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
659 card := launch_helper.Card{}
660 notif := &launch_helper.Notification{Card: &card}
661 b := svc.validateActions(clickhelp.MustParseAppId("com.example.test_test-app_0"), notif)
662 c.Check(b, Equals, true)
663}
664
665func (ps *postalSuite) TestValidateActionsNoCard(c *C) {
666 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
667 notif := &launch_helper.Notification{}
668 b := svc.validateActions(clickhelp.MustParseAppId("com.example.test_test-app_0"), notif)
669 c.Check(b, Equals, true)
670}
671
672type fakeMM struct {
673 calls []string
674}
675
676func (*fakeMM) Present(*click.AppId, string, *launch_helper.Notification) bool { return false }
677func (*fakeMM) GetCh() chan *reply.MMActionReply { return nil }
678func (*fakeMM) StartCleanupLoop() {}
679func (fmm *fakeMM) RemoveNotification(s string, b bool) {
680 fmm.calls = append(fmm.calls, fmt.Sprintf("remove:%s:%t", s, b))
681}
682func (fmm *fakeMM) Clear(*click.AppId, ...string) int {
683 fmm.calls = append(fmm.calls, "clear")
684 return 42
685}
686func (fmm *fakeMM) Tags(*click.AppId) []string {
687 fmm.calls = append(fmm.calls, "tags")
688 return []string{"hello"}
689}
690
691func (ps *postalSuite) TestListPersistent(c *C) {
692 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
693 fmm := new(fakeMM)
694 svc.messagingMenu = fmm
695
696 itags, err := svc.listPersistent(aPackageOnBus, []interface{}{anAppId}, nil)
697 c.Assert(err, IsNil)
698 c.Assert(itags, HasLen, 1)
699 c.Assert(itags[0], FitsTypeOf, []string(nil))
700 tags := itags[0].([]string)
701 c.Check(tags, DeepEquals, []string{"hello"})
702 c.Check(fmm.calls, DeepEquals, []string{"tags"})
703}
704
705func (ps *postalSuite) TestListPersistentErrors(c *C) {
706 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
707 _, err := svc.listPersistent(aPackageOnBus, nil, nil)
708 c.Check(err, Equals, ErrBadArgCount)
709 _, err = svc.listPersistent(aPackageOnBus, []interface{}{42}, nil)
710 c.Check(err, Equals, ErrBadArgType)
711 _, err = svc.listPersistent(aPackageOnBus, []interface{}{"xyzzy"}, nil)
712 c.Check(err, Equals, ErrBadAppId)
713}
714
715func (ps *postalSuite) TestClearPersistent(c *C) {
716 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
717 fmm := new(fakeMM)
718 svc.messagingMenu = fmm
719
720 icleared, err := svc.clearPersistent(aPackageOnBus, []interface{}{anAppId, "one", ""}, nil)
721 c.Assert(err, IsNil)
722 c.Assert(icleared, HasLen, 1)
723 c.Check(icleared[0], Equals, uint32(42))
724}
725
726func (ps *postalSuite) TestClearPersistentErrors(c *C) {
727 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
728 for i, s := range []struct {
729 args []interface{}
730 err error
731 }{
732 {[]interface{}{}, ErrBadArgCount},
733 {[]interface{}{42}, ErrBadArgType},
734 {[]interface{}{"xyzzy"}, ErrBadAppId},
735 {[]interface{}{anAppId, 42}, ErrBadArgType},
736 {[]interface{}{anAppId, "", 42}, ErrBadArgType},
737 } {
738 _, err := svc.clearPersistent(aPackageOnBus, s.args, nil)
739 c.Check(err, Equals, s.err, Commentf("iter %d", i))
740 }
741}
742
743func (ps *postalSuite) TestSetCounter(c *C) {
744 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
745 c.Check(svc.Start(), IsNil)
746
747 _, err := svc.setCounter(aPackageOnBus, []interface{}{anAppId, int32(42), true}, nil)
748 c.Assert(err, IsNil)
749
750 quoted := "/" + string(nih.Quote([]byte(anAppId)))
751
752 callArgs := testibus.GetCallArgs(svc.EmblemCounterEndp)
753 c.Assert(callArgs, HasLen, 2)
754 c.Check(callArgs[0].Member, Equals, "::SetProperty")
755 c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", quoted, dbus.Variant{int32(42)}})
756
757 c.Check(callArgs[1].Member, Equals, "::SetProperty")
758 c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", quoted, dbus.Variant{true}})
759}
760
761func (ps *postalSuite) TestSetCounterErrors(c *C) {
762 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
763 svc.Start()
764 for i, s := range []struct {
765 args []interface{}
766 err error
767 }{
768 {[]interface{}{anAppId, int32(42), true}, nil}, // for reference
769 {[]interface{}{}, ErrBadArgCount},
770 {[]interface{}{anAppId}, ErrBadArgCount},
771 {[]interface{}{anAppId, int32(42)}, ErrBadArgCount},
772 {[]interface{}{anAppId, int32(42), true, "potato"}, ErrBadArgCount},
773 {[]interface{}{"xyzzy", int32(42), true}, ErrBadAppId},
774 {[]interface{}{1234567, int32(42), true}, ErrBadArgType},
775 {[]interface{}{anAppId, "potatoe", true}, ErrBadArgType},
776 {[]interface{}{anAppId, int32(42), "ru"}, ErrBadArgType},
777 } {
778 _, err := svc.setCounter(aPackageOnBus, s.args, nil)
779 c.Check(err, Equals, s.err, Commentf("iter %d", i))
780 }
650}781}
651782
=== modified file 'debian/changelog'
--- debian/changelog 2014-07-23 14:09:31 +0000
+++ debian/changelog 2014-07-29 16:56:22 +0000
@@ -1,3 +1,24 @@
1ubuntu-push (0.50) UNRELEASED; urgency=medium
2
3 [ Samuele Pedroni ]
4 * Cleanup and improve pos/Post tests
5
6 [ Guillermo Gonzalez ]
7 * Add a loop to cleanup MessagingMenu.notifications map when the
8 notifications are dismissed.
9 * Add TestURL to URLDispatcher and update DispatchURL signature.
10 * Add validateActions (check with url-dispatcher->TestURL) method to the
11 PostalService and wire it in messageHandler.
12
13 [ John R. Lenton ]
14 * Implement ListPersistent/ClearPersistent/SetCounter postal endpoints.
15 * Remove snap decisions support.
16 * Remove the notification from the messaging menu when the bubble is
17 acted on.
18 * Don't run more than 5 helpers at a time, and never more than one per app.
19
20 -- John R. Lenton <john.lenton@canonical.com> Mon, 28 Jul 2014 17:44:35 +0200
21
1ubuntu-push (0.49.1+14.10.20140723.1-0ubuntu1) utopic; urgency=medium22ubuntu-push (0.49.1+14.10.20140723.1-0ubuntu1) utopic; urgency=medium
223
3 [ John R. Lenton ]24 [ John R. Lenton ]
425
=== modified file 'launch_helper/helper_output.go'
--- launch_helper/helper_output.go 2014-07-18 14:23:34 +0000
+++ launch_helper/helper_output.go 2014-07-29 16:56:22 +0000
@@ -54,6 +54,7 @@
54 Sound string `json:"sound"` // a sound file. Users can disable this, so don't rely on it exclusively. Defaults to empty (no sound).54 Sound string `json:"sound"` // a sound file. Users can disable this, so don't rely on it exclusively. Defaults to empty (no sound).
55 Vibrate *Vibration `json:"vibrate"` // users can disable this, blah blah. Defaults to null (no vibration)55 Vibrate *Vibration `json:"vibrate"` // users can disable this, blah blah. Defaults to null (no vibration)
56 EmblemCounter *EmblemCounter `json:"emblem-counter"` // puts a counter on an emblem in the launcher. Defaults to nil (no change to emblem counter).56 EmblemCounter *EmblemCounter `json:"emblem-counter"` // puts a counter on an emblem in the launcher. Defaults to nil (no change to emblem counter).
57 Tag string `json:"tag,omitempty"` // tag used for Clear/ListPersistent.
57}58}
5859
59// HelperOutput is the expected output of a helper60// HelperOutput is the expected output of a helper
6061
=== modified file 'launch_helper/helper_test.go'
--- launch_helper/helper_test.go 2014-07-18 14:53:47 +0000
+++ launch_helper/helper_test.go 2014-07-29 16:56:22 +0000
@@ -43,11 +43,11 @@
43}43}
4444
45func (s *runnerSuite) TestTrivialPoolWorks(c *C) {45func (s *runnerSuite) TestTrivialPoolWorks(c *C) {
46 notif := &Notification{Sound: "42"}46 notif := &Notification{Sound: "42", Tag: "foo"}
4747
48 triv := NewTrivialHelperPool(s.testlog)48 triv := NewTrivialHelperPool(s.testlog)
49 ch := triv.Start()49 ch := triv.Start()
50 in := &HelperInput{App: s.app, Payload: []byte(`{"message": {"m":42}, "notification": {"sound": "42"}}`)}50 in := &HelperInput{App: s.app, Payload: []byte(`{"message": {"m":42}, "notification": {"sound": "42", "tag": "foo"}}`)}
51 triv.Run("klick", in)51 triv.Run("klick", in)
52 out := <-ch52 out := <-ch
53 c.Assert(out, NotNil)53 c.Assert(out, NotNil)
5454
=== modified file 'launch_helper/kindpool.go'
--- launch_helper/kindpool.go 2014-07-19 00:18:18 +0000
+++ launch_helper/kindpool.go 2014-07-29 16:56:22 +0000
@@ -60,10 +60,12 @@
60 log logger.Logger60 log logger.Logger
61 chOut chan *HelperResult61 chOut chan *HelperResult
62 chIn chan *HelperInput62 chIn chan *HelperInput
63 chDone chan *click.AppId
63 launchers map[string]HelperLauncher64 launchers map[string]HelperLauncher
64 lock sync.Mutex65 lock sync.Mutex
65 hmap map[string]*HelperArgs66 hmap map[string]*HelperArgs
66 maxRuntime time.Duration67 maxRuntime time.Duration
68 maxNum int
67}69}
6870
69// DefaultLaunchers produces the default map for kind -> HelperLauncher71// DefaultLaunchers produces the default map for kind -> HelperLauncher
@@ -81,12 +83,14 @@
81 hmap: make(map[string]*HelperArgs),83 hmap: make(map[string]*HelperArgs),
82 launchers: launchers,84 launchers: launchers,
83 maxRuntime: 5 * time.Second,85 maxRuntime: 5 * time.Second,
86 maxNum: 5,
84 }87 }
85}88}
8689
87func (pool *kindHelperPool) Start() chan *HelperResult {90func (pool *kindHelperPool) Start() chan *HelperResult {
88 pool.chOut = make(chan *HelperResult)91 pool.chOut = make(chan *HelperResult)
89 pool.chIn = make(chan *HelperInput, InputBufferSize)92 pool.chIn = make(chan *HelperInput, InputBufferSize)
93 pool.chDone = make(chan *click.AppId)
9094
91 for kind, launcher := range pool.launchers {95 for kind, launcher := range pool.launchers {
92 kind1 := kind96 kind1 := kind
@@ -98,18 +102,72 @@
98 }102 }
99 }103 }
100104
101 // xxx make sure at most X helpers are running105 go pool.loop()
102 go func() {
103 for i := range pool.chIn {
104 if pool.handleOne(i) != nil {
105 pool.failOne(i)
106 }
107 }
108 }()
109106
110 return pool.chOut107 return pool.chOut
111}108}
112109
110func (pool *kindHelperPool) loop() {
111 running := make(map[string]bool)
112 var backlog []*HelperInput
113
114 for {
115 select {
116 case in, ok := <-pool.chIn:
117 if !ok {
118 return
119 }
120 if len(running) >= pool.maxNum || running[in.App.Original()] {
121 backlog = append(backlog, in)
122 pool.log.Debugf("current helper input backlog has grown to %d entries.", len(backlog))
123 } else {
124 if pool.tryOne(in) {
125 running[in.App.Original()] = true
126 }
127 }
128 case app := <-pool.chDone:
129 delete(running, app.Original())
130 if len(backlog) == 0 {
131 continue
132 }
133 backlogSz := 0
134 done := false
135 for i, in := range backlog {
136 if in != nil {
137 if !done && !running[in.App.Original()] {
138 backlog[i] = nil
139 if pool.tryOne(in) {
140 running[in.App.Original()] = true
141 done = true
142 }
143 } else {
144 backlogSz++
145 }
146 }
147 }
148 backlog = pool.shrinkBacklog(backlog, backlogSz)
149 pool.log.Debugf("current helper input backlog has shrunk to %d entries.", backlogSz)
150 }
151 }
152}
153
154func (pool *kindHelperPool) shrinkBacklog(backlog []*HelperInput, backlogSz int) []*HelperInput {
155 if backlogSz == 0 {
156 return nil
157 }
158 if cap(backlog) < 2*backlogSz {
159 return backlog
160 }
161 pool.log.Debugf("copying backlog to avoid wasting too much space (%d/%d used)", backlogSz, cap(backlog))
162 clean := make([]*HelperInput, 0, backlogSz)
163 for _, bentry := range backlog {
164 if bentry != nil {
165 clean = append(clean, bentry)
166 }
167 }
168 return clean
169}
170
113func (pool *kindHelperPool) Stop() {171func (pool *kindHelperPool) Stop() {
114 close(pool.chIn)172 close(pool.chIn)
115 for kind, launcher := range pool.launchers {173 for kind, launcher := range pool.launchers {
@@ -125,6 +183,14 @@
125 pool.chIn <- input183 pool.chIn <- input
126}184}
127185
186func (pool *kindHelperPool) tryOne(input *HelperInput) bool {
187 if pool.handleOne(input) != nil {
188 pool.failOne(input)
189 return false
190 }
191 return true
192}
193
128func (pool *kindHelperPool) failOne(input *HelperInput) {194func (pool *kindHelperPool) failOne(input *HelperInput) {
129 pool.log.Errorf("unable to get helper output; putting payload into message")195 pool.log.Errorf("unable to get helper output; putting payload into message")
130 pool.chOut <- &HelperResult{HelperOutput: HelperOutput{Message: input.Payload, Notification: nil}, Input: input}196 pool.chOut <- &HelperResult{HelperOutput: HelperOutput{Message: input.Payload, Notification: nil}, Input: input}
@@ -218,6 +284,7 @@
218 // nothing to do284 // nothing to do
219 return285 return
220 }286 }
287 pool.chDone <- args.Input.App
221 defer func() {288 defer func() {
222 pool.cleanupTempFiles(args.FileIn, args.FileOut)289 pool.cleanupTempFiles(args.FileIn, args.FileOut)
223 }()290 }()
224291
=== modified file 'launch_helper/kindpool_test.go'
--- launch_helper/kindpool_test.go 2014-07-19 00:18:18 +0000
+++ launch_helper/kindpool_test.go 2014-07-29 16:56:22 +0000
@@ -17,6 +17,7 @@
17package launch_helper17package launch_helper
1818
19import (19import (
20 "fmt"
20 "io/ioutil"21 "io/ioutil"
21 "os"22 "os"
22 "path/filepath"23 "path/filepath"
@@ -32,18 +33,30 @@
32)33)
3334
34type poolSuite struct {35type poolSuite struct {
35 log *helpers.TestLogger36 log *helpers.TestLogger
36 pool HelperPool37 pool HelperPool
38 fakeLauncher *fakeHelperLauncher
37}39}
3840
39var _ = Suite(&poolSuite{})41var _ = Suite(&poolSuite{})
4042
43func takeNext(ch chan *HelperResult, c *C) *HelperResult {
44 select {
45 case res := <-ch:
46 return res
47 case <-time.After(100 * time.Millisecond):
48 c.Fatal("timeout waiting for result")
49 }
50 return nil
51}
52
41type fakeHelperLauncher struct {53type fakeHelperLauncher struct {
42 done func(string)54 done func(string)
43 obs int55 obs int
44 err error56 err error
45 lhex string57 lhex string
46 argCh chan [5]string58 argCh chan [5]string
59 runid int
47}60}
4861
49func (fhl *fakeHelperLauncher) InstallObserver(done func(string)) error {62func (fhl *fakeHelperLauncher) InstallObserver(done func(string)) error {
@@ -67,7 +80,9 @@
6780
68func (fhl *fakeHelperLauncher) Launch(appId string, exec string, f1 string, f2 string) (string, error) {81func (fhl *fakeHelperLauncher) Launch(appId string, exec string, f1 string, f2 string) (string, error) {
69 fhl.argCh <- [5]string{"Launch", appId, exec, f1, f2}82 fhl.argCh <- [5]string{"Launch", appId, exec, f1, f2}
70 return "0", fhl.err83 runid := fmt.Sprintf("%d", fhl.runid)
84 fhl.runid++
85 return runid, fhl.err
71}86}
7287
73func (fhl *fakeHelperLauncher) Stop(appId string, iid string) error {88func (fhl *fakeHelperLauncher) Stop(appId string, iid string) error {
@@ -75,12 +90,21 @@
75 return nil90 return nil
76}91}
7792
78var fakeLauncher *fakeHelperLauncher93func (s *poolSuite) waitForArgs(c *C, method string) [5]string {
94 var args [5]string
95 select {
96 case args = <-s.fakeLauncher.argCh:
97 case <-time.After(2 * time.Second):
98 c.Fatal("didn't call " + method)
99 }
100 c.Assert(args[0], Equals, method)
101 return args
102}
79103
80func (s *poolSuite) SetUpTest(c *C) {104func (s *poolSuite) SetUpTest(c *C) {
81 s.log = helpers.NewTestLogger(c, "debug")105 s.log = helpers.NewTestLogger(c, "debug")
82 fakeLauncher = &fakeHelperLauncher{argCh: make(chan [5]string, 10)}106 s.fakeLauncher = &fakeHelperLauncher{argCh: make(chan [5]string, 10)}
83 s.pool = NewHelperPool(map[string]HelperLauncher{"fake": fakeLauncher}, s.log)107 s.pool = NewHelperPool(map[string]HelperLauncher{"fake": s.fakeLauncher}, s.log)
84 xdgCacheHome = c.MkDir108 xdgCacheHome = c.MkDir
85}109}
86110
@@ -99,12 +123,12 @@
99123
100// check that Stop (tries to) remove the observer124// check that Stop (tries to) remove the observer
101func (s *poolSuite) TestStartStopWork(c *C) {125func (s *poolSuite) TestStartStopWork(c *C) {
102 c.Check(fakeLauncher.obs, Equals, 0)126 c.Check(s.fakeLauncher.obs, Equals, 0)
103 s.pool.Start()127 s.pool.Start()
104 c.Check(fakeLauncher.done, NotNil)128 c.Check(s.fakeLauncher.done, NotNil)
105 c.Check(fakeLauncher.obs, Equals, 1)129 c.Check(s.fakeLauncher.obs, Equals, 1)
106 s.pool.Stop()130 s.pool.Stop()
107 c.Check(fakeLauncher.obs, Equals, 0)131 c.Check(s.fakeLauncher.obs, Equals, 0)
108}132}
109133
110func (s *poolSuite) TestRunLaunches(c *C) {134func (s *poolSuite) TestRunLaunches(c *C) {
@@ -119,12 +143,8 @@
119 Payload: []byte(`"hello"`),143 Payload: []byte(`"hello"`),
120 }144 }
121 s.pool.Run("fake", &input)145 s.pool.Run("fake", &input)
122 select {146 launchArgs := s.waitForArgs(c, "Launch")
123 case arg := <-fakeLauncher.argCh:147 c.Check(launchArgs[:3], DeepEquals, []string{"Launch", helpId, "bar"})
124 c.Check(arg[:3], DeepEquals, []string{"Launch", helpId, "bar"})
125 case <-time.After(100 * time.Millisecond):
126 c.Fatal("didn't call Launch")
127 }
128 args := s.pool.(*kindHelperPool).peekId("fake:0", func(*HelperArgs) {})148 args := s.pool.(*kindHelperPool).peekId("fake:0", func(*HelperArgs) {})
129 c.Assert(args, NotNil)149 c.Assert(args, NotNil)
130 args.Timer.Stop()150 args.Timer.Stop()
@@ -135,7 +155,7 @@
135}155}
136156
137func (s *poolSuite) TestRunLaunchesLegacyStyle(c *C) {157func (s *poolSuite) TestRunLaunchesLegacyStyle(c *C) {
138 fakeLauncher.lhex = "lhex"158 s.fakeLauncher.lhex = "lhex"
139 s.pool.Start()159 s.pool.Start()
140 defer s.pool.Stop()160 defer s.pool.Stop()
141 appId := "_legacy"161 appId := "_legacy"
@@ -146,12 +166,8 @@
146 Payload: []byte(`"hello"`),166 Payload: []byte(`"hello"`),
147 }167 }
148 s.pool.Run("fake", &input)168 s.pool.Run("fake", &input)
149 select {169 launchArgs := s.waitForArgs(c, "Launch")
150 case arg := <-fakeLauncher.argCh:170 c.Check(launchArgs[:3], DeepEquals, []string{"Launch", "", "lhex"})
151 c.Check(arg[:3], DeepEquals, []string{"Launch", "", "lhex"})
152 case <-time.After(100 * time.Millisecond):
153 c.Fatal("didn't call Launch")
154 }
155 args := s.pool.(*kindHelperPool).peekId("fake:0", func(*HelperArgs) {})171 args := s.pool.(*kindHelperPool).peekId("fake:0", func(*HelperArgs) {})
156 c.Assert(args, NotNil)172 c.Assert(args, NotNil)
157 args.Timer.Stop()173 args.Timer.Stop()
@@ -170,12 +186,7 @@
170 Payload: []byte(`"hello"`),186 Payload: []byte(`"hello"`),
171 }187 }
172 s.pool.Run("not-there", &input)188 s.pool.Run("not-there", &input)
173 var res *HelperResult189 res := takeNext(ch, c)
174 select {
175 case res = <-ch:
176 case <-time.After(100 * time.Millisecond):
177 c.Fatal("timeout")
178 }
179 c.Check(res.Message, DeepEquals, input.Payload)190 c.Check(res.Message, DeepEquals, input.Payload)
180 c.Check(res.Notification, IsNil)191 c.Check(res.Notification, IsNil)
181 c.Check(*res.Input, DeepEquals, input)192 c.Check(*res.Input, DeepEquals, input)
@@ -191,19 +202,14 @@
191 Payload: []byte(`"hello"`),202 Payload: []byte(`"hello"`),
192 }203 }
193 s.pool.Run("fake", &input)204 s.pool.Run("fake", &input)
194 var res *HelperResult205 res := takeNext(ch, c)
195 select {
196 case res = <-ch:
197 case <-time.After(100 * time.Millisecond):
198 c.Fatal("timeout")
199 }
200 c.Check(res.Message, DeepEquals, input.Payload)206 c.Check(res.Message, DeepEquals, input.Payload)
201 c.Check(res.Notification, IsNil)207 c.Check(res.Notification, IsNil)
202 c.Check(*res.Input, DeepEquals, input)208 c.Check(*res.Input, DeepEquals, input)
203}209}
204210
205func (s *poolSuite) TestRunCantLaunch(c *C) {211func (s *poolSuite) TestRunCantLaunch(c *C) {
206 fakeLauncher.err = cual.ErrCantLaunch212 s.fakeLauncher.err = cual.ErrCantLaunch
207 ch := s.pool.Start()213 ch := s.pool.Start()
208 defer s.pool.Stop()214 defer s.pool.Stop()
209 appId := "com.example.test_test-app"215 appId := "com.example.test_test-app"
@@ -215,18 +221,9 @@
215 Payload: []byte(`"hello"`),221 Payload: []byte(`"hello"`),
216 }222 }
217 s.pool.Run("fake", &input)223 s.pool.Run("fake", &input)
218 select {224 launchArgs := s.waitForArgs(c, "Launch")
219 case arg := <-fakeLauncher.argCh:225 c.Check(launchArgs[:3], DeepEquals, []string{"Launch", helpId, "bar"})
220 c.Check(arg[:3], DeepEquals, []string{"Launch", helpId, "bar"})226 res := takeNext(ch, c)
221 case <-time.After(100 * time.Millisecond):
222 c.Fatal("didn't call Launch")
223 }
224 var res *HelperResult
225 select {
226 case res = <-ch:
227 case <-time.After(100 * time.Millisecond):
228 c.Fatal("timeout")
229 }
230 c.Check(res.Message, DeepEquals, input.Payload)227 c.Check(res.Message, DeepEquals, input.Payload)
231 c.Check(s.log.Captured(), Equals, "DEBUG using helper com.example.test_test-app-helper (exec: bar) for app com.example.test_test-app\n"+"ERROR unable to launch helper com.example.test_test-app-helper: can't launch helper\n"+"ERROR unable to get helper output; putting payload into message\n")228 c.Check(s.log.Captured(), Equals, "DEBUG using helper com.example.test_test-app-helper (exec: bar) for app com.example.test_test-app\n"+"ERROR unable to launch helper com.example.test_test-app-helper: can't launch helper\n"+"ERROR unable to get helper output; putting payload into message\n")
232}229}
@@ -244,27 +241,14 @@
244 Payload: []byte(`"hello"`),241 Payload: []byte(`"hello"`),
245 }242 }
246 s.pool.Run("fake", &input)243 s.pool.Run("fake", &input)
247 select {244 launchArgs := s.waitForArgs(c, "Launch")
248 case arg := <-fakeLauncher.argCh:245 c.Check(launchArgs[0], Equals, "Launch")
249 c.Check(arg[0], Equals, "Launch")246 stopArgs := s.waitForArgs(c, "Stop")
250 case <-time.After(100 * time.Millisecond):247 c.Check(stopArgs[:3], DeepEquals, []string{"Stop", helpId, "0"})
251 c.Fatal("didn't call Launch")
252 }
253 select {
254 case arg := <-fakeLauncher.argCh:
255 c.Check(arg[:3], DeepEquals, []string{"Stop", helpId, "0"})
256 case <-time.After(2 * time.Second):
257 c.Fatal("didn't call Stop")
258 }
259 // this will be invoked248 // this will be invoked
260 go fakeLauncher.done("0")249 go s.fakeLauncher.done("0")
261250
262 var res *HelperResult251 res := takeNext(ch, c)
263 select {
264 case res = <-ch:
265 case <-time.After(100 * time.Millisecond):
266 c.Fatal("timeout")
267 }
268 c.Check(res.Message, DeepEquals, input.Payload)252 c.Check(res.Message, DeepEquals, input.Payload)
269}253}
270254
@@ -294,19 +278,14 @@
294 f, err := os.Create(args.FileOut)278 f, err := os.Create(args.FileOut)
295 c.Assert(err, IsNil)279 c.Assert(err, IsNil)
296 defer f.Close()280 defer f.Close()
297 _, err = f.Write([]byte(`{"notification": {"sound": "hello"}}`))281 _, err = f.Write([]byte(`{"notification": {"sound": "hello", "tag": "a-tag"}}`))
298 c.Assert(err, IsNil)282 c.Assert(err, IsNil)
299283
300 go pool.OneDone("l:1")284 go pool.OneDone("l:1")
301285
302 var res *HelperResult286 res := takeNext(ch, c)
303 select {
304 case res = <-ch:
305 case <-time.After(100 * time.Millisecond):
306 c.Fatal("timeout")
307 }
308287
309 expected := HelperOutput{Notification: &Notification{Sound: "hello"}}288 expected := HelperOutput{Notification: &Notification{Sound: "hello", Tag: "a-tag"}}
310 c.Check(res.HelperOutput, DeepEquals, expected)289 c.Check(res.HelperOutput, DeepEquals, expected)
311 c.Check(pool.hmap, HasLen, 0)290 c.Check(pool.hmap, HasLen, 0)
312}291}
@@ -330,12 +309,7 @@
330309
331 go pool.OneDone("l:1")310 go pool.OneDone("l:1")
332311
333 var res *HelperResult312 res := takeNext(ch, c)
334 select {
335 case res = <-ch:
336 case <-time.After(100 * time.Millisecond):
337 c.Fatal("timeout")
338 }
339313
340 expected := HelperOutput{Message: args.Input.Payload}314 expected := HelperOutput{Message: args.Input.Payload}
341 c.Check(res.HelperOutput, DeepEquals, expected)315 c.Check(res.HelperOutput, DeepEquals, expected)
@@ -368,12 +342,7 @@
368342
369 go pool.OneDone("l:1")343 go pool.OneDone("l:1")
370344
371 var res *HelperResult345 res := takeNext(ch, c)
372 select {
373 case res = <-ch:
374 case <-time.After(100 * time.Millisecond):
375 c.Fatal("timeout")
376 }
377346
378 expected := HelperOutput{Message: args.Input.Payload}347 expected := HelperOutput{Message: args.Input.Payload}
379 c.Check(res.HelperOutput, DeepEquals, expected)348 c.Check(res.HelperOutput, DeepEquals, expected)
@@ -438,3 +407,191 @@
438 c.Check(err, IsNil)407 c.Check(err, IsNil)
439 c.Check(dname, Equals, filepath.Join(tmpDir, "pkg.name"))408 c.Check(dname, Equals, filepath.Join(tmpDir, "pkg.name"))
440}409}
410
411// checks that the a second helper run of an already-running helper
412// (for an app) goes to the backlog
413func (s *poolSuite) TestSecondRunSameAppToBacklog(c *C) {
414 ch := s.pool.Start()
415 defer s.pool.Stop()
416
417 app1 := clickhelp.MustParseAppId("com.example.test_test-app-1")
418 input1 := &HelperInput{
419 App: app1,
420 NotificationId: "foo1",
421 Payload: []byte(`"hello1"`),
422 }
423 app2 := clickhelp.MustParseAppId("com.example.test_test-app-1")
424 input2 := &HelperInput{
425 App: app2,
426 NotificationId: "foo2",
427 Payload: []byte(`"hello2"`),
428 }
429
430 c.Assert(app1.Base(), Equals, app2.Base())
431
432 s.pool.Run("fake", input1)
433 s.pool.Run("fake", input2)
434
435 s.waitForArgs(c, "Launch")
436 go s.fakeLauncher.done("0")
437 takeNext(ch, c)
438
439 // this is where we check that:
440 c.Check(s.log.Captured(), Matches, `(?ms).* helper input backlog has grown to 1 entries.$`)
441}
442
443// checks that the an Nth helper run goes to the backlog
444func (s *poolSuite) TestRunNthAppToBacklog(c *C) {
445 s.pool.(*kindHelperPool).maxNum = 2
446 ch := s.pool.Start()
447 defer s.pool.Stop()
448
449 app1 := clickhelp.MustParseAppId("com.example.test_test-app-1")
450 input1 := &HelperInput{
451 App: app1,
452 NotificationId: "foo1",
453 Payload: []byte(`"hello1"`),
454 }
455 app2 := clickhelp.MustParseAppId("com.example.test_test-app-2")
456 input2 := &HelperInput{
457 App: app2,
458 NotificationId: "foo2",
459 Payload: []byte(`"hello2"`),
460 }
461 app3 := clickhelp.MustParseAppId("com.example.test_test-app-3")
462 input3 := &HelperInput{
463 App: app3,
464 NotificationId: "foo3",
465 Payload: []byte(`"hello3"`),
466 }
467
468 s.pool.Run("fake", input1)
469 s.waitForArgs(c, "Launch")
470
471 s.pool.Run("fake", input2)
472 s.log.ResetCapture()
473 s.waitForArgs(c, "Launch")
474
475 s.pool.Run("fake", input3)
476
477 go s.fakeLauncher.done("0")
478 s.waitForArgs(c, "Launch")
479
480 res := takeNext(ch, c)
481 c.Assert(res, NotNil)
482 c.Assert(res.Input, NotNil)
483 c.Assert(res.Input.App, NotNil)
484 c.Assert(res.Input.App.Original(), Equals, "com.example.test_test-app-1")
485 go s.fakeLauncher.done("1")
486 go s.fakeLauncher.done("2")
487 takeNext(ch, c)
488 takeNext(ch, c)
489
490 // this is the crux: we're checking that the third Run() went to the backlog.
491 c.Check(s.log.Captured(), Matches,
492 `(?ms).* helper input backlog has grown to 1 entries\.$.*shrunk to 0 entries\.$`)
493}
494
495func (s *poolSuite) TestRunBacklogFailedContinuesDiffApp(c *C) {
496 s.pool.(*kindHelperPool).maxNum = 1
497 ch := s.pool.Start()
498 defer s.pool.Stop()
499
500 app1 := clickhelp.MustParseAppId("com.example.test_test-app-1")
501 input1 := &HelperInput{
502 App: app1,
503 NotificationId: "foo1",
504 Payload: []byte(`"hello1"`),
505 }
506 app2 := clickhelp.MustParseAppId("com.example.test_test-app-2")
507 input2 := &HelperInput{
508 App: app2,
509 NotificationId: "foo2",
510 Payload: []byte(`"hello2"`),
511 }
512 app3 := clickhelp.MustParseAppId("com.example.test_test-app-3")
513 input3 := &HelperInput{
514 App: app3,
515 NotificationId: "foo3",
516 Payload: []byte(`"hello3"`),
517 }
518 app4 := clickhelp.MustParseAppId("com.example.test_test-app-4")
519 input4 := &HelperInput{
520 App: app4,
521 NotificationId: "foo4",
522 Payload: []byte(`"hello4"`),
523 }
524
525 s.pool.Run("fake", input1)
526 s.waitForArgs(c, "Launch")
527 s.pool.Run("NOT-THERE", input2) // this will fail
528 s.pool.Run("fake", input3)
529 s.pool.Run("fake", input4)
530
531 go s.fakeLauncher.done("0")
532 // Everything up to here was just set-up.
533 //
534 // What we're checking for is that, if a helper launch fails, the
535 // next one in the backlog is picked up.
536 c.Assert(takeNext(ch, c).Input.App, Equals, app1)
537 c.Assert(takeNext(ch, c).Input.App, Equals, app2)
538 go s.fakeLauncher.done("2")
539 s.waitForArgs(c, "Launch")
540 c.Check(s.log.Captured(), Matches,
541 `(?ms).* helper input backlog has grown to 3 entries\.$.*shrunk to 1 entries\.$`)
542}
543
544func (s *poolSuite) TestBigBacklogShrinks(c *C) {
545 s.pool.(*kindHelperPool).maxNum = 1
546 ch := s.pool.Start()
547 defer s.pool.Stop()
548 numBad := 10
549
550 app := clickhelp.MustParseAppId("com.example.test_test-app")
551 s.pool.Run("fake", &HelperInput{App: app, NotificationId: "0", Payload: []byte(`""`)})
552
553 for i := 0; i < numBad; i++ {
554 s.pool.Run("NOT-THERE", &HelperInput{App: app})
555 }
556 s.pool.Run("fake", &HelperInput{App: app, NotificationId: "1", Payload: []byte(`""`)})
557 s.pool.Run("fake", &HelperInput{App: app, NotificationId: "2", Payload: []byte(`""`)})
558 s.waitForArgs(c, "Launch")
559 go s.fakeLauncher.done("0")
560 // now we should get the fake + all the bad ones
561 for i := 0; i < numBad+1; i++ {
562 takeNext(ch, c)
563 }
564 s.waitForArgs(c, "Launch")
565 go s.fakeLauncher.done("1")
566 takeNext(ch, c)
567 // so now there's one good one "running", and one more waiting.
568 c.Check(s.log.Captured(), Matches, `(?ms).* shrunk to 1 entries\.$`)
569 // and the shrinker shrunk
570 c.Check(s.log.Captured(), Matches, `(?ms).*copying backlog to avoid wasting too much space .*`)
571}
572
573func (s *poolSuite) TestBacklogShrinkerNilToNil(c *C) {
574 pool := s.pool.(*kindHelperPool)
575 c.Check(pool.shrinkBacklog(nil, 0), IsNil)
576}
577
578func (s *poolSuite) TestBacklogShrinkerEmptyToNil(c *C) {
579 pool := s.pool.(*kindHelperPool)
580 empty := []*HelperInput{nil, nil, nil}
581 c.Check(pool.shrinkBacklog(empty, 0), IsNil)
582}
583
584func (s *poolSuite) TestBacklogShrinkerFullUntouched(c *C) {
585 pool := s.pool.(*kindHelperPool)
586 input := &HelperInput{}
587 full := []*HelperInput{input, input, input}
588 c.Check(pool.shrinkBacklog(full, 3), DeepEquals, full)
589}
590
591func (s *poolSuite) TestBacklogShrinkerSparseShrunk(c *C) {
592 pool := s.pool.(*kindHelperPool)
593 input := &HelperInput{}
594 sparse := []*HelperInput{nil, input, nil, input, nil}
595 full := []*HelperInput{input, input}
596 c.Check(pool.shrinkBacklog(sparse, 2), DeepEquals, full)
597}
441598
=== modified file 'messaging/cmessaging/cmessaging.go'
--- messaging/cmessaging/cmessaging.go 2014-07-16 16:10:03 +0000
+++ messaging/cmessaging/cmessaging.go 2014-07-29 16:56:22 +0000
@@ -25,11 +25,16 @@
25void add_notification(const gchar* desktop_id, const gchar* notification_id,25void add_notification(const gchar* desktop_id, const gchar* notification_id,
26 const gchar* icon_path, const gchar* summary, const gchar* body,26 const gchar* icon_path, const gchar* summary, const gchar* body,
27 gint64 timestamp, const gchar** actions, gpointer obj);27 gint64 timestamp, const gchar** actions, gpointer obj);
28
29void remove_notification(const gchar* desktop_id, const gchar* notification_id);
30
31gboolean notification_exists(const gchar* desktop_id, const gchar* notification_id);
28*/32*/
29import "C"33import "C"
30import "unsafe"34import "unsafe"
3135
32import (36import (
37 "launchpad.net/ubuntu-push/click"
33 "launchpad.net/ubuntu-push/launch_helper"38 "launchpad.net/ubuntu-push/launch_helper"
34 "launchpad.net/ubuntu-push/messaging/reply"39 "launchpad.net/ubuntu-push/messaging/reply"
35)40)
@@ -37,6 +42,9 @@
37type Payload struct {42type Payload struct {
38 Ch chan *reply.MMActionReply43 Ch chan *reply.MMActionReply
39 Actions []string44 Actions []string
45 App *click.AppId
46 Tag string
47 Gone bool
40}48}
4149
42func gchar(s string) *C.gchar {50func gchar(s string) *C.gchar {
@@ -56,7 +64,7 @@
56 if action == "" && len(payload.Actions) >= 2 {64 if action == "" && len(payload.Actions) >= 2 {
57 action = payload.Actions[1]65 action = payload.Actions[1]
58 }66 }
59 mmar := &reply.MMActionReply{Notification: C.GoString(c_notification), Action: action}67 mmar := &reply.MMActionReply{Notification: C.GoString(c_notification), Action: action, App: payload.App}
60 payload.Ch <- mmar68 payload.Ch <- mmar
61}69}
6270
@@ -81,6 +89,26 @@
81 C.add_notification(desktop_id, notification_id, icon_path, summary, body, timestamp, nil, (C.gpointer)(payload))89 C.add_notification(desktop_id, notification_id, icon_path, summary, body, timestamp, nil, (C.gpointer)(payload))
82}90}
8391
92func RemoveNotification(desktopId string, notificationId string) {
93 desktop_id := gchar(desktopId)
94 defer gfree(desktop_id)
95
96 notification_id := gchar(notificationId)
97 defer gfree(notification_id)
98
99 C.remove_notification(desktop_id, notification_id)
100}
101
102func NotificationExists(desktopId string, notificationId string) bool {
103 notification_id := gchar(notificationId)
104 defer gfree(notification_id)
105
106 desktop_id := gchar(desktopId)
107 defer gfree(desktop_id)
108
109 return C.notification_exists(desktop_id, notification_id) == C.TRUE
110}
111
84func init() {112func init() {
85 go C.g_main_loop_run(C.g_main_loop_new(nil, C.FALSE))113 go C.g_main_loop_run(C.g_main_loop_new(nil, C.FALSE))
86}114}
87115
=== modified file 'messaging/cmessaging/cmessaging_c.go'
--- messaging/cmessaging/cmessaging_c.go 2014-07-16 13:59:39 +0000
+++ messaging/cmessaging/cmessaging_c.go 2014-07-29 16:56:22 +0000
@@ -25,10 +25,11 @@
25 handleActivate(action, messaging_menu_message_get_id(msg), obj);25 handleActivate(action, messaging_menu_message_get_id(msg), obj);
26}26}
2727
28static GHashTable* map = NULL;
29
28void add_notification (const gchar* desktop_id, const gchar* notification_id,30void add_notification (const gchar* desktop_id, const gchar* notification_id,
29 const gchar* icon_path, const gchar* summary, const gchar* body,31 const gchar* icon_path, const gchar* summary, const gchar* body,
30 gint64 timestamp, const gchar** actions, gpointer obj) {32 gint64 timestamp, const gchar** actions, gpointer obj) {
31 static GHashTable* map = NULL;
32 if (map == NULL) {33 if (map == NULL) {
33 map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);34 map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
34 }35 }
@@ -54,5 +55,34 @@
54 g_signal_connect(msg, "activate", G_CALLBACK(activate_cb), obj);55 g_signal_connect(msg, "activate", G_CALLBACK(activate_cb), obj);
55 g_object_unref(msg);56 g_object_unref(msg);
56}57}
58
59void remove_notification (const gchar* desktop_id, const gchar* notification_id) {
60 if (map == NULL) {
61 return;
62 }
63 MessagingMenuApp* app = g_hash_table_lookup (map, desktop_id);
64 if (app == NULL) {
65 // no app in the hash table, bailout
66 return;
67 }
68 messaging_menu_app_remove_message_by_id (app, notification_id);
69}
70
71gboolean notification_exists (const gchar* desktop_id, const gchar* notification_id) {
72 if (map == NULL) {
73 return FALSE;
74 }
75 MessagingMenuApp* app = g_hash_table_lookup (map, desktop_id);
76 if (app == NULL) {
77 // no app in the hash table, bailout
78 return FALSE;
79 }
80 MessagingMenuMessage* msg = messaging_menu_app_get_message(app, notification_id);
81 if (msg != NULL) {
82 // the notification is still there
83 return TRUE;
84 }
85 return FALSE;
86}
57*/87*/
58import "C"88import "C"
5989
=== modified file 'messaging/messaging.go'
--- messaging/messaging.go 2014-07-16 17:51:06 +0000
+++ messaging/messaging.go 2014-07-29 16:56:22 +0000
@@ -21,6 +21,7 @@
21import (21import (
22 "encoding/json"22 "encoding/json"
23 "sync"23 "sync"
24 "time"
2425
25 "launchpad.net/ubuntu-push/bus/notifications"26 "launchpad.net/ubuntu-push/bus/notifications"
26 "launchpad.net/ubuntu-push/click"27 "launchpad.net/ubuntu-push/click"
@@ -30,56 +31,166 @@
30 "launchpad.net/ubuntu-push/messaging/reply"31 "launchpad.net/ubuntu-push/messaging/reply"
31)32)
3233
34var cleanupLoopDuration = 5 * time.Minute
35
33type MessagingMenu struct {36type MessagingMenu struct {
34 Log logger.Logger37 Log logger.Logger
35 Ch chan *reply.MMActionReply38 Ch chan *reply.MMActionReply
36 notifications map[string]*cmessaging.Payload39 notifications map[string]*cmessaging.Payload // keep a ref to the Payload used in the MMU callback
37 lock sync.RWMutex40 lock sync.RWMutex
41 stopCleanupLoopCh chan bool
42 ticker *time.Ticker
43 tickerCh <-chan time.Time
38}44}
3945
40// New returns a new MessagingMenu46// New returns a new MessagingMenu
41func New(log logger.Logger) *MessagingMenu {47func New(log logger.Logger) *MessagingMenu {
42 return &MessagingMenu{Log: log, Ch: make(chan *reply.MMActionReply), notifications: make(map[string]*cmessaging.Payload)}48 ticker := time.NewTicker(cleanupLoopDuration)
49 stopCh := make(chan bool)
50 return &MessagingMenu{Log: log, Ch: make(chan *reply.MMActionReply), notifications: make(map[string]*cmessaging.Payload), ticker: ticker, tickerCh: ticker.C, stopCleanupLoopCh: stopCh}
43}51}
4452
45var cAddNotification = cmessaging.AddNotification53var cAddNotification = cmessaging.AddNotification
4654var cRemoveNotification = cmessaging.RemoveNotification
47func (mmu *MessagingMenu) addNotification(desktopId string, notificationId string, card *launch_helper.Card, actions []string) {55var cNotificationExists = cmessaging.NotificationExists
48 payload := &cmessaging.Payload{Ch: mmu.Ch, Actions: actions}56
57// GetCh returns the reply channel, exactly like mm.Ch.
58func (mmu *MessagingMenu) GetCh() chan *reply.MMActionReply {
59 return mmu.Ch
60}
61
62func (mmu *MessagingMenu) addNotification(app *click.AppId, notificationId string, tag string, card *launch_helper.Card, actions []string) {
49 mmu.lock.Lock()63 mmu.lock.Lock()
50 // XXX: only gets removed if the action is activated.64 defer mmu.lock.Unlock()
65 payload := &cmessaging.Payload{Ch: mmu.Ch, Actions: actions, App: app, Tag: tag}
51 mmu.notifications[notificationId] = payload66 mmu.notifications[notificationId] = payload
52 mmu.lock.Unlock()67 cAddNotification(app.DesktopId(), notificationId, card, payload)
53 cAddNotification(desktopId, notificationId, card, payload)
54}68}
5569
56// RemoveNotification deletes the notification from internal map70func (mmu *MessagingMenu) RemoveNotification(notificationId string, fromUI bool) {
57func (mmu *MessagingMenu) RemoveNotification(notificationId string) {
58 mmu.lock.Lock()71 mmu.lock.Lock()
59 defer mmu.lock.Unlock()72 defer mmu.lock.Unlock()
73 payload := mmu.notifications[notificationId]
60 delete(mmu.notifications, notificationId)74 delete(mmu.notifications, notificationId)
61}75 if payload != nil && payload.App != nil && fromUI {
6276 cRemoveNotification(payload.App.DesktopId(), notificationId)
63func (mmu *MessagingMenu) Present(app *click.AppId, notificationId string, notification *launch_helper.Notification) {77 }
64 if notification == nil || notification.Card == nil || !notification.Card.Persist || notification.Card.Summary == "" {78}
65 mmu.Log.Debugf("[%s] no notification or notification has no persistable card: %#v", notificationId, notification)79
66 return80// cleanupNotifications remove notifications that were cleared from the messaging menu
67 }81func (mmu *MessagingMenu) cleanUpNotifications() {
68 actions := make([]string, 2*len(notification.Card.Actions))82 mmu.lock.Lock()
69 for i, action := range notification.Card.Actions {83 defer mmu.lock.Unlock()
84 for nid, payload := range mmu.notifications {
85 if payload.Gone {
86 // sweep
87 delete(mmu.notifications, nid)
88 // don't check the mmu for this nid
89 continue
90 }
91 exists := cNotificationExists(payload.App.DesktopId(), nid)
92 if !exists {
93 // mark
94 payload.Gone = true
95 }
96 }
97}
98
99func (mmu *MessagingMenu) StartCleanupLoop() {
100 mmu.doStartCleanupLoop(mmu.cleanUpNotifications)
101}
102
103func (mmu *MessagingMenu) doStartCleanupLoop(cleanupFunc func()) {
104 go func() {
105 for {
106 select {
107 case <-mmu.tickerCh:
108 cleanupFunc()
109 case <-mmu.stopCleanupLoopCh:
110 mmu.ticker.Stop()
111 mmu.Log.Debugf("CleanupLoop stopped.")
112 return
113 }
114 }
115 }()
116}
117
118func (mmu *MessagingMenu) StopCleanupLoop() {
119 mmu.stopCleanupLoopCh <- true
120}
121
122func (mmu *MessagingMenu) Tags(app *click.AppId) []string {
123 orig := app.Original()
124 tags := []string(nil)
125 mmu.lock.RLock()
126 defer mmu.lock.RUnlock()
127 for _, payload := range mmu.notifications {
128 if payload.App.Original() == orig {
129 tags = append(tags, payload.Tag)
130 }
131 }
132 return tags
133}
134
135func (mmu *MessagingMenu) Clear(app *click.AppId, tags ...string) int {
136 orig := app.Original()
137 var nids []string
138
139 mmu.lock.RLock()
140 // O(n×m). Should be small n and m though.
141 for nid, payload := range mmu.notifications {
142 if payload.App.Original() == orig {
143 if len(tags) == 0 {
144 nids = append(nids, nid)
145 } else {
146 for _, tag := range tags {
147 if payload.Tag == tag {
148 nids = append(nids, nid)
149 }
150 }
151 }
152 }
153 }
154 mmu.lock.RUnlock()
155
156 for _, nid := range nids {
157 mmu.RemoveNotification(nid, true)
158 }
159
160 return len(nids)
161}
162
163func (mmu *MessagingMenu) Present(app *click.AppId, nid string, notification *launch_helper.Notification) bool {
164 if notification == nil {
165 panic("please check notification is not nil before calling present")
166 }
167
168 card := notification.Card
169
170 if card == nil || !card.Persist || card.Summary == "" {
171 mmu.Log.Debugf("[%s] notification has no persistable card: %#v", nid, card)
172 return false
173 }
174
175 actions := make([]string, 2*len(card.Actions))
176 for i, action := range card.Actions {
70 act, err := json.Marshal(&notifications.RawAction{177 act, err := json.Marshal(&notifications.RawAction{
71 App: app,178 App: app,
72 Nid: notificationId,179 Nid: nid,
73 ActionId: i,180 ActionId: i,
74 Action: action,181 Action: action,
75 })182 })
76 if err != nil {183 if err != nil {
77 mmu.Log.Errorf("Failed to build action: %s", action)184 mmu.Log.Errorf("Failed to build action: %s", action)
78 return185 return false
79 }186 }
80 actions[2*i] = string(act)187 actions[2*i] = string(act)
81 actions[2*i+1] = action188 actions[2*i+1] = action
82 }189 }
83190
84 mmu.addNotification(app.DesktopId(), notificationId, notification.Card, actions)191 mmu.Log.Debugf("[%s] creating notification centre entry for %s (summary: %s)", nid, app.Base(), card.Summary)
192
193 mmu.addNotification(app, nid, notification.Tag, card, actions)
194
195 return true
85}196}
86197
=== modified file 'messaging/messaging_test.go'
--- messaging/messaging_test.go 2014-07-16 17:54:32 +0000
+++ messaging/messaging_test.go 2014-07-29 16:56:22 +0000
@@ -17,6 +17,8 @@
17package messaging17package messaging
1818
19import (19import (
20 "time"
21
20 . "launchpad.net/gocheck"22 . "launchpad.net/gocheck"
21 "testing"23 "testing"
2224
@@ -24,6 +26,7 @@
24 clickhelp "launchpad.net/ubuntu-push/click/testing"26 clickhelp "launchpad.net/ubuntu-push/click/testing"
25 "launchpad.net/ubuntu-push/launch_helper"27 "launchpad.net/ubuntu-push/launch_helper"
26 "launchpad.net/ubuntu-push/messaging/cmessaging"28 "launchpad.net/ubuntu-push/messaging/cmessaging"
29 "launchpad.net/ubuntu-push/messaging/reply"
27 helpers "launchpad.net/ubuntu-push/testing"30 helpers "launchpad.net/ubuntu-push/testing"
28)31)
2932
@@ -41,6 +44,17 @@
41 cAddNotification = func(a string, n string, c *launch_helper.Card, payload *cmessaging.Payload) {44 cAddNotification = func(a string, n string, c *launch_helper.Card, payload *cmessaging.Payload) {
42 ms.log.Debugf("ADD: app: %s, not: %s, card: %v, chan: %v", a, n, c, payload)45 ms.log.Debugf("ADD: app: %s, not: %s, card: %v, chan: %v", a, n, c, payload)
43 }46 }
47 cRemoveNotification = func(a, n string) {
48 ms.log.Debugf("REMOVE: app: %s, not: %s", a, n)
49 }
50 // just in case
51 cNotificationExists = nil
52}
53
54func (ms *MessagingSuite) TearDownSuite(c *C) {
55 cAddNotification = cmessaging.AddNotification
56 cRemoveNotification = cmessaging.RemoveNotification
57 cNotificationExists = cmessaging.NotificationExists
44}58}
4559
46func (ms *MessagingSuite) SetUpTest(c *C) {60func (ms *MessagingSuite) SetUpTest(c *C) {
@@ -53,7 +67,7 @@
53 card := launch_helper.Card{Summary: "ehlo", Persist: true}67 card := launch_helper.Card{Summary: "ehlo", Persist: true}
54 notif := launch_helper.Notification{Card: &card}68 notif := launch_helper.Notification{Card: &card}
5569
56 mmu.Present(ms.app, "notif-id", &notif)70 c.Check(mmu.Present(ms.app, "notif-id", &notif), Equals, true)
5771
58 c.Check(ms.log.Captured(), Matches, `(?s).* ADD:.*notif-id.*`)72 c.Check(ms.log.Captured(), Matches, `(?s).* ADD:.*notif-id.*`)
59}73}
@@ -63,7 +77,7 @@
63 card := launch_helper.Card{Persist: true}77 card := launch_helper.Card{Persist: true}
64 notif := launch_helper.Notification{Card: &card}78 notif := launch_helper.Notification{Card: &card}
6579
66 mmu.Present(ms.app, "notif-id", &notif)80 c.Check(mmu.Present(ms.app, "notif-id", &notif), Equals, false)
6781
68 c.Check(ms.log.Captured(), Matches, "(?sm).*has no persistable card.*")82 c.Check(ms.log.Captured(), Matches, "(?sm).*has no persistable card.*")
69}83}
@@ -73,54 +87,248 @@
73 card := launch_helper.Card{Summary: "ehlo"}87 card := launch_helper.Card{Summary: "ehlo"}
74 notif := launch_helper.Notification{Card: &card}88 notif := launch_helper.Notification{Card: &card}
7589
76 mmu.Present(ms.app, "notif-id", &notif)90 c.Check(mmu.Present(ms.app, "notif-id", &notif), Equals, false)
7791
78 c.Check(ms.log.Captured(), Matches, "(?sm).*has no persistable card.*")92 c.Check(ms.log.Captured(), Matches, "(?sm).*has no persistable card.*")
79}93}
8094
81func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNil(c *C) {95func (ms *MessagingSuite) TestPresentPanicsIfNil(c *C) {
82 mmu := New(ms.log)96 mmu := New(ms.log)
83 mmu.Present(ms.app, "notif-id", nil)97 c.Check(func() { mmu.Present(ms.app, "notif-id", nil) }, Panics, `please check notification is not nil before calling present`)
84 c.Check(ms.log.Captured(), Matches, "(?sm).*no notification.*")
85}98}
8699
87func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNilCard(c *C) {100func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNilCard(c *C) {
88 mmu := New(ms.log)101 mmu := New(ms.log)
89 mmu.Present(ms.app, "notif-id", &launch_helper.Notification{})102 c.Check(mmu.Present(ms.app, "notif-id", &launch_helper.Notification{}), Equals, false)
90 c.Check(ms.log.Captured(), Matches, "(?sm).*no notification.*")103 c.Check(ms.log.Captured(), Matches, "(?sm).*no persistable card.*")
91}104}
92105
93func (ms *MessagingSuite) TestPresentWithActions(c *C) {106func (ms *MessagingSuite) TestPresentWithActions(c *C) {
94 mmu := New(ms.log)107 mmu := New(ms.log)
95 card := launch_helper.Card{Summary: "ehlo", Persist: true, Actions: []string{"action-1"}}108 card := launch_helper.Card{Summary: "ehlo", Persist: true, Actions: []string{"action-1"}}
96 notif := launch_helper.Notification{Card: &card}109 notif := launch_helper.Notification{Card: &card, Tag: "a-tag"}
97110
98 mmu.Present(ms.app, "notif-id", &notif)111 c.Check(mmu.Present(ms.app, "notif-id", &notif), Equals, true)
99112
100 c.Check(ms.log.Captured(), Matches, `(?s).* ADD:.*notif-id.*`)113 c.Check(ms.log.Captured(), Matches, `(?s).* ADD:.*notif-id.*`)
101114
102 payload, _ := mmu.notifications["notif-id"]115 payload, _ := mmu.notifications["notif-id"]
103 c.Check(payload.Ch, Equals, mmu.Ch)116 c.Check(payload.Ch, Equals, mmu.Ch)
104 c.Check(len(payload.Actions), Equals, 2)117 c.Check(len(payload.Actions), Equals, 2)
118 c.Check(payload.Tag, Equals, "a-tag")
105 rawAction := "{\"app\":\"com.example.test_test_0\",\"act\":\"action-1\",\"nid\":\"notif-id\"}"119 rawAction := "{\"app\":\"com.example.test_test_0\",\"act\":\"action-1\",\"nid\":\"notif-id\"}"
106 c.Check(payload.Actions[0], Equals, rawAction)120 c.Check(payload.Actions[0], Equals, rawAction)
107 c.Check(payload.Actions[1], Equals, "action-1")121 c.Check(payload.Actions[1], Equals, "action-1")
108}122}
109123
124func (ms *MessagingSuite) TestTagsListsTags(c *C) {
125 mmu := New(ms.log)
126 f := func(s string) *launch_helper.Notification {
127 card := launch_helper.Card{Summary: "tag: \"" + s + "\"", Persist: true}
128 return &launch_helper.Notification{Card: &card, Tag: s}
129 }
130
131 c.Check(mmu.Tags(ms.app), IsNil)
132 c.Assert(mmu.Present(ms.app, "notif1", f("one")), Equals, true)
133 c.Check(mmu.Tags(ms.app), DeepEquals, []string{"one"})
134 c.Assert(mmu.Present(ms.app, "notif2", f("")), Equals, true)
135 c.Check(mmu.Tags(ms.app), DeepEquals, []string{"one", ""})
136 // and an empty notification doesn't count
137 c.Assert(mmu.Present(ms.app, "notif3", &launch_helper.Notification{Tag: "X"}), Equals, false)
138 c.Check(mmu.Tags(ms.app), DeepEquals, []string{"one", ""})
139 // and they go away if we remove one
140 mmu.RemoveNotification("notif1", false)
141 c.Check(mmu.Tags(ms.app), DeepEquals, []string{""})
142 mmu.RemoveNotification("notif2", false)
143 c.Check(mmu.Tags(ms.app), IsNil)
144}
145
146func (ms *MessagingSuite) TestClearClears(c *C) {
147 app1 := ms.app
148 app2 := clickhelp.MustParseAppId("com.example.test_test-2_0")
149 app3 := clickhelp.MustParseAppId("com.example.test_test-3_0")
150 mm := New(ms.log)
151 f := func(app *click.AppId, nid string, tag string, withCard bool) bool {
152 notif := launch_helper.Notification{Tag: tag}
153 card := launch_helper.Card{Summary: "tag: \"" + tag + "\"", Persist: true}
154 if withCard {
155 notif.Card = &card
156 }
157 return mm.Present(app, nid, &notif)
158 }
159 // create a bunch
160 c.Assert(f(app1, "notif1", "one", true), Equals, true)
161 c.Assert(f(app1, "notif2", "two", true), Equals, true)
162 c.Assert(f(app1, "notif3", "", true), Equals, true)
163 c.Assert(f(app2, "notif4", "one", true), Equals, true)
164 c.Assert(f(app2, "notif5", "two", true), Equals, true)
165 c.Assert(f(app3, "notif6", "one", true), Equals, true)
166 c.Assert(f(app3, "notif7", "", true), Equals, true)
167
168 // that is:
169 // app 1: "one", "two", "";
170 // app 2: "one", "two";
171 // app 3: "one", ""
172 c.Assert(mm.Tags(app1), DeepEquals, []string{"one", "two", ""})
173 c.Assert(mm.Tags(app2), DeepEquals, []string{"one", "two"})
174 c.Assert(mm.Tags(app3), DeepEquals, []string{"one", ""})
175
176 // clearing a non-existent tag does nothing
177 c.Check(mm.Clear(app1, "foo"), Equals, 0)
178 c.Check(mm.Tags(app1), HasLen, 3)
179 c.Check(mm.Tags(app2), HasLen, 2)
180 c.Check(mm.Tags(app3), HasLen, 2)
181
182 // asking to clear a tag that exists only for another app does nothing
183 c.Check(mm.Clear(app3, "two"), Equals, 0)
184 c.Check(mm.Tags(app1), HasLen, 3)
185 c.Check(mm.Tags(app2), HasLen, 2)
186 c.Check(mm.Tags(app3), HasLen, 2)
187
188 // asking to clear a list of tags, only one of which is yours, only clears yours
189 c.Check(mm.Clear(app3, "one", "two"), Equals, 1)
190 c.Check(mm.Tags(app1), HasLen, 3)
191 c.Check(mm.Tags(app2), HasLen, 2)
192 c.Check(mm.Tags(app3), HasLen, 1)
193
194 // clearing with no args just empties it
195 c.Check(mm.Clear(app1), Equals, 3)
196 c.Check(mm.Tags(app1), IsNil)
197 c.Check(mm.Tags(app2), HasLen, 2)
198 c.Check(mm.Tags(app3), HasLen, 1)
199
200 // asking to clear all the tags from an already tagless app does nothing
201 c.Check(mm.Clear(app1), Equals, 0)
202 c.Check(mm.Tags(app1), IsNil)
203 c.Check(mm.Tags(app2), HasLen, 2)
204 c.Check(mm.Tags(app3), HasLen, 1)
205
206 // check we work ok with a "" tag, too.
207 c.Check(mm.Clear(app1, ""), Equals, 0)
208 c.Check(mm.Clear(app2, ""), Equals, 0)
209 c.Check(mm.Clear(app3, ""), Equals, 1)
210 c.Check(mm.Tags(app1), IsNil)
211 c.Check(mm.Tags(app2), HasLen, 2)
212 c.Check(mm.Tags(app3), HasLen, 0)
213}
214
110func (ms *MessagingSuite) TestRemoveNotification(c *C) {215func (ms *MessagingSuite) TestRemoveNotification(c *C) {
111 mmu := New(ms.log)216 mmu := New(ms.log)
112 card := launch_helper.Card{Summary: "ehlo", Persist: true, Actions: []string{"action-1"}}217 card := launch_helper.Card{Summary: "ehlo", Persist: true, Actions: []string{"action-1"}}
113 actions := []string{"{\"app\":\"com.example.test_test_0\",\"act\":\"action-1\",\"nid\":\"notif-id\"}", "action-1"}218 actions := []string{"{\"app\":\"com.example.test_test_0\",\"act\":\"action-1\",\"nid\":\"notif-id\"}", "action-1"}
114 mmu.addNotification(ms.app.DesktopId(), "notif-id", &card, actions)219 mmu.addNotification(ms.app, "notif-id", "a-tag", &card, actions)
115220
116 // check it's there221 // check it's there
117 payload, ok := mmu.notifications["notif-id"]222 payload, ok := mmu.notifications["notif-id"]
118 c.Check(ok, Equals, true)223 c.Check(ok, Equals, true)
119 c.Check(payload.Actions, DeepEquals, actions)224 c.Check(payload.Actions, DeepEquals, actions)
225 c.Check(payload.Tag, Equals, "a-tag")
120 c.Check(payload.Ch, Equals, mmu.Ch)226 c.Check(payload.Ch, Equals, mmu.Ch)
227 // remove the notification (internal only)
228 mmu.RemoveNotification("notif-id", false)
229 // check it's gone
230 _, ok = mmu.notifications["notif-id"]
231 c.Check(ok, Equals, false)
232}
233
234func (ms *MessagingSuite) TestRemoveNotificationsFromUI(c *C) {
235 mmu := New(ms.log)
236 card := launch_helper.Card{Summary: "ehlo", Persist: true, Actions: []string{"action-1"}}
237 actions := []string{"{\"app\":\"com.example.test_test_0\",\"act\":\"action-1\",\"nid\":\"notif-id\"}", "action-1"}
238 mmu.addNotification(ms.app, "notif-id", "a-tag", &card, actions)
239
240 // check it's there
241 _, ok := mmu.notifications["notif-id"]
242 c.Assert(ok, Equals, true)
243 // remove the notification (both internal and from UI)
244 mmu.RemoveNotification("notif-id", true)
245 // check it's gone
246 _, ok = mmu.notifications["notif-id"]
247 c.Check(ok, Equals, false)
248
249 // and check it's been removed from the UI too
250 c.Check(ms.log.Captured(), Matches, `(?s).* REMOVE:.*notif-id.*`)
251}
252
253func (ms *MessagingSuite) TestCleanupStaleNotification(c *C) {
254 mmu := New(ms.log)
255 card := launch_helper.Card{Summary: "ehlo", Persist: true, Actions: []string{"action-1"}}
256 actions := []string{"{\"app\":\"com.example.test_test_0\",\"act\":\"action-1\",\"nid\":\"notif-id\"}", "action-1"}
257 mmu.addNotification(ms.app, "notif-id", "", &card, actions)
258
259 // check it's there
260 _, ok := mmu.notifications["notif-id"]
261 c.Check(ok, Equals, true)
262
263 // patch cnotificationexists to return true
264 cNotificationExists = func(did string, nid string) bool {
265 return true
266 }
121 // remove the notification267 // remove the notification
122 mmu.RemoveNotification("notif-id")268 mmu.cleanUpNotifications()
269 // check it's still there
270 _, ok = mmu.notifications["notif-id"]
271 c.Check(ok, Equals, true)
272 // patch cnotificationexists to return false
273 cNotificationExists = func(did string, nid string) bool {
274 return false
275 }
276 // mark the notification
277 mmu.cleanUpNotifications()
278 // check it's gone
279 _, ok = mmu.notifications["notif-id"]
280 c.Check(ok, Equals, true)
281 // sweep the notification
282 mmu.cleanUpNotifications()
123 // check it's gone283 // check it's gone
124 _, ok = mmu.notifications["notif-id"]284 _, ok = mmu.notifications["notif-id"]
125 c.Check(ok, Equals, false)285 c.Check(ok, Equals, false)
126}286}
287
288func (ms *MessagingSuite) TestCleanupLoop(c *C) {
289 mmu := New(ms.log)
290 tickerCh := make(chan time.Time)
291 mmu.tickerCh = tickerCh
292 cleanupCh := make(chan bool)
293 cleanupFunc := func() {
294 cleanupCh <- true
295 }
296 // start the cleanup loop
297 mmu.doStartCleanupLoop(cleanupFunc)
298 // mark
299 tickerCh <- time.Now()
300 // check it was called
301 <-cleanupCh
302 // stop the loop and check that it's actually stopped.
303 mmu.StopCleanupLoop()
304 c.Check(ms.log.Captured(), Matches, "(?s).*DEBUG CleanupLoop stopped.*")
305}
306
307func (ms *MessagingSuite) TestStartCleanupLoop(c *C) {
308 mmu := New(ms.log)
309 tickerCh := make(chan time.Time)
310 mmu.tickerCh = tickerCh
311 card := launch_helper.Card{Summary: "ehlo", Persist: true, Actions: []string{"action-1"}}
312 actions := []string{"{\"app\":\"com.example.test_test_0\",\"act\":\"action-1\",\"nid\":\"notif-id\"}", "action-1"}
313 mmu.addNotification(ms.app, "notif-id", "", &card, actions)
314 // patch cnotificationexists to return true and signal when it's called
315 notifExistsCh := make(chan bool)
316 cNotificationExists = func(did string, nid string) bool {
317 notifExistsCh <- true
318 return true
319 }
320 // statr the cleanup loop
321 mmu.StartCleanupLoop()
322 // mark
323 tickerCh <- time.Now()
324 // check it's there, and marked
325 <-notifExistsCh
326 // stop the loop
327 mmu.StopCleanupLoop()
328}
329
330func (ms *MessagingSuite) TestGetCh(c *C) {
331 mmu := New(ms.log)
332 mmu.Ch = make(chan *reply.MMActionReply)
333 c.Check(mmu.GetCh(), Equals, mmu.Ch)
334}
127335
=== modified file 'messaging/reply/reply.go'
--- messaging/reply/reply.go 2014-07-16 16:10:03 +0000
+++ messaging/reply/reply.go 2014-07-29 16:56:22 +0000
@@ -18,8 +18,11 @@
18// messaging and cmessaging without going circular about it18// messaging and cmessaging without going circular about it
19package reply19package reply
2020
21import "launchpad.net/ubuntu-push/click"
22
21// MMActionReply holds the reply from a MessagingMenu action23// MMActionReply holds the reply from a MessagingMenu action
22type MMActionReply struct {24type MMActionReply struct {
23 Notification string25 Notification string
24 Action string26 Action string
27 App *click.AppId
25}28}
2629
=== modified file 'sounds/sounds.go'
--- sounds/sounds.go 2014-07-09 00:13:19 +0000
+++ sounds/sounds.go 2014-07-29 16:56:22 +0000
@@ -19,7 +19,7 @@
19import (19import (
20 "os"20 "os"
21 "os/exec"21 "os/exec"
22 "path"22 "path/filepath"
2323
24 "launchpad.net/go-xdg/v0"24 "launchpad.net/go-xdg/v0"
2525
@@ -40,8 +40,12 @@
40}40}
4141
42func (snd *Sound) Present(app *click.AppId, nid string, notification *launch_helper.Notification) bool {42func (snd *Sound) Present(app *click.AppId, nid string, notification *launch_helper.Notification) bool {
43 if notification == nil || notification.Sound == "" {43 if notification == nil {
44 snd.log.Debugf("[%s] no notification or no Sound in the notification; doing nothing: %#v", nid, notification)44 panic("please check notification is not nil before calling present")
45 }
46
47 if notification.Sound == "" {
48 snd.log.Debugf("[%s] notification has no Sound: %#v", nid, notification.Sound)
45 return false49 return false
46 }50 }
47 absPath := snd.findSoundFile(app, nid, notification.Sound)51 absPath := snd.findSoundFile(app, nid, notification.Sound)
@@ -68,7 +72,7 @@
68func (snd *Sound) findSoundFile(app *click.AppId, nid string, sound string) string {72func (snd *Sound) findSoundFile(app *click.AppId, nid string, sound string) string {
69 // XXX also support legacy appIds?73 // XXX also support legacy appIds?
70 // first, check package-specific74 // first, check package-specific
71 absPath, err := snd.dataFind(path.Join(app.Package, sound))75 absPath, err := snd.dataFind(filepath.Join(app.Package, sound))
72 if err == nil {76 if err == nil {
73 // ffffound77 // ffffound
74 return absPath78 return absPath
@@ -76,7 +80,7 @@
76 // next, check the XDG data dirs (but skip the first one -- that's "home")80 // next, check the XDG data dirs (but skip the first one -- that's "home")
77 // XXX should we only check in $XDG/sounds ? (that's for sound *themes*...)81 // XXX should we only check in $XDG/sounds ? (that's for sound *themes*...)
78 for _, dir := range snd.dataDirs()[1:] {82 for _, dir := range snd.dataDirs()[1:] {
79 absPath := path.Join(dir, sound)83 absPath := filepath.Join(dir, sound)
80 _, err := os.Stat(absPath)84 _, err := os.Stat(absPath)
81 if err == nil {85 if err == nil {
82 return absPath86 return absPath
8387
=== modified file 'sounds/sounds_test.go'
--- sounds/sounds_test.go 2014-07-11 19:42:57 +0000
+++ sounds/sounds_test.go 2014-07-29 16:56:22 +0000
@@ -70,7 +70,7 @@
70 }70 }
7171
72 // nil notification72 // nil notification
73 c.Check(s.Present(ss.app, "", nil), Equals, false)73 c.Check(func() { s.Present(ss.app, "", nil) }, Panics, `please check notification is not nil before calling present`)
74 // no Sound74 // no Sound
75 c.Check(s.Present(ss.app, "", &launch_helper.Notification{}), Equals, false)75 c.Check(s.Present(ss.app, "", &launch_helper.Notification{}), Equals, false)
76 // bad player76 // bad player

Subscribers

People subscribed via source and target branches