Merge lp:~chipaca/ubuntu-push/the-push-automatic into lp:ubuntu-push
- the-push-automatic
- Merge into trunk
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 |
Related bugs: |
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
1 | === modified file 'bus/emblemcounter/emblemcounter.go' |
2 | --- bus/emblemcounter/emblemcounter.go 2014-07-09 10:22:09 +0000 |
3 | +++ bus/emblemcounter/emblemcounter.go 2014-07-29 16:56:22 +0000 |
4 | @@ -49,16 +49,37 @@ |
5 | |
6 | // Look for an EmblemCounter section in a Notification and, if |
7 | // present, presents it to the user. |
8 | -func (ctr *EmblemCounter) Present(app *click.AppId, nid string, notification *launch_helper.Notification) { |
9 | - if notification == nil || notification.EmblemCounter == nil { |
10 | - ctr.log.Debugf("[%s] no notification or no EmblemCounter in the notification; doing nothing: %#v", nid, notification) |
11 | - return |
12 | +func (ctr *EmblemCounter) Present(app *click.AppId, nid string, notification *launch_helper.Notification) bool { |
13 | + if notification == nil { |
14 | + panic("please check notification is not nil before calling present") |
15 | } |
16 | + |
17 | ec := notification.EmblemCounter |
18 | + |
19 | + if ec == nil { |
20 | + ctr.log.Debugf("[%s] notification has no EmblemCounter: %#v", nid, ec) |
21 | + return false |
22 | + } |
23 | ctr.log.Debugf("[%s] setting emblem counter for %s to %d (visible: %t)", nid, app.Base(), ec.Count, ec.Visible) |
24 | - |
25 | - quoted := string(nih.Quote([]byte(app.Base()))) |
26 | - |
27 | - ctr.bus.SetProperty("count", "/"+quoted, dbus.Variant{ec.Count}) |
28 | - ctr.bus.SetProperty("countVisible", "/"+quoted, dbus.Variant{ec.Visible}) |
29 | + return ctr.SetCounter(app, ec.Count, ec.Visible) |
30 | +} |
31 | + |
32 | +// SetCounter sets an emblem counter on the launcher for app to count (if |
33 | +// visible is true), or clears it (if count is 0 or visible is false). |
34 | +func (ctr *EmblemCounter) SetCounter(app *click.AppId, count int32, visible bool) bool { |
35 | + base := app.Base() |
36 | + quoted := string(nih.Quote([]byte(base))) |
37 | + |
38 | + err := ctr.bus.SetProperty("count", "/"+quoted, dbus.Variant{count}) |
39 | + if err != nil { |
40 | + ctr.log.Errorf("call to set count failed: %v", err) |
41 | + return false |
42 | + } |
43 | + err = ctr.bus.SetProperty("countVisible", "/"+quoted, dbus.Variant{visible}) |
44 | + if err != nil { |
45 | + ctr.log.Errorf("call to set countVisible failed: %v", err) |
46 | + return false |
47 | + } |
48 | + |
49 | + return true |
50 | } |
51 | |
52 | === modified file 'bus/emblemcounter/emblemcounter_test.go' |
53 | --- bus/emblemcounter/emblemcounter_test.go 2014-07-11 19:42:57 +0000 |
54 | +++ bus/emblemcounter/emblemcounter_test.go 2014-07-29 16:56:22 +0000 |
55 | @@ -45,6 +45,21 @@ |
56 | ecs.app = clickhelp.MustParseAppId("com.example.test_test-app_0") |
57 | } |
58 | |
59 | +// checks that SetCounter() actually calls SetProperty on the launcher |
60 | +func (ecs *ecSuite) TestSetCounterSetsTheCounter(c *C) { |
61 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
62 | + quoted := string(nih.Quote([]byte(ecs.app.Base()))) |
63 | + |
64 | + ec := New(endp, ecs.log) |
65 | + c.Check(ec.SetCounter(ecs.app, 42, true), Equals, true) |
66 | + callArgs := testibus.GetCallArgs(endp) |
67 | + c.Assert(callArgs, HasLen, 2) |
68 | + c.Check(callArgs[0].Member, Equals, "::SetProperty") |
69 | + c.Check(callArgs[1].Member, Equals, "::SetProperty") |
70 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", "/" + quoted, dbus.Variant{Value: int32(42)}}) |
71 | + c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", "/" + quoted, dbus.Variant{Value: true}}) |
72 | +} |
73 | + |
74 | // checks that Present() actually calls SetProperty on the launcher |
75 | func (ecs *ecSuite) TestPresentPresents(c *C) { |
76 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
77 | @@ -52,7 +67,7 @@ |
78 | |
79 | ec := New(endp, ecs.log) |
80 | notif := launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{Count: 42, Visible: true}} |
81 | - ec.Present(ecs.app, "nid", ¬if) |
82 | + c.Check(ec.Present(ecs.app, "nid", ¬if), Equals, true) |
83 | callArgs := testibus.GetCallArgs(endp) |
84 | c.Assert(callArgs, HasLen, 2) |
85 | c.Check(callArgs[0].Member, Equals, "::SetProperty") |
86 | @@ -67,16 +82,12 @@ |
87 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
88 | ec := New(endp, ecs.log) |
89 | |
90 | - // nothing happens if nil Notification |
91 | - ec.Present(ecs.app, "nid", nil) |
92 | - c.Assert(testibus.GetCallArgs(endp), HasLen, 0) |
93 | - |
94 | // nothing happens if no EmblemCounter in Notification |
95 | - ec.Present(ecs.app, "nid", &launch_helper.Notification{}) |
96 | + c.Check(ec.Present(ecs.app, "nid", &launch_helper.Notification{}), Equals, false) |
97 | c.Assert(testibus.GetCallArgs(endp), HasLen, 0) |
98 | |
99 | // but an empty EmblemCounter is acted on |
100 | - ec.Present(ecs.app, "nid", &launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{}}) |
101 | + c.Check(ec.Present(ecs.app, "nid", &launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{}}), Equals, true) |
102 | callArgs := testibus.GetCallArgs(endp) |
103 | c.Assert(callArgs, HasLen, 2) |
104 | c.Check(callArgs[0].Member, Equals, "::SetProperty") |
105 | @@ -84,3 +95,12 @@ |
106 | c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", "/" + quoted, dbus.Variant{Value: int32(0)}}) |
107 | c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", "/" + quoted, dbus.Variant{Value: false}}) |
108 | } |
109 | + |
110 | +// check that Present() panics if the notification is nil |
111 | +func (ecs *ecSuite) TestPanicsIfNil(c *C) { |
112 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
113 | + ec := New(endp, ecs.log) |
114 | + |
115 | + // nothing happens if no EmblemCounter in Notification |
116 | + c.Check(func() { ec.Present(ecs.app, "nid", nil) }, Panics, `please check notification is not nil before calling present`) |
117 | +} |
118 | |
119 | === modified file 'bus/haptic/haptic.go' |
120 | --- bus/haptic/haptic.go 2014-07-15 15:02:33 +0000 |
121 | +++ bus/haptic/haptic.go 2014-07-29 16:56:22 +0000 |
122 | @@ -44,9 +44,13 @@ |
123 | } |
124 | |
125 | // Present presents the notification via a vibrate pattern |
126 | -func (haptic *Haptic) Present(_ *click.AppId, _ string, notification *launch_helper.Notification) bool { |
127 | - if notification == nil || notification.Vibrate == nil { |
128 | - haptic.log.Debugf("no notification or no Vibrate in the notification; doing nothing: %#v", notification) |
129 | +func (haptic *Haptic) Present(_ *click.AppId, nid string, notification *launch_helper.Notification) bool { |
130 | + if notification == nil { |
131 | + panic("please check notification is not nil before calling present") |
132 | + } |
133 | + |
134 | + if notification.Vibrate == nil { |
135 | + haptic.log.Debugf("[%s] notification has no Vibrate: %#v", nid, notification.Vibrate) |
136 | return false |
137 | } |
138 | pattern := notification.Vibrate.Pattern |
139 | @@ -58,13 +62,13 @@ |
140 | pattern = []uint32{notification.Vibrate.Duration} |
141 | } |
142 | if len(pattern) == 0 { |
143 | - haptic.log.Debugf("not enough information in the notification's Vibrate section to create a pattern") |
144 | + haptic.log.Debugf("[%s] not enough information in the Vibrate to create a pattern", nid) |
145 | return false |
146 | } |
147 | - haptic.log.Debugf("vibrating %d times to the tune of %v", repeat, pattern) |
148 | + haptic.log.Debugf("[%s] vibrating %d times to the tune of %v", nid, repeat, pattern) |
149 | err := haptic.bus.Call("VibratePattern", bus.Args(pattern, repeat)) |
150 | if err != nil { |
151 | - haptic.log.Errorf("VibratePattern call returned %v", err) |
152 | + haptic.log.Errorf("[%s] call to VibratePattern returned %v", nid, err) |
153 | return false |
154 | } |
155 | return true |
156 | |
157 | === modified file 'bus/haptic/haptic_test.go' |
158 | --- bus/haptic/haptic_test.go 2014-07-11 19:42:57 +0000 |
159 | +++ bus/haptic/haptic_test.go 2014-07-29 16:56:22 +0000 |
160 | @@ -106,10 +106,17 @@ |
161 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
162 | |
163 | ec := New(endp, hs.log) |
164 | - // no notification at all |
165 | - c.Check(ec.Present(hs.app, "", nil), Equals, false) |
166 | // no Vibration in the notificaton |
167 | c.Check(ec.Present(hs.app, "", &launch_helper.Notification{}), Equals, false) |
168 | // empty Vibration |
169 | c.Check(ec.Present(hs.app, "", &launch_helper.Notification{Vibrate: &launch_helper.Vibration{}}), Equals, false) |
170 | } |
171 | + |
172 | +// check that Present() panics if the notification is nil |
173 | +func (hs *hapticSuite) TestPanicsIfNil(c *C) { |
174 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
175 | + |
176 | + ec := New(endp, hs.log) |
177 | + // no notification at all |
178 | + c.Check(func() { ec.Present(hs.app, "", nil) }, Panics, `please check notification is not nil before calling present`) |
179 | +} |
180 | |
181 | === modified file 'bus/notifications/raw.go' |
182 | --- bus/notifications/raw.go 2014-07-17 22:43:17 +0000 |
183 | +++ bus/notifications/raw.go 2014-07-29 16:56:22 +0000 |
184 | @@ -131,14 +131,18 @@ |
185 | // If card.Actions has 1 action, it's an interactive notification. |
186 | // If card.Actions has 2 actions, it will show as a snap decision. |
187 | // If it has more actions, who knows (good luck). |
188 | -func (raw *RawNotifications) Present(app *click.AppId, nid string, notification *launch_helper.Notification) (uint32, error) { |
189 | - if notification == nil || notification.Card == nil || !notification.Card.Popup || notification.Card.Summary == "" { |
190 | - raw.log.Debugf("[%s] skipping notification: nil, or nil card, or not popup, or no summary: %#v", nid, notification) |
191 | - return 0, nil |
192 | +func (raw *RawNotifications) Present(app *click.AppId, nid string, notification *launch_helper.Notification) bool { |
193 | + if notification == nil { |
194 | + panic("please check notification is not nil before calling present") |
195 | } |
196 | |
197 | card := notification.Card |
198 | |
199 | + if card == nil || !card.Popup || card.Summary == "" { |
200 | + raw.log.Debugf("[%s] notification has no popup card: %#v", nid, card) |
201 | + return false |
202 | + } |
203 | + |
204 | hints := make(map[string]*dbus.Variant) |
205 | hints["x-canonical-secondary-icon"] = &dbus.Variant{app.Icon()} |
206 | |
207 | @@ -152,7 +156,8 @@ |
208 | Action: action, |
209 | }) |
210 | if err != nil { |
211 | - return 0, err |
212 | + raw.log.Errorf("[%s] while marshaling %#v to json: %v", nid, action, err) |
213 | + return false |
214 | } |
215 | actions[2*i] = string(act) |
216 | actions[2*i+1] = action |
217 | @@ -160,13 +165,22 @@ |
218 | switch len(card.Actions) { |
219 | case 0: |
220 | // nothing |
221 | + default: |
222 | + raw.log.Errorf("[%s] don't know what to do with %d actions; ignoring the rest", nid, len(card.Actions)) |
223 | + actions = actions[:2] |
224 | + fallthrough |
225 | case 1: |
226 | hints["x-canonical-switch-to-application"] = &dbus.Variant{"true"} |
227 | - case 2: |
228 | - hints["x-canonical-snap-decisions"] = &dbus.Variant{"true"} |
229 | - hints["x-canonical-private-button-tint"] = &dbus.Variant{"true"} |
230 | - default: |
231 | - raw.log.Debugf("[%s] don't know what to do with %d actions; no hints set", nid, len(card.Actions)) |
232 | - } |
233 | - return raw.Notify(appId, 0, card.Icon, card.Summary, card.Body, actions, hints, 30*1000) |
234 | + } |
235 | + |
236 | + raw.log.Debugf("[%s] creating popup (or snap decision) for %s (summary: %s)", nid, app.Base(), card.Summary) |
237 | + |
238 | + _, err := raw.Notify(appId, 0, card.Icon, card.Summary, card.Body, actions, hints, 30*1000) |
239 | + |
240 | + if err != nil { |
241 | + raw.log.Errorf("[%s] call to Notify failed: %v", nid, err) |
242 | + return false |
243 | + } |
244 | + |
245 | + return true |
246 | } |
247 | |
248 | === modified file 'bus/notifications/raw_test.go' |
249 | --- bus/notifications/raw_test.go 2014-07-17 22:57:16 +0000 |
250 | +++ bus/notifications/raw_test.go 2014-07-29 16:56:22 +0000 |
251 | @@ -160,16 +160,15 @@ |
252 | func (s *RawSuite) TestPresentNotifies(c *C) { |
253 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
254 | raw := Raw(endp, s.log) |
255 | - nid, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true}}) |
256 | - c.Check(err, IsNil) |
257 | - c.Check(nid, Equals, uint32(1)) |
258 | + worked := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true}}) |
259 | + c.Check(worked, Equals, true) |
260 | } |
261 | |
262 | func (s *RawSuite) TestPresentOneAction(c *C) { |
263 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
264 | raw := Raw(endp, s.log) |
265 | - _, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true, Actions: []string{"Yes"}}}) |
266 | - c.Check(err, IsNil) |
267 | + worked := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true, Actions: []string{"Yes"}}}) |
268 | + c.Check(worked, Equals, true) |
269 | callArgs := testibus.GetCallArgs(endp) |
270 | c.Assert(callArgs, HasLen, 1) |
271 | c.Assert(callArgs[0].Member, Equals, "Notify") |
272 | @@ -192,78 +191,48 @@ |
273 | func (s *RawSuite) TestPresentTwoActions(c *C) { |
274 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
275 | raw := Raw(endp, s.log) |
276 | - _, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true, Actions: []string{"Yes", "No"}}}) |
277 | - c.Check(err, IsNil) |
278 | + worked := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true, Actions: []string{"Yes", "No"}}}) |
279 | + c.Check(worked, Equals, true) |
280 | callArgs := testibus.GetCallArgs(endp) |
281 | c.Assert(callArgs, HasLen, 1) |
282 | c.Assert(callArgs[0].Member, Equals, "Notify") |
283 | c.Assert(len(callArgs[0].Args), Equals, 8) |
284 | actions, ok := callArgs[0].Args[5].([]string) |
285 | c.Assert(ok, Equals, true) |
286 | - c.Assert(actions, HasLen, 4) |
287 | + c.Assert(actions, HasLen, 2) |
288 | c.Check(actions[0], Equals, `{"app":"com.example.test_test-app_0","act":"Yes","nid":"notifId"}`) |
289 | c.Check(actions[1], Equals, "Yes") |
290 | - c.Check(actions[2], Equals, `{"app":"com.example.test_test-app_0","act":"No","aid":1,"nid":"notifId"}`) |
291 | - c.Check(actions[3], Equals, "No") |
292 | - hints, ok := callArgs[0].Args[6].(map[string]*dbus.Variant) |
293 | - c.Assert(ok, Equals, true) |
294 | - // with two actions, there should be 3 hints set: |
295 | - c.Assert(hints, HasLen, 3) |
296 | - c.Check(hints["x-canonical-switch-to-application"], IsNil) |
297 | - c.Check(hints["x-canonical-secondary-icon"], NotNil) |
298 | - c.Check(hints["x-canonical-snap-decisions"], NotNil) |
299 | - c.Check(hints["x-canonical-private-button-tint"], NotNil) |
300 | - c.Check(hints["x-canonical-non-shaped-icon"], IsNil) // checking just in case |
301 | -} |
302 | - |
303 | -func (s *RawSuite) TestPresentThreeActions(c *C) { |
304 | - endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
305 | - raw := Raw(endp, s.log) |
306 | - _, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true, Actions: []string{"Yes", "No", "What"}}}) |
307 | - c.Check(err, IsNil) |
308 | - callArgs := testibus.GetCallArgs(endp) |
309 | - c.Assert(callArgs, HasLen, 1) |
310 | - c.Assert(callArgs[0].Member, Equals, "Notify") |
311 | - c.Assert(len(callArgs[0].Args), Equals, 8) |
312 | - actions, ok := callArgs[0].Args[5].([]string) |
313 | - c.Assert(ok, Equals, true) |
314 | - c.Assert(actions, HasLen, 6) |
315 | - hints, ok := callArgs[0].Args[6].(map[string]*dbus.Variant) |
316 | - c.Assert(ok, Equals, true) |
317 | - // with two actions, there should be 3 hints set: |
318 | - c.Check(hints, HasLen, 1) |
319 | - c.Check(hints["x-canonical-secondary-icon"], NotNil) |
320 | - c.Check(s.log.Captured(), Matches, `(?ms).* no hints set$`) |
321 | -} |
322 | - |
323 | -func (s *RawSuite) TestPresentNoNotificationDoesNotNotify(c *C) { |
324 | - endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
325 | - raw := Raw(endp, s.log) |
326 | - nid, err := raw.Present(s.app, "notifId", nil) |
327 | - c.Check(err, IsNil) |
328 | - c.Check(nid, Equals, uint32(0)) |
329 | + // note that the rest are ignored. |
330 | + hints, ok := callArgs[0].Args[6].(map[string]*dbus.Variant) |
331 | + c.Assert(ok, Equals, true) |
332 | + c.Assert(hints, HasLen, 2) |
333 | + c.Check(hints["x-canonical-switch-to-application"], NotNil) |
334 | + c.Check(hints["x-canonical-secondary-icon"], NotNil) |
335 | +} |
336 | + |
337 | +func (s *RawSuite) TestPresentNoNotificationPanics(c *C) { |
338 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
339 | + raw := Raw(endp, s.log) |
340 | + c.Check(func() { raw.Present(s.app, "notifId", nil) }, Panics, `please check notification is not nil before calling present`) |
341 | } |
342 | |
343 | func (s *RawSuite) TestPresentNoCardDoesNotNotify(c *C) { |
344 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
345 | raw := Raw(endp, s.log) |
346 | - nid, err := raw.Present(s.app, "notifId", &launch_helper.Notification{}) |
347 | - c.Check(err, IsNil) |
348 | - c.Check(nid, Equals, uint32(0)) |
349 | + worked := raw.Present(s.app, "notifId", &launch_helper.Notification{}) |
350 | + c.Check(worked, Equals, false) |
351 | } |
352 | |
353 | func (s *RawSuite) TestPresentNoSummaryDoesNotNotify(c *C) { |
354 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
355 | raw := Raw(endp, s.log) |
356 | - nid, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{}}) |
357 | - c.Check(err, IsNil) |
358 | - c.Check(nid, Equals, uint32(0)) |
359 | + worked := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{}}) |
360 | + c.Check(worked, Equals, false) |
361 | } |
362 | |
363 | func (s *RawSuite) TestPresentNoPopupNoNotify(c *C) { |
364 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
365 | raw := Raw(endp, s.log) |
366 | - nid, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary"}}) |
367 | - c.Check(err, IsNil) |
368 | - c.Check(nid, Equals, uint32(0)) |
369 | + worked := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary"}}) |
370 | + c.Check(worked, Equals, false) |
371 | } |
372 | |
373 | === modified file 'bus/urldispatcher/urldispatcher.go' |
374 | --- bus/urldispatcher/urldispatcher.go 2014-04-02 08:23:15 +0000 |
375 | +++ bus/urldispatcher/urldispatcher.go 2014-07-29 16:56:22 +0000 |
376 | @@ -19,6 +19,7 @@ |
377 | |
378 | import ( |
379 | "launchpad.net/ubuntu-push/bus" |
380 | + "launchpad.net/ubuntu-push/click" |
381 | "launchpad.net/ubuntu-push/logger" |
382 | ) |
383 | |
384 | @@ -32,7 +33,8 @@ |
385 | // A URLDispatcher is a simple beast, with a single method that does what it |
386 | // says on the box. |
387 | type URLDispatcher interface { |
388 | - DispatchURL(string) error |
389 | + DispatchURL(string, *click.AppId) error |
390 | + TestURL(*click.AppId, []string) bool |
391 | } |
392 | |
393 | type urlDispatcher struct { |
394 | @@ -47,11 +49,28 @@ |
395 | |
396 | var _ URLDispatcher = &urlDispatcher{} // ensures it conforms |
397 | |
398 | -func (ud *urlDispatcher) DispatchURL(url string) error { |
399 | +func (ud *urlDispatcher) DispatchURL(url string, app *click.AppId) error { |
400 | ud.log.Debugf("Dispatching %s", url) |
401 | - err := ud.endp.Call("DispatchURL", bus.Args(url)) |
402 | + err := ud.endp.Call("DispatchURL", bus.Args(url, app.DispatchPackage())) |
403 | if err != nil { |
404 | ud.log.Errorf("Dispatch to %s failed with %s", url, err) |
405 | } |
406 | return err |
407 | } |
408 | + |
409 | +func (ud *urlDispatcher) TestURL(app *click.AppId, urls []string) bool { |
410 | + ud.log.Debugf("TestURL: %s", urls) |
411 | + var appIds []string |
412 | + err := ud.endp.Call("TestURL", bus.Args(urls), &appIds) |
413 | + if err != nil { |
414 | + ud.log.Errorf("TestURL for %s failed with %s", urls, err) |
415 | + return false |
416 | + } |
417 | + for _, appId := range appIds { |
418 | + if appId != app.Versioned() { |
419 | + ud.log.Debugf("Notification skipped because of different appid for actions: %v - %s != %s", urls, appId, app.Versioned()) |
420 | + return false |
421 | + } |
422 | + } |
423 | + return true |
424 | +} |
425 | |
426 | === modified file 'bus/urldispatcher/urldispatcher_test.go' |
427 | --- bus/urldispatcher/urldispatcher_test.go 2014-02-05 18:17:26 +0000 |
428 | +++ bus/urldispatcher/urldispatcher_test.go 2014-07-29 16:56:22 +0000 |
429 | @@ -19,7 +19,7 @@ |
430 | import ( |
431 | . "launchpad.net/gocheck" |
432 | testibus "launchpad.net/ubuntu-push/bus/testing" |
433 | - "launchpad.net/ubuntu-push/logger" |
434 | + clickhelp "launchpad.net/ubuntu-push/click/testing" |
435 | helpers "launchpad.net/ubuntu-push/testing" |
436 | "launchpad.net/ubuntu-push/testing/condition" |
437 | "testing" |
438 | @@ -29,7 +29,7 @@ |
439 | func TestUrldispatcher(t *testing.T) { TestingT(t) } |
440 | |
441 | type UDSuite struct { |
442 | - log logger.Logger |
443 | + log *helpers.TestLogger |
444 | } |
445 | |
446 | var _ = Suite(&UDSuite{}) |
447 | @@ -38,16 +38,78 @@ |
448 | s.log = helpers.NewTestLogger(c, "debug") |
449 | } |
450 | |
451 | -func (s *UDSuite) TestWorks(c *C) { |
452 | +func (s *UDSuite) TestDispatchURLWorks(c *C) { |
453 | endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{}) |
454 | ud := New(endp, s.log) |
455 | - err := ud.DispatchURL("this") |
456 | + appId := clickhelp.MustParseAppId("com.example.test_app_0.99") |
457 | + err := ud.DispatchURL("this", appId) |
458 | c.Check(err, IsNil) |
459 | } |
460 | |
461 | -func (s *UDSuite) TestFailsIfCallFails(c *C) { |
462 | +func (s *UDSuite) TestDispatchURLFailsIfCallFails(c *C) { |
463 | endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) |
464 | ud := New(endp, s.log) |
465 | - err := ud.DispatchURL("this") |
466 | + appId := clickhelp.MustParseAppId("com.example.test_app_0.99") |
467 | + err := ud.DispatchURL("this", appId) |
468 | c.Check(err, NotNil) |
469 | } |
470 | + |
471 | +func (s *UDSuite) TestTestURLWorks(c *C) { |
472 | + endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{[]string{"com.example.test_app_0.99"}}) |
473 | + ud := New(endp, s.log) |
474 | + appId := clickhelp.MustParseAppId("com.example.test_app_0.99") |
475 | + c.Check(ud.TestURL(appId, []string{"this"}), Equals, true) |
476 | + c.Check(s.log.Captured(), Matches, `(?sm).*TestURL: \[this\].*`) |
477 | +} |
478 | + |
479 | +func (s *UDSuite) TestTestURLFailsIfCallFails(c *C) { |
480 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) |
481 | + ud := New(endp, s.log) |
482 | + appId := clickhelp.MustParseAppId("com.example.test_app_0.99") |
483 | + c.Check(ud.TestURL(appId, []string{"this"}), Equals, false) |
484 | +} |
485 | + |
486 | +func (s *UDSuite) TestTestURLMultipleURLs(c *C) { |
487 | + endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []string{"com.example.test_app_0.99", "com.example.test_app_0.99"}) |
488 | + ud := New(endp, s.log) |
489 | + appId := clickhelp.MustParseAppId("com.example.test_app_0.99") |
490 | + urls := []string{"potato://test-app", "potato_a://foo"} |
491 | + c.Check(ud.TestURL(appId, urls), Equals, true) |
492 | + c.Check(s.log.Captured(), Matches, `(?sm).*TestURL: \[potato://test-app potato_a://foo\].*`) |
493 | +} |
494 | + |
495 | +func (s *UDSuite) TestTestURLWrongApp(c *C) { |
496 | + endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []string{"com.example.test_test-app_0.1"}) |
497 | + ud := New(endp, s.log) |
498 | + appId := clickhelp.MustParseAppId("com.example.test_app_0.99") |
499 | + urls := []string{"potato://test-app"} |
500 | + c.Check(ud.TestURL(appId, urls), Equals, false) |
501 | + 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`) |
502 | +} |
503 | + |
504 | +func (s *UDSuite) TestTestURLOneWrongApp(c *C) { |
505 | + endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []string{"com.example.test_test-app_0", "com.example.test_test-app1"}) |
506 | + ud := New(endp, s.log) |
507 | + appId := clickhelp.MustParseAppId("com.example.test_test-app_0") |
508 | + urls := []string{"potato://test-app", "potato_a://foo"} |
509 | + c.Check(ud.TestURL(appId, urls), Equals, false) |
510 | + 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.*`) |
511 | +} |
512 | + |
513 | +func (s *UDSuite) TestTestURLInvalidURL(c *C) { |
514 | + endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false), []string{"com.example.test_test-app_0.1"}) |
515 | + ud := New(endp, s.log) |
516 | + appId := clickhelp.MustParseAppId("com.example.test_app_0.2") |
517 | + urls := []string{"notsupported://test-app"} |
518 | + c.Check(ud.TestURL(appId, urls), Equals, false) |
519 | + c.Check(s.log.Captured(), Matches, `(?sm).*TestURL for \[notsupported://test-app\] failed with no way.*`) |
520 | +} |
521 | + |
522 | +func (s *UDSuite) TestTestURLLegacyApp(c *C) { |
523 | + endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []string{"ubuntu-system-settings"}) |
524 | + ud := New(endp, s.log) |
525 | + appId := clickhelp.MustParseAppId("_ubuntu-system-settings") |
526 | + urls := []string{"settings://test-app"} |
527 | + c.Check(ud.TestURL(appId, urls), Equals, true) |
528 | + c.Check(s.log.Captured(), Matches, `(?sm).*TestURL: \[settings://test-app\].*`) |
529 | +} |
530 | |
531 | === modified file 'click/click.go' |
532 | --- click/click.go 2014-07-18 20:45:21 +0000 |
533 | +++ click/click.go 2014-07-29 16:56:22 +0000 |
534 | @@ -98,6 +98,13 @@ |
535 | return app.Package == pkgname |
536 | } |
537 | |
538 | +func (app *AppId) DispatchPackage() string { |
539 | + if app.Click { |
540 | + return app.Package |
541 | + } |
542 | + return app.Application |
543 | +} |
544 | + |
545 | func (app *AppId) Original() string { |
546 | return app.original |
547 | } |
548 | |
549 | === modified file 'click/click_test.go' |
550 | --- click/click_test.go 2014-07-18 20:45:21 +0000 |
551 | +++ click/click_test.go 2014-07-29 16:56:22 +0000 |
552 | @@ -42,6 +42,7 @@ |
553 | c.Check(app.Click, Equals, true) |
554 | c.Check(app.Original(), Equals, "com.ubuntu.clock_clock") |
555 | c.Check(fmt.Sprintf("%s", app), Equals, "com.ubuntu.clock_clock") |
556 | + c.Check(app.DispatchPackage(), Equals, "com.ubuntu.clock") |
557 | |
558 | app, err = ParseAppId("com.ubuntu.clock_clock_10") |
559 | c.Assert(err, IsNil) |
560 | @@ -55,6 +56,7 @@ |
561 | c.Check(app.Versioned(), Equals, "com.ubuntu.clock_clock_10") |
562 | c.Check(app.Base(), Equals, "com.ubuntu.clock_clock") |
563 | c.Check(app.DesktopId(), Equals, "com.ubuntu.clock_clock_10.desktop") |
564 | + c.Check(app.DispatchPackage(), Equals, "com.ubuntu.clock") |
565 | |
566 | for _, s := range []string{"com.ubuntu.clock_clock_10_4", "com.ubuntu.clock", ""} { |
567 | app, err = ParseAppId(s) |
568 | @@ -81,6 +83,7 @@ |
569 | c.Check(app.Versioned(), Equals, "python3.4") |
570 | c.Check(app.Base(), Equals, "python3.4") |
571 | c.Check(app.DesktopId(), Equals, "python3.4.desktop") |
572 | + c.Check(app.DispatchPackage(), Equals, "python3.4") |
573 | |
574 | for _, s := range []string{"_.foo", "_foo/", "_/foo"} { |
575 | app, err = ParseAppId(s) |
576 | |
577 | === modified file 'client/client.go' |
578 | --- client/client.go 2014-07-21 01:18:16 +0000 |
579 | +++ client/client.go 2014-07-29 16:56:22 +0000 |
580 | @@ -32,8 +32,6 @@ |
581 | "os/exec" |
582 | "strings" |
583 | |
584 | - "code.google.com/p/go-uuid/uuid" |
585 | - |
586 | "launchpad.net/ubuntu-push/bus" |
587 | "launchpad.net/ubuntu-push/bus/connectivity" |
588 | "launchpad.net/ubuntu-push/bus/networkmanager" |
589 | @@ -85,7 +83,7 @@ |
590 | // Post converts a push message into a presentable notification |
591 | // and a postal message, presents the former and stores the |
592 | // latter in the application's mailbox. |
593 | - Post(app *click.AppId, nid string, payload json.RawMessage) error |
594 | + Post(app *click.AppId, nid string, payload json.RawMessage) |
595 | // IsRunning() returns whether the service is running |
596 | IsRunning() bool |
597 | // Stop() stops the service |
598 | @@ -398,29 +396,23 @@ |
599 | } |
600 | // marshal the last decoded msg to json |
601 | payload, err := json.Marshal(msg.Decoded[len(msg.Decoded)-1]) |
602 | - if err == nil { |
603 | - appId, _ := click.ParseAppId("_ubuntu-system-settings") |
604 | - err = client.postalService.Post(appId, uuid.New(), payload) |
605 | - } |
606 | if err != nil { |
607 | client.log.Errorf("while posting broadcast notification %d: %v", msg.TopLevel, err) |
608 | - } else { |
609 | - client.log.Debugf("posted broadcast notification %d.", msg.TopLevel) |
610 | + return err |
611 | } |
612 | - return err |
613 | + appId, _ := click.ParseAppId("_ubuntu-system-settings") |
614 | + client.postalService.Post(appId, "", payload) |
615 | + client.log.Debugf("posted broadcast notification %d.", msg.TopLevel) |
616 | + return nil |
617 | } |
618 | |
619 | // handleUnicastNotification deals with receiving a unicast notification |
620 | func (client *PushClient) handleUnicastNotification(anotif session.AddressedNotification) error { |
621 | app := anotif.To |
622 | msg := anotif.Notification |
623 | - err := client.postalService.Post(app, msg.MsgId, msg.Payload) |
624 | - if err != nil { |
625 | - client.log.Errorf("while posting unicast notification %s for %s: %v", msg.MsgId, msg.AppId, err) |
626 | - } else { |
627 | - client.log.Debugf("posted unicast notification %s for %s.", msg.MsgId, msg.AppId) |
628 | - } |
629 | - return err |
630 | + client.postalService.Post(app, msg.MsgId, msg.Payload) |
631 | + client.log.Debugf("posted unicast notification %s for %s.", msg.MsgId, msg.AppId) |
632 | + return nil |
633 | } |
634 | |
635 | // doLoop connects events with their handlers |
636 | |
637 | === modified file 'client/client_test.go' |
638 | --- client/client_test.go 2014-07-21 01:18:16 +0000 |
639 | +++ client/client_test.go 2014-07-29 16:56:22 +0000 |
640 | @@ -107,13 +107,12 @@ |
641 | postArgs []postArgs |
642 | } |
643 | |
644 | -func (d *dumbPostal) Post(app *click.AppId, nid string, payload json.RawMessage) error { |
645 | +func (d *dumbPostal) Post(app *click.AppId, nid string, payload json.RawMessage) { |
646 | d.postCount++ |
647 | if app.Application == "ubuntu-system-settings" { |
648 | d.bcastCount++ |
649 | } |
650 | d.postArgs = append(d.postArgs, postArgs{app, nid, payload}) |
651 | - return d.err |
652 | } |
653 | |
654 | var _ PostalService = (*dumbPostal)(nil) |
655 | @@ -786,6 +785,9 @@ |
656 | positiveBroadcastNotification = &session.BroadcastNotification{ |
657 | Decoded: []map[string]interface{}{ |
658 | map[string]interface{}{ |
659 | + "daily/mako": []interface{}{float64(102), "tubular"}, |
660 | + }, |
661 | + map[string]interface{}{ |
662 | "daily/mako": []interface{}{float64(103), "tubular"}, |
663 | }, |
664 | }, |
665 | @@ -811,7 +813,8 @@ |
666 | c.Assert(d.postArgs, HasLen, 1) |
667 | expectedApp, _ := click.ParseAppId("_ubuntu-system-settings") |
668 | c.Check(d.postArgs[0].app, DeepEquals, expectedApp) |
669 | - expectedData, _ := json.Marshal(positiveBroadcastNotification.Decoded[0]) |
670 | + c.Check(d.postArgs[0].nid, Equals, "") |
671 | + expectedData, _ := json.Marshal(positiveBroadcastNotification.Decoded[1]) |
672 | c.Check([]byte(d.postArgs[0].payload), DeepEquals, expectedData) |
673 | } |
674 | |
675 | @@ -826,18 +829,6 @@ |
676 | c.Check(d.bcastCount, Equals, 0) |
677 | } |
678 | |
679 | -func (cs *clientSuite) TestHandleBroadcastNotificationFail(c *C) { |
680 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
681 | - cli.systemImageInfo = siInfoRes |
682 | - cli.log = cs.log |
683 | - d := new(dumbPostal) |
684 | - err := errors.New("potato") |
685 | - d.err = err |
686 | - cli.postalService = d |
687 | - c.Check(cli.handleBroadcastNotification(positiveBroadcastNotification), Equals, err) |
688 | - c.Check(d.bcastCount, Equals, 1) |
689 | -} |
690 | - |
691 | /***************************************************************** |
692 | handleUnicastNotification tests |
693 | ******************************************************************/ |
694 | @@ -860,19 +851,6 @@ |
695 | c.Check(d.postArgs[0].payload, DeepEquals, notif.Payload) |
696 | } |
697 | |
698 | -func (cs *clientSuite) TestHandleUcastNotificationError(c *C) { |
699 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
700 | - cli.log = cs.log |
701 | - d := new(dumbPostal) |
702 | - cli.postalService = d |
703 | - fail := errors.New("fail") |
704 | - d.err = fail |
705 | - |
706 | - c.Check(cli.handleUnicastNotification(session.AddressedNotification{appHello, notif}), Equals, fail) |
707 | - // check we sent the notification |
708 | - c.Check(d.postCount, Equals, 1) |
709 | -} |
710 | - |
711 | /***************************************************************** |
712 | handleUnregister tests |
713 | ******************************************************************/ |
714 | |
715 | === modified file 'client/service/postal.go' |
716 | --- client/service/postal.go 2014-07-21 01:18:16 +0000 |
717 | +++ client/service/postal.go 2014-07-29 16:56:22 +0000 |
718 | @@ -39,7 +39,21 @@ |
719 | "launchpad.net/ubuntu-push/util" |
720 | ) |
721 | |
722 | -type messageHandler func(*click.AppId, string, *launch_helper.HelperOutput) error |
723 | +type messageHandler func(*click.AppId, string, *launch_helper.HelperOutput) bool |
724 | + |
725 | +// a Presenter is something that knows how to present a Notification |
726 | +type Presenter interface { |
727 | + Present(*click.AppId, string, *launch_helper.Notification) bool |
728 | +} |
729 | + |
730 | +type notificationCentre interface { |
731 | + Presenter |
732 | + GetCh() chan *reply.MMActionReply |
733 | + RemoveNotification(string, bool) |
734 | + StartCleanupLoop() |
735 | + Tags(*click.AppId) []string |
736 | + Clear(*click.AppId, ...string) int |
737 | +} |
738 | |
739 | // PostalService is the dbus api |
740 | type PostalService struct { |
741 | @@ -48,7 +62,7 @@ |
742 | msgHandler messageHandler |
743 | launchers map[string]launch_helper.HelperLauncher |
744 | HelperPool launch_helper.HelperPool |
745 | - messagingMenu *messaging.MessagingMenu |
746 | + messagingMenu notificationCentre |
747 | // the endpoints are only exposed for testing from client |
748 | // XXX: uncouple some more so this isn't necessary |
749 | EmblemCounterEndp bus.Endpoint |
750 | @@ -57,6 +71,7 @@ |
751 | URLDispatcherEndp bus.Endpoint |
752 | WindowStackEndp bus.Endpoint |
753 | // presenters: |
754 | + Presenters []Presenter |
755 | emblemCounter *emblemcounter.EmblemCounter |
756 | haptic *haptic.Haptic |
757 | notifications *notifications.RawNotifications |
758 | @@ -112,8 +127,11 @@ |
759 | // Start() dials the bus, grab the name, and listens for method calls. |
760 | func (svc *PostalService) Start() error { |
761 | err := svc.DBusService.Start(bus.DispatchMap{ |
762 | - "PopAll": svc.popAll, |
763 | - "Post": svc.post, |
764 | + "PopAll": svc.popAll, |
765 | + "Post": svc.post, |
766 | + "ListPersistent": svc.listPersistent, |
767 | + "ClearPersistent": svc.clearPersistent, |
768 | + "SetCounter": svc.setCounter, |
769 | }, PostalServiceBusAddress) |
770 | if err != nil { |
771 | return err |
772 | @@ -128,6 +146,13 @@ |
773 | svc.haptic = haptic.New(svc.HapticEndp, svc.Log) |
774 | svc.sound = sounds.New(svc.Log) |
775 | svc.messagingMenu = messaging.New(svc.Log) |
776 | + svc.Presenters = []Presenter{ |
777 | + svc.notifications, |
778 | + svc.emblemCounter, |
779 | + svc.haptic, |
780 | + svc.sound, |
781 | + svc.messagingMenu, |
782 | + } |
783 | if useTrivialHelper { |
784 | svc.HelperPool = launch_helper.NewTrivialHelperPool(svc.Log) |
785 | } else { |
786 | @@ -136,7 +161,8 @@ |
787 | svc.windowStack = windowstack.New(svc.WindowStackEndp, svc.Log) |
788 | |
789 | go svc.consumeHelperResults(svc.HelperPool.Start()) |
790 | - go svc.handleActions(actionsCh, svc.messagingMenu.Ch) |
791 | + go svc.handleActions(actionsCh, svc.messagingMenu.GetCh()) |
792 | + svc.messagingMenu.StartCleanupLoop() |
793 | return nil |
794 | } |
795 | |
796 | @@ -155,8 +181,10 @@ |
797 | svc.Log.Debugf("handleActions got nil action; ignoring") |
798 | } else { |
799 | url := action.Action |
800 | + // remove the notification from the messaging menu |
801 | + svc.messagingMenu.RemoveNotification(action.Nid, true) |
802 | // this ignores the error (it's been logged already) |
803 | - svc.urlDispatcher.DispatchURL(url) |
804 | + svc.urlDispatcher.DispatchURL(url, action.App) |
805 | } |
806 | case mmuAction, ok := <-mmuActionsCh: |
807 | if !ok { |
808 | @@ -168,9 +196,9 @@ |
809 | svc.Log.Debugf("handleActions (MMU) got: %v", mmuAction) |
810 | url := mmuAction.Action |
811 | // remove the notification from the messagingmenu map |
812 | - svc.messagingMenu.RemoveNotification(mmuAction.Notification) |
813 | + svc.messagingMenu.RemoveNotification(mmuAction.Notification, false) |
814 | // this ignores the error (it's been logged already) |
815 | - svc.urlDispatcher.DispatchURL(url) |
816 | + svc.urlDispatcher.DispatchURL(url, mmuAction.App) |
817 | } |
818 | |
819 | } |
820 | @@ -209,6 +237,54 @@ |
821 | return notifications.Raw(svc.NotificationsEndp, svc.Log).WatchActions() |
822 | } |
823 | |
824 | +func (svc *PostalService) listPersistent(path string, args, _ []interface{}) ([]interface{}, error) { |
825 | + app, err := svc.grabDBusPackageAndAppId(path, args, 0) |
826 | + if err != nil { |
827 | + return nil, err |
828 | + } |
829 | + |
830 | + tagmap := svc.messagingMenu.Tags(app) |
831 | + return []interface{}{tagmap}, nil |
832 | +} |
833 | + |
834 | +func (svc *PostalService) clearPersistent(path string, args, _ []interface{}) ([]interface{}, error) { |
835 | + if len(args) == 0 { |
836 | + return nil, ErrBadArgCount |
837 | + } |
838 | + app, err := svc.grabDBusPackageAndAppId(path, args[:1], 0) |
839 | + if err != nil { |
840 | + return nil, err |
841 | + } |
842 | + tags := make([]string, len(args)-1) |
843 | + for i, itag := range args[1:] { |
844 | + tag, ok := itag.(string) |
845 | + if !ok { |
846 | + return nil, ErrBadArgType |
847 | + } |
848 | + tags[i] = tag |
849 | + } |
850 | + return []interface{}{uint32(svc.messagingMenu.Clear(app, tags...))}, nil |
851 | +} |
852 | + |
853 | +func (svc *PostalService) setCounter(path string, args, _ []interface{}) ([]interface{}, error) { |
854 | + app, err := svc.grabDBusPackageAndAppId(path, args, 2) |
855 | + if err != nil { |
856 | + return nil, err |
857 | + } |
858 | + |
859 | + count, ok := args[1].(int32) |
860 | + if !ok { |
861 | + return nil, ErrBadArgType |
862 | + } |
863 | + visible, ok := args[2].(bool) |
864 | + if !ok { |
865 | + return nil, ErrBadArgType |
866 | + } |
867 | + |
868 | + svc.emblemCounter.SetCounter(app, count, visible) |
869 | + return nil, nil |
870 | +} |
871 | + |
872 | func (svc *PostalService) popAll(path string, args, _ []interface{}) ([]interface{}, error) { |
873 | app, err := svc.grabDBusPackageAndAppId(path, args, 0) |
874 | if err != nil { |
875 | @@ -250,14 +326,16 @@ |
876 | return nil, ErrBadJSON |
877 | } |
878 | |
879 | - nid := newNid() |
880 | - |
881 | - return nil, svc.Post(app, nid, rawJSON) |
882 | + svc.Post(app, "", rawJSON) |
883 | + return nil, nil |
884 | } |
885 | |
886 | // Post() signals to an application over dbus that a notification |
887 | -// has arrived. |
888 | -func (svc *PostalService) Post(app *click.AppId, nid string, payload json.RawMessage) error { |
889 | +// has arrived. If nid is "" generate one. |
890 | +func (svc *PostalService) Post(app *click.AppId, nid string, payload json.RawMessage) { |
891 | + if nid == "" { |
892 | + nid = newNid() |
893 | + } |
894 | arg := launch_helper.HelperInput{ |
895 | App: app, |
896 | NotificationId: nid, |
897 | @@ -270,7 +348,6 @@ |
898 | kind = "legacy" |
899 | } |
900 | svc.HelperPool.Run(kind, &arg) |
901 | - return nil |
902 | } |
903 | |
904 | func (svc *PostalService) consumeHelperResults(ch chan *launch_helper.HelperResult) { |
905 | @@ -299,27 +376,41 @@ |
906 | box.Append(output.Message, nid) |
907 | |
908 | if svc.msgHandler != nil { |
909 | - err := svc.msgHandler(app, nid, &output) |
910 | - if err != nil { |
911 | - svc.DBusService.Log.Errorf("msgHandler returned %v", err) |
912 | - return |
913 | + b := svc.msgHandler(app, nid, &output) |
914 | + if !b { |
915 | + svc.Log.Debugf("msgHandler did not present the notification") |
916 | } |
917 | - svc.DBusService.Log.Debugf("call to msgHandler successful") |
918 | } |
919 | |
920 | svc.Bus.Signal("Post", "/"+string(nih.Quote([]byte(app.Package))), []interface{}{appId}) |
921 | } |
922 | |
923 | -func (svc *PostalService) messageHandler(app *click.AppId, nid string, output *launch_helper.HelperOutput) error { |
924 | +func (svc *PostalService) validateActions(app *click.AppId, notif *launch_helper.Notification) bool { |
925 | + if notif.Card == nil || len(notif.Card.Actions) == 0 { |
926 | + return true |
927 | + } |
928 | + return svc.urlDispatcher.TestURL(app, notif.Card.Actions) |
929 | +} |
930 | + |
931 | +func (svc *PostalService) messageHandler(app *click.AppId, nid string, output *launch_helper.HelperOutput) bool { |
932 | + if output == nil || output.Notification == nil { |
933 | + svc.Log.Debugf("skipping notification: nil.") |
934 | + return false |
935 | + } |
936 | + // validate actions |
937 | + if !svc.validateActions(app, output.Notification) { |
938 | + // no need to log, (it's been logged already) |
939 | + return false |
940 | + } |
941 | if !svc.windowStack.IsAppFocused(app) { |
942 | - svc.messagingMenu.Present(app, nid, output.Notification) |
943 | - _, err := svc.notifications.Present(app, nid, output.Notification) |
944 | - svc.emblemCounter.Present(app, nid, output.Notification) |
945 | - svc.haptic.Present(app, nid, output.Notification) |
946 | - svc.sound.Present(app, nid, output.Notification) |
947 | - return err |
948 | + b := false |
949 | + for _, p := range svc.Presenters { |
950 | + // we don't want this to shortcut :) |
951 | + b = p.Present(app, nid, output.Notification) || b |
952 | + } |
953 | + return b |
954 | } else { |
955 | - svc.Log.Debugf("Notification skipped because app is focused.") |
956 | - return nil |
957 | + svc.Log.Debugf("notification skipped because app is focused.") |
958 | + return false |
959 | } |
960 | } |
961 | |
962 | === modified file 'client/service/postal_test.go' |
963 | --- client/service/postal_test.go 2014-07-21 10:38:55 +0000 |
964 | +++ client/service/postal_test.go 2014-07-29 16:56:22 +0000 |
965 | @@ -18,15 +18,14 @@ |
966 | |
967 | import ( |
968 | "encoding/json" |
969 | - "errors" |
970 | + "fmt" |
971 | "io/ioutil" |
972 | "os" |
973 | "path/filepath" |
974 | "sort" |
975 | "time" |
976 | |
977 | - "code.google.com/p/go-uuid/uuid" |
978 | - |
979 | + "launchpad.net/go-dbus/v1" |
980 | . "launchpad.net/gocheck" |
981 | |
982 | "launchpad.net/ubuntu-push/bus" |
983 | @@ -37,6 +36,7 @@ |
984 | clickhelp "launchpad.net/ubuntu-push/click/testing" |
985 | "launchpad.net/ubuntu-push/launch_helper" |
986 | "launchpad.net/ubuntu-push/messaging/reply" |
987 | + "launchpad.net/ubuntu-push/nih" |
988 | helpers "launchpad.net/ubuntu-push/testing" |
989 | "launchpad.net/ubuntu-push/testing/condition" |
990 | ) |
991 | @@ -80,16 +80,16 @@ |
992 | } |
993 | } |
994 | |
995 | -func installTickMessageHandler(svc *PostalService) chan error { |
996 | - ch := make(chan error) |
997 | +func installTickMessageHandler(svc *PostalService) chan bool { |
998 | + ch := make(chan bool) |
999 | msgHandler := svc.GetMessageHandler() |
1000 | - svc.SetMessageHandler(func(app *click.AppId, nid string, output *launch_helper.HelperOutput) error { |
1001 | - var err error |
1002 | + svc.SetMessageHandler(func(app *click.AppId, nid string, output *launch_helper.HelperOutput) bool { |
1003 | + var b bool |
1004 | if msgHandler != nil { |
1005 | - err = msgHandler(app, nid, output) |
1006 | + b = msgHandler(app, nid, output) |
1007 | } |
1008 | - ch <- err |
1009 | - return err |
1010 | + ch <- b |
1011 | + return b |
1012 | }) |
1013 | return ch |
1014 | } |
1015 | @@ -270,6 +270,60 @@ |
1016 | } |
1017 | |
1018 | // |
1019 | +// post() tests |
1020 | + |
1021 | +func (ps *postalSuite) TestPostHappyPath(c *C) { |
1022 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1023 | + svc.msgHandler = nil |
1024 | + ch := installTickMessageHandler(svc) |
1025 | + svc.launchers = map[string]launch_helper.HelperLauncher{ |
1026 | + "click": ps.fakeLauncher, |
1027 | + } |
1028 | + c.Assert(svc.Start(), IsNil) |
1029 | + payload := `{"message": {"world":1}}` |
1030 | + rvs, err := svc.post(aPackageOnBus, []interface{}{anAppId, payload}, nil) |
1031 | + c.Assert(err, IsNil) |
1032 | + c.Check(rvs, IsNil) |
1033 | + |
1034 | + if ps.fakeLauncher.done != nil { |
1035 | + // wait for the two posts to "launch" |
1036 | + inputData := takeNextBytes(ps.fakeLauncher.ch) |
1037 | + c.Check(string(inputData), Equals, payload) |
1038 | + |
1039 | + go ps.fakeLauncher.done("0") // OneDone |
1040 | + } |
1041 | + |
1042 | + c.Check(takeNextBool(ch), Equals, false) // one, |
1043 | + // xxx here? |
1044 | + c.Assert(svc.mbox, HasLen, 1) |
1045 | + box, ok := svc.mbox[anAppId] |
1046 | + c.Check(ok, Equals, true) |
1047 | + msgs := box.AllMessages() |
1048 | + c.Assert(msgs, HasLen, 1) |
1049 | + c.Check(msgs[0], Equals, `{"world":1}`) |
1050 | + c.Check(box.nids[0], Not(Equals), "") |
1051 | +} |
1052 | + |
1053 | +func (ps *postalSuite) TestPostFailsIfBadArgs(c *C) { |
1054 | + for i, s := range []struct { |
1055 | + args []interface{} |
1056 | + errt error |
1057 | + }{ |
1058 | + {nil, ErrBadArgCount}, |
1059 | + {[]interface{}{}, ErrBadArgCount}, |
1060 | + {[]interface{}{1}, ErrBadArgCount}, |
1061 | + {[]interface{}{anAppId, 1}, ErrBadArgType}, |
1062 | + {[]interface{}{anAppId, "zoom"}, ErrBadJSON}, |
1063 | + {[]interface{}{1, "hello"}, ErrBadArgType}, |
1064 | + {[]interface{}{1, 2, 3}, ErrBadArgCount}, |
1065 | + {[]interface{}{"bar", "hello"}, ErrBadAppId}, |
1066 | + } { |
1067 | + reg, err := new(PostalService).post(aPackageOnBus, s.args, nil) |
1068 | + c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
1069 | + c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
1070 | + } |
1071 | +} |
1072 | + |
1073 | // Post() tests |
1074 | |
1075 | func (ps *postalSuite) TestPostWorks(c *C) { |
1076 | @@ -282,30 +336,31 @@ |
1077 | "legacy": fakeLauncher2, |
1078 | } |
1079 | c.Assert(svc.Start(), IsNil) |
1080 | - rvs, err := svc.post(aPackageOnBus, []interface{}{anAppId, `{"message":{"world":1}}`}, nil) |
1081 | - c.Assert(err, IsNil) |
1082 | - c.Check(rvs, IsNil) |
1083 | - rvs, err = svc.post(aPackageOnBus, []interface{}{anAppId, `{"message":{"moon":1}}`}, nil) |
1084 | - c.Assert(err, IsNil) |
1085 | - c.Check(rvs, IsNil) |
1086 | - rvs, err = svc.post("_", []interface{}{"_classic-app", `{"message":{"mars":42}}`}, nil) |
1087 | - c.Assert(err, IsNil) |
1088 | - c.Check(rvs, IsNil) |
1089 | + |
1090 | + app := clickhelp.MustParseAppId(anAppId) |
1091 | + // these two, being for the same app, will be done sequentially. |
1092 | + svc.Post(app, "m1", json.RawMessage(`{"message":{"world":1}}`)) |
1093 | + svc.Post(app, "m2", json.RawMessage(`{"message":{"moon":1}}`)) |
1094 | + classicApp := clickhelp.MustParseAppId("_classic-app") |
1095 | + svc.Post(classicApp, "m3", json.RawMessage(`{"message":{"mars":42}}`)) |
1096 | |
1097 | if ps.fakeLauncher.done != nil { |
1098 | // wait for the two posts to "launch" |
1099 | takeNextBytes(ps.fakeLauncher.ch) |
1100 | - takeNextBytes(ps.fakeLauncher.ch) |
1101 | takeNextBytes(fakeLauncher2.ch) |
1102 | - |
1103 | go ps.fakeLauncher.done("0") // OneDone |
1104 | + go fakeLauncher2.done("0") |
1105 | + |
1106 | + inputData := takeNextBytes(ps.fakeLauncher.ch) |
1107 | + |
1108 | + c.Check(string(inputData), Equals, `{"message":{"moon":1}}`) |
1109 | + |
1110 | go ps.fakeLauncher.done("1") // OneDone |
1111 | - go fakeLauncher2.done("0") |
1112 | } |
1113 | |
1114 | - c.Check(takeNextError(ch), IsNil) // one, |
1115 | - c.Check(takeNextError(ch), IsNil) // two, |
1116 | - c.Check(takeNextError(ch), IsNil) // three posts |
1117 | + c.Check(takeNextBool(ch), Equals, false) // one, |
1118 | + c.Check(takeNextBool(ch), Equals, false) // two, |
1119 | + c.Check(takeNextBool(ch), Equals, false) // three posts |
1120 | c.Assert(svc.mbox, HasLen, 2) |
1121 | box, ok := svc.mbox[anAppId] |
1122 | c.Check(ok, Equals, true) |
1123 | @@ -313,6 +368,7 @@ |
1124 | c.Assert(msgs, HasLen, 2) |
1125 | c.Check(msgs[0], Equals, `{"world":1}`) |
1126 | c.Check(msgs[1], Equals, `{"moon":1}`) |
1127 | + c.Check(box.nids, DeepEquals, []string{"m1", "m2"}) |
1128 | box, ok = svc.mbox["_classic-app"] |
1129 | c.Assert(ok, Equals, true) |
1130 | msgs = box.AllMessages() |
1131 | @@ -320,7 +376,34 @@ |
1132 | c.Check(msgs[0], Equals, `{"mars":42}`) |
1133 | } |
1134 | |
1135 | -func (ps *postalSuite) TestPostSignal(c *C) { |
1136 | +func (ps *postalSuite) TestPostCallsMessageHandlerDetails(c *C) { |
1137 | + ch := make(chan *launch_helper.HelperOutput) |
1138 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1139 | + svc.launchers = map[string]launch_helper.HelperLauncher{ |
1140 | + "click": ps.fakeLauncher, |
1141 | + } |
1142 | + c.Assert(svc.Start(), IsNil) |
1143 | + // check the message handler gets called |
1144 | + app := clickhelp.MustParseAppId(anAppId) |
1145 | + f := func(app *click.AppId, nid string, s *launch_helper.HelperOutput) bool { |
1146 | + c.Check(app.Base(), Equals, anAppId) |
1147 | + c.Check(nid, Equals, "m7") |
1148 | + ch <- s |
1149 | + return true |
1150 | + } |
1151 | + svc.SetMessageHandler(f) |
1152 | + svc.Post(app, "m7", json.RawMessage("{}")) |
1153 | + |
1154 | + if ps.fakeLauncher.done != nil { |
1155 | + takeNextBytes(ps.fakeLauncher.ch) |
1156 | + |
1157 | + go ps.fakeLauncher.done("0") // OneDone |
1158 | + } |
1159 | + |
1160 | + c.Check(takeNextHelperOutput(ch), DeepEquals, &launch_helper.HelperOutput{}) |
1161 | +} |
1162 | + |
1163 | +func (ps *postalSuite) TestAfterMessageHandlerSignal(c *C) { |
1164 | svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1165 | svc.msgHandler = nil |
1166 | |
1167 | @@ -341,106 +424,24 @@ |
1168 | c.Check(callArgs[l-1].Args, DeepEquals, []interface{}{"Post", aPackageOnBus, []interface{}{anAppId}}) |
1169 | } |
1170 | |
1171 | -func (ps *postalSuite) TestPostFailsIfPostFails(c *C) { |
1172 | - bus := testibus.NewTestingEndpoint(condition.Work(true), |
1173 | - condition.Work(false)) |
1174 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1175 | - svc.Bus = bus |
1176 | - svc.SetMessageHandler(func(*click.AppId, string, *launch_helper.HelperOutput) error { return errors.New("fail") }) |
1177 | - _, err := svc.post("/hello", []interface{}{"xyzzy"}, nil) |
1178 | - c.Check(err, NotNil) |
1179 | -} |
1180 | - |
1181 | -func (ps *postalSuite) TestPostFailsIfBadArgs(c *C) { |
1182 | - for i, s := range []struct { |
1183 | - args []interface{} |
1184 | - errt error |
1185 | - }{ |
1186 | - {nil, ErrBadArgCount}, |
1187 | - {[]interface{}{}, ErrBadArgCount}, |
1188 | - {[]interface{}{1}, ErrBadArgCount}, |
1189 | - {[]interface{}{anAppId, 1}, ErrBadArgType}, |
1190 | - {[]interface{}{anAppId, "zoom"}, ErrBadJSON}, |
1191 | - {[]interface{}{1, "hello"}, ErrBadArgType}, |
1192 | - {[]interface{}{1, 2, 3}, ErrBadArgCount}, |
1193 | - {[]interface{}{"bar", "hello"}, ErrBadAppId}, |
1194 | - } { |
1195 | - reg, err := new(PostalService).post(aPackageOnBus, s.args, nil) |
1196 | - c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
1197 | - c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
1198 | - } |
1199 | -} |
1200 | - |
1201 | -// |
1202 | -// Post (Broadcast) tests |
1203 | - |
1204 | -func (ps *postalSuite) TestPostBroadcast(c *C) { |
1205 | - |
1206 | - bus := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1)) |
1207 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1208 | - |
1209 | - svc.NotificationsEndp = bus |
1210 | - svc.launchers = map[string]launch_helper.HelperLauncher{ |
1211 | - "legacy": ps.fakeLauncher, |
1212 | - } |
1213 | - c.Assert(svc.Start(), IsNil) |
1214 | - |
1215 | - msgId := uuid.New() |
1216 | - svc.SetMessageHandler(func(app *click.AppId, nid string, output *launch_helper.HelperOutput) error { |
1217 | - expectedAppId, _ := click.ParseAppId("_ubuntu-system-settings") |
1218 | - c.Check(app, DeepEquals, expectedAppId) |
1219 | - c.Check(nid, Equals, msgId) |
1220 | - return nil |
1221 | - }) |
1222 | - decoded := map[string]interface{}{ |
1223 | - "daily/mako": []interface{}{float64(102), "tubular"}, |
1224 | - } |
1225 | - // marshal decoded to json |
1226 | - payload, _ := json.Marshal(decoded) |
1227 | - appId, _ := click.ParseAppId("_ubuntu-system-settings") |
1228 | - err := svc.Post(appId, msgId, payload) |
1229 | - c.Assert(err, IsNil) |
1230 | - |
1231 | - if ps.fakeLauncher.done != nil { |
1232 | - inputData := takeNextBytes(ps.fakeLauncher.ch) |
1233 | - expectedData, _ := json.Marshal(decoded) |
1234 | - c.Check(inputData, DeepEquals, expectedData) |
1235 | - go ps.fakeLauncher.done("0") // OneDone |
1236 | - } |
1237 | -} |
1238 | - |
1239 | -func (ps *postalSuite) TestPostBroadcastDoesNotFail(c *C) { |
1240 | - bus := testibus.NewTestingEndpoint(condition.Work(true), |
1241 | - condition.Work(false)) |
1242 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1243 | - svc.launchers = map[string]launch_helper.HelperLauncher{ |
1244 | - "legacy": ps.fakeLauncher, |
1245 | - } |
1246 | - c.Assert(svc.Start(), IsNil) |
1247 | - svc.NotificationsEndp = bus |
1248 | - svc.SetMessageHandler(func(*click.AppId, string, *launch_helper.HelperOutput) error { |
1249 | - ps.log.Debugf("about to fail") |
1250 | - return errors.New("fail") |
1251 | - }) |
1252 | - ch := installTickMessageHandler(svc) |
1253 | - decoded := map[string]interface{}{ |
1254 | - "daily/mako": []interface{}{float64(102), "tubular"}, |
1255 | - } |
1256 | - // marshal decoded to json |
1257 | - payload, _ := json.Marshal(decoded) |
1258 | - appId, _ := click.ParseAppId("_ubuntu-system-settings") |
1259 | - msgId := uuid.New() |
1260 | - err := svc.Post(appId, msgId, payload) |
1261 | - c.Assert(err, IsNil) |
1262 | - |
1263 | - if ps.fakeLauncher.done != nil { |
1264 | - takeNextBytes(ps.fakeLauncher.ch) |
1265 | - go ps.fakeLauncher.done("0") // OneDone |
1266 | - } |
1267 | - |
1268 | - c.Check(takeNextError(ch), NotNil) // the messagehandler failed |
1269 | - c.Check(err, IsNil) // but broadcast was oblivious |
1270 | - c.Check(ps.log.Captured(), Matches, `(?sm).*about to fail$`) |
1271 | +func (ps *postalSuite) TestFailingMessageHandlerSurvived(c *C) { |
1272 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1273 | + svc.SetMessageHandler(func(*click.AppId, string, *launch_helper.HelperOutput) bool { |
1274 | + return false |
1275 | + }) |
1276 | + |
1277 | + hInp := &launch_helper.HelperInput{ |
1278 | + App: clickhelp.MustParseAppId(anAppId), |
1279 | + } |
1280 | + res := &launch_helper.HelperResult{Input: hInp} |
1281 | + |
1282 | + svc.handleHelperResult(res) |
1283 | + |
1284 | + c.Check(ps.log.Captured(), Equals, "DEBUG msgHandler did not present the notification\n") |
1285 | + // we actually want to send a signal even if we didn't do anything |
1286 | + callArgs := testibus.GetCallArgs(ps.bus) |
1287 | + c.Assert(len(callArgs), Equals, 1) |
1288 | + c.Check(callArgs[0].Member, Equals, "::Signal") |
1289 | } |
1290 | |
1291 | // |
1292 | @@ -492,41 +493,15 @@ |
1293 | svc := new(PostalService) |
1294 | c.Assert(svc.msgHandler, IsNil) |
1295 | var ext = &launch_helper.HelperOutput{} |
1296 | - e := errors.New("Hello") |
1297 | - f := func(_ *click.AppId, _ string, s *launch_helper.HelperOutput) error { ext = s; return e } |
1298 | + f := func(_ *click.AppId, _ string, s *launch_helper.HelperOutput) bool { ext = s; return false } |
1299 | c.Check(svc.GetMessageHandler(), IsNil) |
1300 | svc.SetMessageHandler(f) |
1301 | c.Check(svc.GetMessageHandler(), NotNil) |
1302 | hOutput := &launch_helper.HelperOutput{[]byte("37"), nil} |
1303 | - c.Check(svc.msgHandler(nil, "", hOutput), Equals, e) |
1304 | + c.Check(svc.msgHandler(nil, "", hOutput), Equals, false) |
1305 | c.Check(ext, DeepEquals, hOutput) |
1306 | } |
1307 | |
1308 | -func (ps *postalSuite) TestPostCallsMessageHandler(c *C) { |
1309 | - ch := make(chan *launch_helper.HelperOutput) |
1310 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1311 | - svc.launchers = map[string]launch_helper.HelperLauncher{ |
1312 | - "click": ps.fakeLauncher, |
1313 | - } |
1314 | - c.Assert(svc.Start(), IsNil) |
1315 | - // check the message handler gets called |
1316 | - f := func(_ *click.AppId, _ string, s *launch_helper.HelperOutput) error { ch <- s; return nil } |
1317 | - svc.SetMessageHandler(f) |
1318 | - c.Check(svc.Post(&click.AppId{Click: true}, "thing", json.RawMessage("{}")), IsNil) |
1319 | - |
1320 | - if ps.fakeLauncher.done != nil { |
1321 | - takeNextBytes(ps.fakeLauncher.ch) |
1322 | - |
1323 | - go ps.fakeLauncher.done("0") // OneDone |
1324 | - } |
1325 | - |
1326 | - c.Check(takeNextHelperOutput(ch), DeepEquals, &launch_helper.HelperOutput{}) |
1327 | - err := errors.New("ouch") |
1328 | - svc.SetMessageHandler(func(*click.AppId, string, *launch_helper.HelperOutput) error { return err }) |
1329 | - // but the error doesn't bubble out |
1330 | - c.Check(svc.Post(&click.AppId{}, "", json.RawMessage("{}")), IsNil) |
1331 | -} |
1332 | - |
1333 | func (ps *postalSuite) TestMessageHandlerPresents(c *C) { |
1334 | endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1)) |
1335 | svc := NewPostalService(nil, ps.log) |
1336 | @@ -544,12 +519,12 @@ |
1337 | vib := &launch_helper.Vibration{Duration: 500} |
1338 | emb := &launch_helper.EmblemCounter{Count: 2, Visible: true} |
1339 | output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card, EmblemCounter: emb, Vibrate: vib}} |
1340 | - err := svc.messageHandler(&click.AppId{}, "", output) |
1341 | - c.Assert(err, IsNil) |
1342 | + b := svc.messageHandler(&click.AppId{}, "", output) |
1343 | + c.Assert(b, Equals, true) |
1344 | args := testibus.GetCallArgs(endp) |
1345 | l := len(args) |
1346 | if l < 4 { |
1347 | - c.Fatal("not enough elements in resposne from GetCallArgs") |
1348 | + c.Fatal("not enough elements in response from GetCallArgs") |
1349 | } |
1350 | mm := make([]string, 4) |
1351 | for i, m := range args[l-4:] { |
1352 | @@ -561,7 +536,7 @@ |
1353 | c.Check(mm, DeepEquals, []string{"::SetProperty", "::SetProperty", "Notify", "VibratePattern"}) |
1354 | // For the other ones, check the logs |
1355 | c.Check(ps.log.Captured(), Matches, `(?sm).* no persistable card:.*`) |
1356 | - c.Check(ps.log.Captured(), Matches, `(?sm).* no Sound in the notification.*`) |
1357 | + c.Check(ps.log.Captured(), Matches, `(?sm).* notification has no Sound:.*`) |
1358 | } |
1359 | |
1360 | func (ps *postalSuite) TestMessageHandlerReportsFailedNotifies(c *C) { |
1361 | @@ -581,18 +556,18 @@ |
1362 | svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1363 | svc.WindowStackEndp = endp |
1364 | c.Assert(svc.Start(), IsNil) |
1365 | - output := &launch_helper.HelperOutput{} // Doesn't matter |
1366 | - err := svc.messageHandler(clickhelp.MustParseAppId("com.example.test_test-app_0"), "", output) |
1367 | - c.Check(err, IsNil) |
1368 | - c.Check(ps.log.Captured(), Matches, `(?sm).* Notification skipped because app is focused.*`) |
1369 | + output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{}} // Doesn't matter |
1370 | + b := svc.messageHandler(clickhelp.MustParseAppId("com.example.test_test-app_0"), "", output) |
1371 | + c.Check(b, Equals, false) |
1372 | + c.Check(ps.log.Captured(), Matches, `(?sm).* notification skipped because app is focused.*`) |
1373 | } |
1374 | |
1375 | func (ps *postalSuite) TestMessageHandlerReportsButIgnoresUnmarshalErrors(c *C) { |
1376 | svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1377 | c.Assert(svc.Start(), IsNil) |
1378 | output := &launch_helper.HelperOutput{[]byte(`broken`), nil} |
1379 | - err := svc.messageHandler(nil, "", output) |
1380 | - c.Check(err, IsNil) |
1381 | + b := svc.messageHandler(nil, "", output) |
1382 | + c.Check(b, Equals, false) |
1383 | c.Check(ps.log.Captured(), Matches, "(?msi).*skipping notification: nil.*") |
1384 | } |
1385 | |
1386 | @@ -602,20 +577,35 @@ |
1387 | c.Assert(svc.Start(), IsNil) |
1388 | svc.NotificationsEndp = endp |
1389 | output := &launch_helper.HelperOutput{[]byte(`{}`), nil} |
1390 | - err := svc.messageHandler(nil, "", output) |
1391 | - c.Assert(err, IsNil) |
1392 | + b := svc.messageHandler(nil, "", output) |
1393 | + c.Assert(b, Equals, false) |
1394 | c.Check(ps.log.Captured(), Matches, "(?msi).*skipping notification: nil.*") |
1395 | } |
1396 | |
1397 | +func (ps *postalSuite) TestMessageHandlerInvalidAction(c *C) { |
1398 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1399 | + endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false), []string{"com.example.test_test-app"}) |
1400 | + svc.URLDispatcherEndp = endp |
1401 | + c.Assert(svc.Start(), IsNil) |
1402 | + card := launch_helper.Card{Actions: []string{"notsupported://test-app"}} |
1403 | + output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: &card}} |
1404 | + b := svc.messageHandler(clickhelp.MustParseAppId("com.example.test_test-app_0"), "", output) |
1405 | + c.Check(b, Equals, false) |
1406 | + c.Check(ps.log.Captured(), Matches, `(?sm).*TestURL for \[notsupported://test-app\] failed with no way.*`) |
1407 | +} |
1408 | + |
1409 | func (ps *postalSuite) TestHandleActionsDispatches(c *C) { |
1410 | svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1411 | + fmm := new(fakeMM) |
1412 | + app, _ := click.ParseAppId("com.example.test_test-app") |
1413 | c.Assert(svc.Start(), IsNil) |
1414 | + svc.messagingMenu = fmm |
1415 | aCh := make(chan *notifications.RawAction) |
1416 | rCh := make(chan *reply.MMActionReply) |
1417 | bCh := make(chan bool) |
1418 | go func() { |
1419 | aCh <- nil // just in case? |
1420 | - aCh <- ¬ifications.RawAction{Action: "potato://"} |
1421 | + aCh <- ¬ifications.RawAction{App: app, Action: "potato://", Nid: "xyzzy"} |
1422 | close(aCh) |
1423 | bCh <- true |
1424 | }() |
1425 | @@ -624,19 +614,22 @@ |
1426 | args := testibus.GetCallArgs(ps.urlDispBus) |
1427 | c.Assert(args, HasLen, 1) |
1428 | c.Check(args[0].Member, Equals, "DispatchURL") |
1429 | - c.Assert(args[0].Args, HasLen, 1) |
1430 | + c.Assert(args[0].Args, HasLen, 2) |
1431 | c.Assert(args[0].Args[0], Equals, "potato://") |
1432 | + c.Assert(args[0].Args[1], Equals, app.DispatchPackage()) |
1433 | + c.Check(fmm.calls, DeepEquals, []string{"remove:xyzzy:true"}) |
1434 | } |
1435 | |
1436 | func (ps *postalSuite) TestHandleMMUActionsDispatches(c *C) { |
1437 | svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1438 | c.Assert(svc.Start(), IsNil) |
1439 | + app, _ := click.ParseAppId("com.example.test_test-app") |
1440 | aCh := make(chan *notifications.RawAction) |
1441 | rCh := make(chan *reply.MMActionReply) |
1442 | bCh := make(chan bool) |
1443 | go func() { |
1444 | rCh <- nil // just in case? |
1445 | - rCh <- &reply.MMActionReply{Action: "potato://", Notification: "foo.bar"} |
1446 | + rCh <- &reply.MMActionReply{App: app, Action: "potato://", Notification: "foo.bar"} |
1447 | close(rCh) |
1448 | bCh <- true |
1449 | }() |
1450 | @@ -645,6 +638,144 @@ |
1451 | args := testibus.GetCallArgs(ps.urlDispBus) |
1452 | c.Assert(args, HasLen, 1) |
1453 | c.Check(args[0].Member, Equals, "DispatchURL") |
1454 | - c.Assert(args[0].Args, HasLen, 1) |
1455 | + c.Assert(args[0].Args, HasLen, 2) |
1456 | c.Assert(args[0].Args[0], Equals, "potato://") |
1457 | + c.Assert(args[0].Args[1], Equals, app.DispatchPackage()) |
1458 | +} |
1459 | + |
1460 | +func (ps *postalSuite) TestValidateActions(c *C) { |
1461 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1462 | + endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []string{"com.example.test_test-app_0"}) |
1463 | + svc.URLDispatcherEndp = endp |
1464 | + c.Assert(svc.Start(), IsNil) |
1465 | + card := launch_helper.Card{Actions: []string{"potato://test-app"}} |
1466 | + notif := &launch_helper.Notification{Card: &card} |
1467 | + b := svc.validateActions(clickhelp.MustParseAppId("com.example.test_test-app_0"), notif) |
1468 | + c.Check(b, Equals, true) |
1469 | +} |
1470 | + |
1471 | +func (ps *postalSuite) TestValidateActionsNoActions(c *C) { |
1472 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1473 | + card := launch_helper.Card{} |
1474 | + notif := &launch_helper.Notification{Card: &card} |
1475 | + b := svc.validateActions(clickhelp.MustParseAppId("com.example.test_test-app_0"), notif) |
1476 | + c.Check(b, Equals, true) |
1477 | +} |
1478 | + |
1479 | +func (ps *postalSuite) TestValidateActionsNoCard(c *C) { |
1480 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1481 | + notif := &launch_helper.Notification{} |
1482 | + b := svc.validateActions(clickhelp.MustParseAppId("com.example.test_test-app_0"), notif) |
1483 | + c.Check(b, Equals, true) |
1484 | +} |
1485 | + |
1486 | +type fakeMM struct { |
1487 | + calls []string |
1488 | +} |
1489 | + |
1490 | +func (*fakeMM) Present(*click.AppId, string, *launch_helper.Notification) bool { return false } |
1491 | +func (*fakeMM) GetCh() chan *reply.MMActionReply { return nil } |
1492 | +func (*fakeMM) StartCleanupLoop() {} |
1493 | +func (fmm *fakeMM) RemoveNotification(s string, b bool) { |
1494 | + fmm.calls = append(fmm.calls, fmt.Sprintf("remove:%s:%t", s, b)) |
1495 | +} |
1496 | +func (fmm *fakeMM) Clear(*click.AppId, ...string) int { |
1497 | + fmm.calls = append(fmm.calls, "clear") |
1498 | + return 42 |
1499 | +} |
1500 | +func (fmm *fakeMM) Tags(*click.AppId) []string { |
1501 | + fmm.calls = append(fmm.calls, "tags") |
1502 | + return []string{"hello"} |
1503 | +} |
1504 | + |
1505 | +func (ps *postalSuite) TestListPersistent(c *C) { |
1506 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1507 | + fmm := new(fakeMM) |
1508 | + svc.messagingMenu = fmm |
1509 | + |
1510 | + itags, err := svc.listPersistent(aPackageOnBus, []interface{}{anAppId}, nil) |
1511 | + c.Assert(err, IsNil) |
1512 | + c.Assert(itags, HasLen, 1) |
1513 | + c.Assert(itags[0], FitsTypeOf, []string(nil)) |
1514 | + tags := itags[0].([]string) |
1515 | + c.Check(tags, DeepEquals, []string{"hello"}) |
1516 | + c.Check(fmm.calls, DeepEquals, []string{"tags"}) |
1517 | +} |
1518 | + |
1519 | +func (ps *postalSuite) TestListPersistentErrors(c *C) { |
1520 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1521 | + _, err := svc.listPersistent(aPackageOnBus, nil, nil) |
1522 | + c.Check(err, Equals, ErrBadArgCount) |
1523 | + _, err = svc.listPersistent(aPackageOnBus, []interface{}{42}, nil) |
1524 | + c.Check(err, Equals, ErrBadArgType) |
1525 | + _, err = svc.listPersistent(aPackageOnBus, []interface{}{"xyzzy"}, nil) |
1526 | + c.Check(err, Equals, ErrBadAppId) |
1527 | +} |
1528 | + |
1529 | +func (ps *postalSuite) TestClearPersistent(c *C) { |
1530 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1531 | + fmm := new(fakeMM) |
1532 | + svc.messagingMenu = fmm |
1533 | + |
1534 | + icleared, err := svc.clearPersistent(aPackageOnBus, []interface{}{anAppId, "one", ""}, nil) |
1535 | + c.Assert(err, IsNil) |
1536 | + c.Assert(icleared, HasLen, 1) |
1537 | + c.Check(icleared[0], Equals, uint32(42)) |
1538 | +} |
1539 | + |
1540 | +func (ps *postalSuite) TestClearPersistentErrors(c *C) { |
1541 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1542 | + for i, s := range []struct { |
1543 | + args []interface{} |
1544 | + err error |
1545 | + }{ |
1546 | + {[]interface{}{}, ErrBadArgCount}, |
1547 | + {[]interface{}{42}, ErrBadArgType}, |
1548 | + {[]interface{}{"xyzzy"}, ErrBadAppId}, |
1549 | + {[]interface{}{anAppId, 42}, ErrBadArgType}, |
1550 | + {[]interface{}{anAppId, "", 42}, ErrBadArgType}, |
1551 | + } { |
1552 | + _, err := svc.clearPersistent(aPackageOnBus, s.args, nil) |
1553 | + c.Check(err, Equals, s.err, Commentf("iter %d", i)) |
1554 | + } |
1555 | +} |
1556 | + |
1557 | +func (ps *postalSuite) TestSetCounter(c *C) { |
1558 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1559 | + c.Check(svc.Start(), IsNil) |
1560 | + |
1561 | + _, err := svc.setCounter(aPackageOnBus, []interface{}{anAppId, int32(42), true}, nil) |
1562 | + c.Assert(err, IsNil) |
1563 | + |
1564 | + quoted := "/" + string(nih.Quote([]byte(anAppId))) |
1565 | + |
1566 | + callArgs := testibus.GetCallArgs(svc.EmblemCounterEndp) |
1567 | + c.Assert(callArgs, HasLen, 2) |
1568 | + c.Check(callArgs[0].Member, Equals, "::SetProperty") |
1569 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", quoted, dbus.Variant{int32(42)}}) |
1570 | + |
1571 | + c.Check(callArgs[1].Member, Equals, "::SetProperty") |
1572 | + c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", quoted, dbus.Variant{true}}) |
1573 | +} |
1574 | + |
1575 | +func (ps *postalSuite) TestSetCounterErrors(c *C) { |
1576 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
1577 | + svc.Start() |
1578 | + for i, s := range []struct { |
1579 | + args []interface{} |
1580 | + err error |
1581 | + }{ |
1582 | + {[]interface{}{anAppId, int32(42), true}, nil}, // for reference |
1583 | + {[]interface{}{}, ErrBadArgCount}, |
1584 | + {[]interface{}{anAppId}, ErrBadArgCount}, |
1585 | + {[]interface{}{anAppId, int32(42)}, ErrBadArgCount}, |
1586 | + {[]interface{}{anAppId, int32(42), true, "potato"}, ErrBadArgCount}, |
1587 | + {[]interface{}{"xyzzy", int32(42), true}, ErrBadAppId}, |
1588 | + {[]interface{}{1234567, int32(42), true}, ErrBadArgType}, |
1589 | + {[]interface{}{anAppId, "potatoe", true}, ErrBadArgType}, |
1590 | + {[]interface{}{anAppId, int32(42), "ru"}, ErrBadArgType}, |
1591 | + } { |
1592 | + _, err := svc.setCounter(aPackageOnBus, s.args, nil) |
1593 | + c.Check(err, Equals, s.err, Commentf("iter %d", i)) |
1594 | + } |
1595 | } |
1596 | |
1597 | === modified file 'debian/changelog' |
1598 | --- debian/changelog 2014-07-23 14:09:31 +0000 |
1599 | +++ debian/changelog 2014-07-29 16:56:22 +0000 |
1600 | @@ -1,3 +1,24 @@ |
1601 | +ubuntu-push (0.50) UNRELEASED; urgency=medium |
1602 | + |
1603 | + [ Samuele Pedroni ] |
1604 | + * Cleanup and improve pos/Post tests |
1605 | + |
1606 | + [ Guillermo Gonzalez ] |
1607 | + * Add a loop to cleanup MessagingMenu.notifications map when the |
1608 | + notifications are dismissed. |
1609 | + * Add TestURL to URLDispatcher and update DispatchURL signature. |
1610 | + * Add validateActions (check with url-dispatcher->TestURL) method to the |
1611 | + PostalService and wire it in messageHandler. |
1612 | + |
1613 | + [ John R. Lenton ] |
1614 | + * Implement ListPersistent/ClearPersistent/SetCounter postal endpoints. |
1615 | + * Remove snap decisions support. |
1616 | + * Remove the notification from the messaging menu when the bubble is |
1617 | + acted on. |
1618 | + * Don't run more than 5 helpers at a time, and never more than one per app. |
1619 | + |
1620 | + -- John R. Lenton <john.lenton@canonical.com> Mon, 28 Jul 2014 17:44:35 +0200 |
1621 | + |
1622 | ubuntu-push (0.49.1+14.10.20140723.1-0ubuntu1) utopic; urgency=medium |
1623 | |
1624 | [ John R. Lenton ] |
1625 | |
1626 | === modified file 'launch_helper/helper_output.go' |
1627 | --- launch_helper/helper_output.go 2014-07-18 14:23:34 +0000 |
1628 | +++ launch_helper/helper_output.go 2014-07-29 16:56:22 +0000 |
1629 | @@ -54,6 +54,7 @@ |
1630 | Sound string `json:"sound"` // a sound file. Users can disable this, so don't rely on it exclusively. Defaults to empty (no sound). |
1631 | Vibrate *Vibration `json:"vibrate"` // users can disable this, blah blah. Defaults to null (no vibration) |
1632 | EmblemCounter *EmblemCounter `json:"emblem-counter"` // puts a counter on an emblem in the launcher. Defaults to nil (no change to emblem counter). |
1633 | + Tag string `json:"tag,omitempty"` // tag used for Clear/ListPersistent. |
1634 | } |
1635 | |
1636 | // HelperOutput is the expected output of a helper |
1637 | |
1638 | === modified file 'launch_helper/helper_test.go' |
1639 | --- launch_helper/helper_test.go 2014-07-18 14:53:47 +0000 |
1640 | +++ launch_helper/helper_test.go 2014-07-29 16:56:22 +0000 |
1641 | @@ -43,11 +43,11 @@ |
1642 | } |
1643 | |
1644 | func (s *runnerSuite) TestTrivialPoolWorks(c *C) { |
1645 | - notif := &Notification{Sound: "42"} |
1646 | + notif := &Notification{Sound: "42", Tag: "foo"} |
1647 | |
1648 | triv := NewTrivialHelperPool(s.testlog) |
1649 | ch := triv.Start() |
1650 | - in := &HelperInput{App: s.app, Payload: []byte(`{"message": {"m":42}, "notification": {"sound": "42"}}`)} |
1651 | + in := &HelperInput{App: s.app, Payload: []byte(`{"message": {"m":42}, "notification": {"sound": "42", "tag": "foo"}}`)} |
1652 | triv.Run("klick", in) |
1653 | out := <-ch |
1654 | c.Assert(out, NotNil) |
1655 | |
1656 | === modified file 'launch_helper/kindpool.go' |
1657 | --- launch_helper/kindpool.go 2014-07-19 00:18:18 +0000 |
1658 | +++ launch_helper/kindpool.go 2014-07-29 16:56:22 +0000 |
1659 | @@ -60,10 +60,12 @@ |
1660 | log logger.Logger |
1661 | chOut chan *HelperResult |
1662 | chIn chan *HelperInput |
1663 | + chDone chan *click.AppId |
1664 | launchers map[string]HelperLauncher |
1665 | lock sync.Mutex |
1666 | hmap map[string]*HelperArgs |
1667 | maxRuntime time.Duration |
1668 | + maxNum int |
1669 | } |
1670 | |
1671 | // DefaultLaunchers produces the default map for kind -> HelperLauncher |
1672 | @@ -81,12 +83,14 @@ |
1673 | hmap: make(map[string]*HelperArgs), |
1674 | launchers: launchers, |
1675 | maxRuntime: 5 * time.Second, |
1676 | + maxNum: 5, |
1677 | } |
1678 | } |
1679 | |
1680 | func (pool *kindHelperPool) Start() chan *HelperResult { |
1681 | pool.chOut = make(chan *HelperResult) |
1682 | pool.chIn = make(chan *HelperInput, InputBufferSize) |
1683 | + pool.chDone = make(chan *click.AppId) |
1684 | |
1685 | for kind, launcher := range pool.launchers { |
1686 | kind1 := kind |
1687 | @@ -98,18 +102,72 @@ |
1688 | } |
1689 | } |
1690 | |
1691 | - // xxx make sure at most X helpers are running |
1692 | - go func() { |
1693 | - for i := range pool.chIn { |
1694 | - if pool.handleOne(i) != nil { |
1695 | - pool.failOne(i) |
1696 | - } |
1697 | - } |
1698 | - }() |
1699 | + go pool.loop() |
1700 | |
1701 | return pool.chOut |
1702 | } |
1703 | |
1704 | +func (pool *kindHelperPool) loop() { |
1705 | + running := make(map[string]bool) |
1706 | + var backlog []*HelperInput |
1707 | + |
1708 | + for { |
1709 | + select { |
1710 | + case in, ok := <-pool.chIn: |
1711 | + if !ok { |
1712 | + return |
1713 | + } |
1714 | + if len(running) >= pool.maxNum || running[in.App.Original()] { |
1715 | + backlog = append(backlog, in) |
1716 | + pool.log.Debugf("current helper input backlog has grown to %d entries.", len(backlog)) |
1717 | + } else { |
1718 | + if pool.tryOne(in) { |
1719 | + running[in.App.Original()] = true |
1720 | + } |
1721 | + } |
1722 | + case app := <-pool.chDone: |
1723 | + delete(running, app.Original()) |
1724 | + if len(backlog) == 0 { |
1725 | + continue |
1726 | + } |
1727 | + backlogSz := 0 |
1728 | + done := false |
1729 | + for i, in := range backlog { |
1730 | + if in != nil { |
1731 | + if !done && !running[in.App.Original()] { |
1732 | + backlog[i] = nil |
1733 | + if pool.tryOne(in) { |
1734 | + running[in.App.Original()] = true |
1735 | + done = true |
1736 | + } |
1737 | + } else { |
1738 | + backlogSz++ |
1739 | + } |
1740 | + } |
1741 | + } |
1742 | + backlog = pool.shrinkBacklog(backlog, backlogSz) |
1743 | + pool.log.Debugf("current helper input backlog has shrunk to %d entries.", backlogSz) |
1744 | + } |
1745 | + } |
1746 | +} |
1747 | + |
1748 | +func (pool *kindHelperPool) shrinkBacklog(backlog []*HelperInput, backlogSz int) []*HelperInput { |
1749 | + if backlogSz == 0 { |
1750 | + return nil |
1751 | + } |
1752 | + if cap(backlog) < 2*backlogSz { |
1753 | + return backlog |
1754 | + } |
1755 | + pool.log.Debugf("copying backlog to avoid wasting too much space (%d/%d used)", backlogSz, cap(backlog)) |
1756 | + clean := make([]*HelperInput, 0, backlogSz) |
1757 | + for _, bentry := range backlog { |
1758 | + if bentry != nil { |
1759 | + clean = append(clean, bentry) |
1760 | + } |
1761 | + } |
1762 | + return clean |
1763 | +} |
1764 | + |
1765 | func (pool *kindHelperPool) Stop() { |
1766 | close(pool.chIn) |
1767 | for kind, launcher := range pool.launchers { |
1768 | @@ -125,6 +183,14 @@ |
1769 | pool.chIn <- input |
1770 | } |
1771 | |
1772 | +func (pool *kindHelperPool) tryOne(input *HelperInput) bool { |
1773 | + if pool.handleOne(input) != nil { |
1774 | + pool.failOne(input) |
1775 | + return false |
1776 | + } |
1777 | + return true |
1778 | +} |
1779 | + |
1780 | func (pool *kindHelperPool) failOne(input *HelperInput) { |
1781 | pool.log.Errorf("unable to get helper output; putting payload into message") |
1782 | pool.chOut <- &HelperResult{HelperOutput: HelperOutput{Message: input.Payload, Notification: nil}, Input: input} |
1783 | @@ -218,6 +284,7 @@ |
1784 | // nothing to do |
1785 | return |
1786 | } |
1787 | + pool.chDone <- args.Input.App |
1788 | defer func() { |
1789 | pool.cleanupTempFiles(args.FileIn, args.FileOut) |
1790 | }() |
1791 | |
1792 | === modified file 'launch_helper/kindpool_test.go' |
1793 | --- launch_helper/kindpool_test.go 2014-07-19 00:18:18 +0000 |
1794 | +++ launch_helper/kindpool_test.go 2014-07-29 16:56:22 +0000 |
1795 | @@ -17,6 +17,7 @@ |
1796 | package launch_helper |
1797 | |
1798 | import ( |
1799 | + "fmt" |
1800 | "io/ioutil" |
1801 | "os" |
1802 | "path/filepath" |
1803 | @@ -32,18 +33,30 @@ |
1804 | ) |
1805 | |
1806 | type poolSuite struct { |
1807 | - log *helpers.TestLogger |
1808 | - pool HelperPool |
1809 | + log *helpers.TestLogger |
1810 | + pool HelperPool |
1811 | + fakeLauncher *fakeHelperLauncher |
1812 | } |
1813 | |
1814 | var _ = Suite(&poolSuite{}) |
1815 | |
1816 | +func takeNext(ch chan *HelperResult, c *C) *HelperResult { |
1817 | + select { |
1818 | + case res := <-ch: |
1819 | + return res |
1820 | + case <-time.After(100 * time.Millisecond): |
1821 | + c.Fatal("timeout waiting for result") |
1822 | + } |
1823 | + return nil |
1824 | +} |
1825 | + |
1826 | type fakeHelperLauncher struct { |
1827 | done func(string) |
1828 | obs int |
1829 | err error |
1830 | lhex string |
1831 | argCh chan [5]string |
1832 | + runid int |
1833 | } |
1834 | |
1835 | func (fhl *fakeHelperLauncher) InstallObserver(done func(string)) error { |
1836 | @@ -67,7 +80,9 @@ |
1837 | |
1838 | func (fhl *fakeHelperLauncher) Launch(appId string, exec string, f1 string, f2 string) (string, error) { |
1839 | fhl.argCh <- [5]string{"Launch", appId, exec, f1, f2} |
1840 | - return "0", fhl.err |
1841 | + runid := fmt.Sprintf("%d", fhl.runid) |
1842 | + fhl.runid++ |
1843 | + return runid, fhl.err |
1844 | } |
1845 | |
1846 | func (fhl *fakeHelperLauncher) Stop(appId string, iid string) error { |
1847 | @@ -75,12 +90,21 @@ |
1848 | return nil |
1849 | } |
1850 | |
1851 | -var fakeLauncher *fakeHelperLauncher |
1852 | +func (s *poolSuite) waitForArgs(c *C, method string) [5]string { |
1853 | + var args [5]string |
1854 | + select { |
1855 | + case args = <-s.fakeLauncher.argCh: |
1856 | + case <-time.After(2 * time.Second): |
1857 | + c.Fatal("didn't call " + method) |
1858 | + } |
1859 | + c.Assert(args[0], Equals, method) |
1860 | + return args |
1861 | +} |
1862 | |
1863 | func (s *poolSuite) SetUpTest(c *C) { |
1864 | s.log = helpers.NewTestLogger(c, "debug") |
1865 | - fakeLauncher = &fakeHelperLauncher{argCh: make(chan [5]string, 10)} |
1866 | - s.pool = NewHelperPool(map[string]HelperLauncher{"fake": fakeLauncher}, s.log) |
1867 | + s.fakeLauncher = &fakeHelperLauncher{argCh: make(chan [5]string, 10)} |
1868 | + s.pool = NewHelperPool(map[string]HelperLauncher{"fake": s.fakeLauncher}, s.log) |
1869 | xdgCacheHome = c.MkDir |
1870 | } |
1871 | |
1872 | @@ -99,12 +123,12 @@ |
1873 | |
1874 | // check that Stop (tries to) remove the observer |
1875 | func (s *poolSuite) TestStartStopWork(c *C) { |
1876 | - c.Check(fakeLauncher.obs, Equals, 0) |
1877 | + c.Check(s.fakeLauncher.obs, Equals, 0) |
1878 | s.pool.Start() |
1879 | - c.Check(fakeLauncher.done, NotNil) |
1880 | - c.Check(fakeLauncher.obs, Equals, 1) |
1881 | + c.Check(s.fakeLauncher.done, NotNil) |
1882 | + c.Check(s.fakeLauncher.obs, Equals, 1) |
1883 | s.pool.Stop() |
1884 | - c.Check(fakeLauncher.obs, Equals, 0) |
1885 | + c.Check(s.fakeLauncher.obs, Equals, 0) |
1886 | } |
1887 | |
1888 | func (s *poolSuite) TestRunLaunches(c *C) { |
1889 | @@ -119,12 +143,8 @@ |
1890 | Payload: []byte(`"hello"`), |
1891 | } |
1892 | s.pool.Run("fake", &input) |
1893 | - select { |
1894 | - case arg := <-fakeLauncher.argCh: |
1895 | - c.Check(arg[:3], DeepEquals, []string{"Launch", helpId, "bar"}) |
1896 | - case <-time.After(100 * time.Millisecond): |
1897 | - c.Fatal("didn't call Launch") |
1898 | - } |
1899 | + launchArgs := s.waitForArgs(c, "Launch") |
1900 | + c.Check(launchArgs[:3], DeepEquals, []string{"Launch", helpId, "bar"}) |
1901 | args := s.pool.(*kindHelperPool).peekId("fake:0", func(*HelperArgs) {}) |
1902 | c.Assert(args, NotNil) |
1903 | args.Timer.Stop() |
1904 | @@ -135,7 +155,7 @@ |
1905 | } |
1906 | |
1907 | func (s *poolSuite) TestRunLaunchesLegacyStyle(c *C) { |
1908 | - fakeLauncher.lhex = "lhex" |
1909 | + s.fakeLauncher.lhex = "lhex" |
1910 | s.pool.Start() |
1911 | defer s.pool.Stop() |
1912 | appId := "_legacy" |
1913 | @@ -146,12 +166,8 @@ |
1914 | Payload: []byte(`"hello"`), |
1915 | } |
1916 | s.pool.Run("fake", &input) |
1917 | - select { |
1918 | - case arg := <-fakeLauncher.argCh: |
1919 | - c.Check(arg[:3], DeepEquals, []string{"Launch", "", "lhex"}) |
1920 | - case <-time.After(100 * time.Millisecond): |
1921 | - c.Fatal("didn't call Launch") |
1922 | - } |
1923 | + launchArgs := s.waitForArgs(c, "Launch") |
1924 | + c.Check(launchArgs[:3], DeepEquals, []string{"Launch", "", "lhex"}) |
1925 | args := s.pool.(*kindHelperPool).peekId("fake:0", func(*HelperArgs) {}) |
1926 | c.Assert(args, NotNil) |
1927 | args.Timer.Stop() |
1928 | @@ -170,12 +186,7 @@ |
1929 | Payload: []byte(`"hello"`), |
1930 | } |
1931 | s.pool.Run("not-there", &input) |
1932 | - var res *HelperResult |
1933 | - select { |
1934 | - case res = <-ch: |
1935 | - case <-time.After(100 * time.Millisecond): |
1936 | - c.Fatal("timeout") |
1937 | - } |
1938 | + res := takeNext(ch, c) |
1939 | c.Check(res.Message, DeepEquals, input.Payload) |
1940 | c.Check(res.Notification, IsNil) |
1941 | c.Check(*res.Input, DeepEquals, input) |
1942 | @@ -191,19 +202,14 @@ |
1943 | Payload: []byte(`"hello"`), |
1944 | } |
1945 | s.pool.Run("fake", &input) |
1946 | - var res *HelperResult |
1947 | - select { |
1948 | - case res = <-ch: |
1949 | - case <-time.After(100 * time.Millisecond): |
1950 | - c.Fatal("timeout") |
1951 | - } |
1952 | + res := takeNext(ch, c) |
1953 | c.Check(res.Message, DeepEquals, input.Payload) |
1954 | c.Check(res.Notification, IsNil) |
1955 | c.Check(*res.Input, DeepEquals, input) |
1956 | } |
1957 | |
1958 | func (s *poolSuite) TestRunCantLaunch(c *C) { |
1959 | - fakeLauncher.err = cual.ErrCantLaunch |
1960 | + s.fakeLauncher.err = cual.ErrCantLaunch |
1961 | ch := s.pool.Start() |
1962 | defer s.pool.Stop() |
1963 | appId := "com.example.test_test-app" |
1964 | @@ -215,18 +221,9 @@ |
1965 | Payload: []byte(`"hello"`), |
1966 | } |
1967 | s.pool.Run("fake", &input) |
1968 | - select { |
1969 | - case arg := <-fakeLauncher.argCh: |
1970 | - c.Check(arg[:3], DeepEquals, []string{"Launch", helpId, "bar"}) |
1971 | - case <-time.After(100 * time.Millisecond): |
1972 | - c.Fatal("didn't call Launch") |
1973 | - } |
1974 | - var res *HelperResult |
1975 | - select { |
1976 | - case res = <-ch: |
1977 | - case <-time.After(100 * time.Millisecond): |
1978 | - c.Fatal("timeout") |
1979 | - } |
1980 | + launchArgs := s.waitForArgs(c, "Launch") |
1981 | + c.Check(launchArgs[:3], DeepEquals, []string{"Launch", helpId, "bar"}) |
1982 | + res := takeNext(ch, c) |
1983 | c.Check(res.Message, DeepEquals, input.Payload) |
1984 | 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") |
1985 | } |
1986 | @@ -244,27 +241,14 @@ |
1987 | Payload: []byte(`"hello"`), |
1988 | } |
1989 | s.pool.Run("fake", &input) |
1990 | - select { |
1991 | - case arg := <-fakeLauncher.argCh: |
1992 | - c.Check(arg[0], Equals, "Launch") |
1993 | - case <-time.After(100 * time.Millisecond): |
1994 | - c.Fatal("didn't call Launch") |
1995 | - } |
1996 | - select { |
1997 | - case arg := <-fakeLauncher.argCh: |
1998 | - c.Check(arg[:3], DeepEquals, []string{"Stop", helpId, "0"}) |
1999 | - case <-time.After(2 * time.Second): |
2000 | - c.Fatal("didn't call Stop") |
2001 | - } |
2002 | + launchArgs := s.waitForArgs(c, "Launch") |
2003 | + c.Check(launchArgs[0], Equals, "Launch") |
2004 | + stopArgs := s.waitForArgs(c, "Stop") |
2005 | + c.Check(stopArgs[:3], DeepEquals, []string{"Stop", helpId, "0"}) |
2006 | // this will be invoked |
2007 | - go fakeLauncher.done("0") |
2008 | + go s.fakeLauncher.done("0") |
2009 | |
2010 | - var res *HelperResult |
2011 | - select { |
2012 | - case res = <-ch: |
2013 | - case <-time.After(100 * time.Millisecond): |
2014 | - c.Fatal("timeout") |
2015 | - } |
2016 | + res := takeNext(ch, c) |
2017 | c.Check(res.Message, DeepEquals, input.Payload) |
2018 | } |
2019 | |
2020 | @@ -294,19 +278,14 @@ |
2021 | f, err := os.Create(args.FileOut) |
2022 | c.Assert(err, IsNil) |
2023 | defer f.Close() |
2024 | - _, err = f.Write([]byte(`{"notification": {"sound": "hello"}}`)) |
2025 | + _, err = f.Write([]byte(`{"notification": {"sound": "hello", "tag": "a-tag"}}`)) |
2026 | c.Assert(err, IsNil) |
2027 | |
2028 | go pool.OneDone("l:1") |
2029 | |
2030 | - var res *HelperResult |
2031 | - select { |
2032 | - case res = <-ch: |
2033 | - case <-time.After(100 * time.Millisecond): |
2034 | - c.Fatal("timeout") |
2035 | - } |
2036 | + res := takeNext(ch, c) |
2037 | |
2038 | - expected := HelperOutput{Notification: &Notification{Sound: "hello"}} |
2039 | + expected := HelperOutput{Notification: &Notification{Sound: "hello", Tag: "a-tag"}} |
2040 | c.Check(res.HelperOutput, DeepEquals, expected) |
2041 | c.Check(pool.hmap, HasLen, 0) |
2042 | } |
2043 | @@ -330,12 +309,7 @@ |
2044 | |
2045 | go pool.OneDone("l:1") |
2046 | |
2047 | - var res *HelperResult |
2048 | - select { |
2049 | - case res = <-ch: |
2050 | - case <-time.After(100 * time.Millisecond): |
2051 | - c.Fatal("timeout") |
2052 | - } |
2053 | + res := takeNext(ch, c) |
2054 | |
2055 | expected := HelperOutput{Message: args.Input.Payload} |
2056 | c.Check(res.HelperOutput, DeepEquals, expected) |
2057 | @@ -368,12 +342,7 @@ |
2058 | |
2059 | go pool.OneDone("l:1") |
2060 | |
2061 | - var res *HelperResult |
2062 | - select { |
2063 | - case res = <-ch: |
2064 | - case <-time.After(100 * time.Millisecond): |
2065 | - c.Fatal("timeout") |
2066 | - } |
2067 | + res := takeNext(ch, c) |
2068 | |
2069 | expected := HelperOutput{Message: args.Input.Payload} |
2070 | c.Check(res.HelperOutput, DeepEquals, expected) |
2071 | @@ -438,3 +407,191 @@ |
2072 | c.Check(err, IsNil) |
2073 | c.Check(dname, Equals, filepath.Join(tmpDir, "pkg.name")) |
2074 | } |
2075 | + |
2076 | +// checks that the a second helper run of an already-running helper |
2077 | +// (for an app) goes to the backlog |
2078 | +func (s *poolSuite) TestSecondRunSameAppToBacklog(c *C) { |
2079 | + ch := s.pool.Start() |
2080 | + defer s.pool.Stop() |
2081 | + |
2082 | + app1 := clickhelp.MustParseAppId("com.example.test_test-app-1") |
2083 | + input1 := &HelperInput{ |
2084 | + App: app1, |
2085 | + NotificationId: "foo1", |
2086 | + Payload: []byte(`"hello1"`), |
2087 | + } |
2088 | + app2 := clickhelp.MustParseAppId("com.example.test_test-app-1") |
2089 | + input2 := &HelperInput{ |
2090 | + App: app2, |
2091 | + NotificationId: "foo2", |
2092 | + Payload: []byte(`"hello2"`), |
2093 | + } |
2094 | + |
2095 | + c.Assert(app1.Base(), Equals, app2.Base()) |
2096 | + |
2097 | + s.pool.Run("fake", input1) |
2098 | + s.pool.Run("fake", input2) |
2099 | + |
2100 | + s.waitForArgs(c, "Launch") |
2101 | + go s.fakeLauncher.done("0") |
2102 | + takeNext(ch, c) |
2103 | + |
2104 | + // this is where we check that: |
2105 | + c.Check(s.log.Captured(), Matches, `(?ms).* helper input backlog has grown to 1 entries.$`) |
2106 | +} |
2107 | + |
2108 | +// checks that the an Nth helper run goes to the backlog |
2109 | +func (s *poolSuite) TestRunNthAppToBacklog(c *C) { |
2110 | + s.pool.(*kindHelperPool).maxNum = 2 |
2111 | + ch := s.pool.Start() |
2112 | + defer s.pool.Stop() |
2113 | + |
2114 | + app1 := clickhelp.MustParseAppId("com.example.test_test-app-1") |
2115 | + input1 := &HelperInput{ |
2116 | + App: app1, |
2117 | + NotificationId: "foo1", |
2118 | + Payload: []byte(`"hello1"`), |
2119 | + } |
2120 | + app2 := clickhelp.MustParseAppId("com.example.test_test-app-2") |
2121 | + input2 := &HelperInput{ |
2122 | + App: app2, |
2123 | + NotificationId: "foo2", |
2124 | + Payload: []byte(`"hello2"`), |
2125 | + } |
2126 | + app3 := clickhelp.MustParseAppId("com.example.test_test-app-3") |
2127 | + input3 := &HelperInput{ |
2128 | + App: app3, |
2129 | + NotificationId: "foo3", |
2130 | + Payload: []byte(`"hello3"`), |
2131 | + } |
2132 | + |
2133 | + s.pool.Run("fake", input1) |
2134 | + s.waitForArgs(c, "Launch") |
2135 | + |
2136 | + s.pool.Run("fake", input2) |
2137 | + s.log.ResetCapture() |
2138 | + s.waitForArgs(c, "Launch") |
2139 | + |
2140 | + s.pool.Run("fake", input3) |
2141 | + |
2142 | + go s.fakeLauncher.done("0") |
2143 | + s.waitForArgs(c, "Launch") |
2144 | + |
2145 | + res := takeNext(ch, c) |
2146 | + c.Assert(res, NotNil) |
2147 | + c.Assert(res.Input, NotNil) |
2148 | + c.Assert(res.Input.App, NotNil) |
2149 | + c.Assert(res.Input.App.Original(), Equals, "com.example.test_test-app-1") |
2150 | + go s.fakeLauncher.done("1") |
2151 | + go s.fakeLauncher.done("2") |
2152 | + takeNext(ch, c) |
2153 | + takeNext(ch, c) |
2154 | + |
2155 | + // this is the crux: we're checking that the third Run() went to the backlog. |
2156 | + c.Check(s.log.Captured(), Matches, |
2157 | + `(?ms).* helper input backlog has grown to 1 entries\.$.*shrunk to 0 entries\.$`) |
2158 | +} |
2159 | + |
2160 | +func (s *poolSuite) TestRunBacklogFailedContinuesDiffApp(c *C) { |
2161 | + s.pool.(*kindHelperPool).maxNum = 1 |
2162 | + ch := s.pool.Start() |
2163 | + defer s.pool.Stop() |
2164 | + |
2165 | + app1 := clickhelp.MustParseAppId("com.example.test_test-app-1") |
2166 | + input1 := &HelperInput{ |
2167 | + App: app1, |
2168 | + NotificationId: "foo1", |
2169 | + Payload: []byte(`"hello1"`), |
2170 | + } |
2171 | + app2 := clickhelp.MustParseAppId("com.example.test_test-app-2") |
2172 | + input2 := &HelperInput{ |
2173 | + App: app2, |
2174 | + NotificationId: "foo2", |
2175 | + Payload: []byte(`"hello2"`), |
2176 | + } |
2177 | + app3 := clickhelp.MustParseAppId("com.example.test_test-app-3") |
2178 | + input3 := &HelperInput{ |
2179 | + App: app3, |
2180 | + NotificationId: "foo3", |
2181 | + Payload: []byte(`"hello3"`), |
2182 | + } |
2183 | + app4 := clickhelp.MustParseAppId("com.example.test_test-app-4") |
2184 | + input4 := &HelperInput{ |
2185 | + App: app4, |
2186 | + NotificationId: "foo4", |
2187 | + Payload: []byte(`"hello4"`), |
2188 | + } |
2189 | + |
2190 | + s.pool.Run("fake", input1) |
2191 | + s.waitForArgs(c, "Launch") |
2192 | + s.pool.Run("NOT-THERE", input2) // this will fail |
2193 | + s.pool.Run("fake", input3) |
2194 | + s.pool.Run("fake", input4) |
2195 | + |
2196 | + go s.fakeLauncher.done("0") |
2197 | + // Everything up to here was just set-up. |
2198 | + // |
2199 | + // What we're checking for is that, if a helper launch fails, the |
2200 | + // next one in the backlog is picked up. |
2201 | + c.Assert(takeNext(ch, c).Input.App, Equals, app1) |
2202 | + c.Assert(takeNext(ch, c).Input.App, Equals, app2) |
2203 | + go s.fakeLauncher.done("2") |
2204 | + s.waitForArgs(c, "Launch") |
2205 | + c.Check(s.log.Captured(), Matches, |
2206 | + `(?ms).* helper input backlog has grown to 3 entries\.$.*shrunk to 1 entries\.$`) |
2207 | +} |
2208 | + |
2209 | +func (s *poolSuite) TestBigBacklogShrinks(c *C) { |
2210 | + s.pool.(*kindHelperPool).maxNum = 1 |
2211 | + ch := s.pool.Start() |
2212 | + defer s.pool.Stop() |
2213 | + numBad := 10 |
2214 | + |
2215 | + app := clickhelp.MustParseAppId("com.example.test_test-app") |
2216 | + s.pool.Run("fake", &HelperInput{App: app, NotificationId: "0", Payload: []byte(`""`)}) |
2217 | + |
2218 | + for i := 0; i < numBad; i++ { |
2219 | + s.pool.Run("NOT-THERE", &HelperInput{App: app}) |
2220 | + } |
2221 | + s.pool.Run("fake", &HelperInput{App: app, NotificationId: "1", Payload: []byte(`""`)}) |
2222 | + s.pool.Run("fake", &HelperInput{App: app, NotificationId: "2", Payload: []byte(`""`)}) |
2223 | + s.waitForArgs(c, "Launch") |
2224 | + go s.fakeLauncher.done("0") |
2225 | + // now we should get the fake + all the bad ones |
2226 | + for i := 0; i < numBad+1; i++ { |
2227 | + takeNext(ch, c) |
2228 | + } |
2229 | + s.waitForArgs(c, "Launch") |
2230 | + go s.fakeLauncher.done("1") |
2231 | + takeNext(ch, c) |
2232 | + // so now there's one good one "running", and one more waiting. |
2233 | + c.Check(s.log.Captured(), Matches, `(?ms).* shrunk to 1 entries\.$`) |
2234 | + // and the shrinker shrunk |
2235 | + c.Check(s.log.Captured(), Matches, `(?ms).*copying backlog to avoid wasting too much space .*`) |
2236 | +} |
2237 | + |
2238 | +func (s *poolSuite) TestBacklogShrinkerNilToNil(c *C) { |
2239 | + pool := s.pool.(*kindHelperPool) |
2240 | + c.Check(pool.shrinkBacklog(nil, 0), IsNil) |
2241 | +} |
2242 | + |
2243 | +func (s *poolSuite) TestBacklogShrinkerEmptyToNil(c *C) { |
2244 | + pool := s.pool.(*kindHelperPool) |
2245 | + empty := []*HelperInput{nil, nil, nil} |
2246 | + c.Check(pool.shrinkBacklog(empty, 0), IsNil) |
2247 | +} |
2248 | + |
2249 | +func (s *poolSuite) TestBacklogShrinkerFullUntouched(c *C) { |
2250 | + pool := s.pool.(*kindHelperPool) |
2251 | + input := &HelperInput{} |
2252 | + full := []*HelperInput{input, input, input} |
2253 | + c.Check(pool.shrinkBacklog(full, 3), DeepEquals, full) |
2254 | +} |
2255 | + |
2256 | +func (s *poolSuite) TestBacklogShrinkerSparseShrunk(c *C) { |
2257 | + pool := s.pool.(*kindHelperPool) |
2258 | + input := &HelperInput{} |
2259 | + sparse := []*HelperInput{nil, input, nil, input, nil} |
2260 | + full := []*HelperInput{input, input} |
2261 | + c.Check(pool.shrinkBacklog(sparse, 2), DeepEquals, full) |
2262 | +} |
2263 | |
2264 | === modified file 'messaging/cmessaging/cmessaging.go' |
2265 | --- messaging/cmessaging/cmessaging.go 2014-07-16 16:10:03 +0000 |
2266 | +++ messaging/cmessaging/cmessaging.go 2014-07-29 16:56:22 +0000 |
2267 | @@ -25,11 +25,16 @@ |
2268 | void add_notification(const gchar* desktop_id, const gchar* notification_id, |
2269 | const gchar* icon_path, const gchar* summary, const gchar* body, |
2270 | gint64 timestamp, const gchar** actions, gpointer obj); |
2271 | + |
2272 | +void remove_notification(const gchar* desktop_id, const gchar* notification_id); |
2273 | + |
2274 | +gboolean notification_exists(const gchar* desktop_id, const gchar* notification_id); |
2275 | */ |
2276 | import "C" |
2277 | import "unsafe" |
2278 | |
2279 | import ( |
2280 | + "launchpad.net/ubuntu-push/click" |
2281 | "launchpad.net/ubuntu-push/launch_helper" |
2282 | "launchpad.net/ubuntu-push/messaging/reply" |
2283 | ) |
2284 | @@ -37,6 +42,9 @@ |
2285 | type Payload struct { |
2286 | Ch chan *reply.MMActionReply |
2287 | Actions []string |
2288 | + App *click.AppId |
2289 | + Tag string |
2290 | + Gone bool |
2291 | } |
2292 | |
2293 | func gchar(s string) *C.gchar { |
2294 | @@ -56,7 +64,7 @@ |
2295 | if action == "" && len(payload.Actions) >= 2 { |
2296 | action = payload.Actions[1] |
2297 | } |
2298 | - mmar := &reply.MMActionReply{Notification: C.GoString(c_notification), Action: action} |
2299 | + mmar := &reply.MMActionReply{Notification: C.GoString(c_notification), Action: action, App: payload.App} |
2300 | payload.Ch <- mmar |
2301 | } |
2302 | |
2303 | @@ -81,6 +89,26 @@ |
2304 | C.add_notification(desktop_id, notification_id, icon_path, summary, body, timestamp, nil, (C.gpointer)(payload)) |
2305 | } |
2306 | |
2307 | +func RemoveNotification(desktopId string, notificationId string) { |
2308 | + desktop_id := gchar(desktopId) |
2309 | + defer gfree(desktop_id) |
2310 | + |
2311 | + notification_id := gchar(notificationId) |
2312 | + defer gfree(notification_id) |
2313 | + |
2314 | + C.remove_notification(desktop_id, notification_id) |
2315 | +} |
2316 | + |
2317 | +func NotificationExists(desktopId string, notificationId string) bool { |
2318 | + notification_id := gchar(notificationId) |
2319 | + defer gfree(notification_id) |
2320 | + |
2321 | + desktop_id := gchar(desktopId) |
2322 | + defer gfree(desktop_id) |
2323 | + |
2324 | + return C.notification_exists(desktop_id, notification_id) == C.TRUE |
2325 | +} |
2326 | + |
2327 | func init() { |
2328 | go C.g_main_loop_run(C.g_main_loop_new(nil, C.FALSE)) |
2329 | } |
2330 | |
2331 | === modified file 'messaging/cmessaging/cmessaging_c.go' |
2332 | --- messaging/cmessaging/cmessaging_c.go 2014-07-16 13:59:39 +0000 |
2333 | +++ messaging/cmessaging/cmessaging_c.go 2014-07-29 16:56:22 +0000 |
2334 | @@ -25,10 +25,11 @@ |
2335 | handleActivate(action, messaging_menu_message_get_id(msg), obj); |
2336 | } |
2337 | |
2338 | +static GHashTable* map = NULL; |
2339 | + |
2340 | void add_notification (const gchar* desktop_id, const gchar* notification_id, |
2341 | const gchar* icon_path, const gchar* summary, const gchar* body, |
2342 | gint64 timestamp, const gchar** actions, gpointer obj) { |
2343 | - static GHashTable* map = NULL; |
2344 | if (map == NULL) { |
2345 | map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); |
2346 | } |
2347 | @@ -54,5 +55,34 @@ |
2348 | g_signal_connect(msg, "activate", G_CALLBACK(activate_cb), obj); |
2349 | g_object_unref(msg); |
2350 | } |
2351 | + |
2352 | +void remove_notification (const gchar* desktop_id, const gchar* notification_id) { |
2353 | + if (map == NULL) { |
2354 | + return; |
2355 | + } |
2356 | + MessagingMenuApp* app = g_hash_table_lookup (map, desktop_id); |
2357 | + if (app == NULL) { |
2358 | + // no app in the hash table, bailout |
2359 | + return; |
2360 | + } |
2361 | + messaging_menu_app_remove_message_by_id (app, notification_id); |
2362 | +} |
2363 | + |
2364 | +gboolean notification_exists (const gchar* desktop_id, const gchar* notification_id) { |
2365 | + if (map == NULL) { |
2366 | + return FALSE; |
2367 | + } |
2368 | + MessagingMenuApp* app = g_hash_table_lookup (map, desktop_id); |
2369 | + if (app == NULL) { |
2370 | + // no app in the hash table, bailout |
2371 | + return FALSE; |
2372 | + } |
2373 | + MessagingMenuMessage* msg = messaging_menu_app_get_message(app, notification_id); |
2374 | + if (msg != NULL) { |
2375 | + // the notification is still there |
2376 | + return TRUE; |
2377 | + } |
2378 | + return FALSE; |
2379 | +} |
2380 | */ |
2381 | import "C" |
2382 | |
2383 | === modified file 'messaging/messaging.go' |
2384 | --- messaging/messaging.go 2014-07-16 17:51:06 +0000 |
2385 | +++ messaging/messaging.go 2014-07-29 16:56:22 +0000 |
2386 | @@ -21,6 +21,7 @@ |
2387 | import ( |
2388 | "encoding/json" |
2389 | "sync" |
2390 | + "time" |
2391 | |
2392 | "launchpad.net/ubuntu-push/bus/notifications" |
2393 | "launchpad.net/ubuntu-push/click" |
2394 | @@ -30,56 +31,166 @@ |
2395 | "launchpad.net/ubuntu-push/messaging/reply" |
2396 | ) |
2397 | |
2398 | +var cleanupLoopDuration = 5 * time.Minute |
2399 | + |
2400 | type MessagingMenu struct { |
2401 | - Log logger.Logger |
2402 | - Ch chan *reply.MMActionReply |
2403 | - notifications map[string]*cmessaging.Payload |
2404 | - lock sync.RWMutex |
2405 | + Log logger.Logger |
2406 | + Ch chan *reply.MMActionReply |
2407 | + notifications map[string]*cmessaging.Payload // keep a ref to the Payload used in the MMU callback |
2408 | + lock sync.RWMutex |
2409 | + stopCleanupLoopCh chan bool |
2410 | + ticker *time.Ticker |
2411 | + tickerCh <-chan time.Time |
2412 | } |
2413 | |
2414 | // New returns a new MessagingMenu |
2415 | func New(log logger.Logger) *MessagingMenu { |
2416 | - return &MessagingMenu{Log: log, Ch: make(chan *reply.MMActionReply), notifications: make(map[string]*cmessaging.Payload)} |
2417 | + ticker := time.NewTicker(cleanupLoopDuration) |
2418 | + stopCh := make(chan bool) |
2419 | + return &MessagingMenu{Log: log, Ch: make(chan *reply.MMActionReply), notifications: make(map[string]*cmessaging.Payload), ticker: ticker, tickerCh: ticker.C, stopCleanupLoopCh: stopCh} |
2420 | } |
2421 | |
2422 | var cAddNotification = cmessaging.AddNotification |
2423 | - |
2424 | -func (mmu *MessagingMenu) addNotification(desktopId string, notificationId string, card *launch_helper.Card, actions []string) { |
2425 | - payload := &cmessaging.Payload{Ch: mmu.Ch, Actions: actions} |
2426 | +var cRemoveNotification = cmessaging.RemoveNotification |
2427 | +var cNotificationExists = cmessaging.NotificationExists |
2428 | + |
2429 | +// GetCh returns the reply channel, exactly like mm.Ch. |
2430 | +func (mmu *MessagingMenu) GetCh() chan *reply.MMActionReply { |
2431 | + return mmu.Ch |
2432 | +} |
2433 | + |
2434 | +func (mmu *MessagingMenu) addNotification(app *click.AppId, notificationId string, tag string, card *launch_helper.Card, actions []string) { |
2435 | mmu.lock.Lock() |
2436 | - // XXX: only gets removed if the action is activated. |
2437 | + defer mmu.lock.Unlock() |
2438 | + payload := &cmessaging.Payload{Ch: mmu.Ch, Actions: actions, App: app, Tag: tag} |
2439 | mmu.notifications[notificationId] = payload |
2440 | - mmu.lock.Unlock() |
2441 | - cAddNotification(desktopId, notificationId, card, payload) |
2442 | + cAddNotification(app.DesktopId(), notificationId, card, payload) |
2443 | } |
2444 | |
2445 | -// RemoveNotification deletes the notification from internal map |
2446 | -func (mmu *MessagingMenu) RemoveNotification(notificationId string) { |
2447 | +func (mmu *MessagingMenu) RemoveNotification(notificationId string, fromUI bool) { |
2448 | mmu.lock.Lock() |
2449 | defer mmu.lock.Unlock() |
2450 | + payload := mmu.notifications[notificationId] |
2451 | delete(mmu.notifications, notificationId) |
2452 | -} |
2453 | - |
2454 | -func (mmu *MessagingMenu) Present(app *click.AppId, notificationId string, notification *launch_helper.Notification) { |
2455 | - if notification == nil || notification.Card == nil || !notification.Card.Persist || notification.Card.Summary == "" { |
2456 | - mmu.Log.Debugf("[%s] no notification or notification has no persistable card: %#v", notificationId, notification) |
2457 | - return |
2458 | - } |
2459 | - actions := make([]string, 2*len(notification.Card.Actions)) |
2460 | - for i, action := range notification.Card.Actions { |
2461 | + if payload != nil && payload.App != nil && fromUI { |
2462 | + cRemoveNotification(payload.App.DesktopId(), notificationId) |
2463 | + } |
2464 | +} |
2465 | + |
2466 | +// cleanupNotifications remove notifications that were cleared from the messaging menu |
2467 | +func (mmu *MessagingMenu) cleanUpNotifications() { |
2468 | + mmu.lock.Lock() |
2469 | + defer mmu.lock.Unlock() |
2470 | + for nid, payload := range mmu.notifications { |
2471 | + if payload.Gone { |
2472 | + // sweep |
2473 | + delete(mmu.notifications, nid) |
2474 | + // don't check the mmu for this nid |
2475 | + continue |
2476 | + } |
2477 | + exists := cNotificationExists(payload.App.DesktopId(), nid) |
2478 | + if !exists { |
2479 | + // mark |
2480 | + payload.Gone = true |
2481 | + } |
2482 | + } |
2483 | +} |
2484 | + |
2485 | +func (mmu *MessagingMenu) StartCleanupLoop() { |
2486 | + mmu.doStartCleanupLoop(mmu.cleanUpNotifications) |
2487 | +} |
2488 | + |
2489 | +func (mmu *MessagingMenu) doStartCleanupLoop(cleanupFunc func()) { |
2490 | + go func() { |
2491 | + for { |
2492 | + select { |
2493 | + case <-mmu.tickerCh: |
2494 | + cleanupFunc() |
2495 | + case <-mmu.stopCleanupLoopCh: |
2496 | + mmu.ticker.Stop() |
2497 | + mmu.Log.Debugf("CleanupLoop stopped.") |
2498 | + return |
2499 | + } |
2500 | + } |
2501 | + }() |
2502 | +} |
2503 | + |
2504 | +func (mmu *MessagingMenu) StopCleanupLoop() { |
2505 | + mmu.stopCleanupLoopCh <- true |
2506 | +} |
2507 | + |
2508 | +func (mmu *MessagingMenu) Tags(app *click.AppId) []string { |
2509 | + orig := app.Original() |
2510 | + tags := []string(nil) |
2511 | + mmu.lock.RLock() |
2512 | + defer mmu.lock.RUnlock() |
2513 | + for _, payload := range mmu.notifications { |
2514 | + if payload.App.Original() == orig { |
2515 | + tags = append(tags, payload.Tag) |
2516 | + } |
2517 | + } |
2518 | + return tags |
2519 | +} |
2520 | + |
2521 | +func (mmu *MessagingMenu) Clear(app *click.AppId, tags ...string) int { |
2522 | + orig := app.Original() |
2523 | + var nids []string |
2524 | + |
2525 | + mmu.lock.RLock() |
2526 | + // O(n×m). Should be small n and m though. |
2527 | + for nid, payload := range mmu.notifications { |
2528 | + if payload.App.Original() == orig { |
2529 | + if len(tags) == 0 { |
2530 | + nids = append(nids, nid) |
2531 | + } else { |
2532 | + for _, tag := range tags { |
2533 | + if payload.Tag == tag { |
2534 | + nids = append(nids, nid) |
2535 | + } |
2536 | + } |
2537 | + } |
2538 | + } |
2539 | + } |
2540 | + mmu.lock.RUnlock() |
2541 | + |
2542 | + for _, nid := range nids { |
2543 | + mmu.RemoveNotification(nid, true) |
2544 | + } |
2545 | + |
2546 | + return len(nids) |
2547 | +} |
2548 | + |
2549 | +func (mmu *MessagingMenu) Present(app *click.AppId, nid string, notification *launch_helper.Notification) bool { |
2550 | + if notification == nil { |
2551 | + panic("please check notification is not nil before calling present") |
2552 | + } |
2553 | + |
2554 | + card := notification.Card |
2555 | + |
2556 | + if card == nil || !card.Persist || card.Summary == "" { |
2557 | + mmu.Log.Debugf("[%s] notification has no persistable card: %#v", nid, card) |
2558 | + return false |
2559 | + } |
2560 | + |
2561 | + actions := make([]string, 2*len(card.Actions)) |
2562 | + for i, action := range card.Actions { |
2563 | act, err := json.Marshal(¬ifications.RawAction{ |
2564 | App: app, |
2565 | - Nid: notificationId, |
2566 | + Nid: nid, |
2567 | ActionId: i, |
2568 | Action: action, |
2569 | }) |
2570 | if err != nil { |
2571 | mmu.Log.Errorf("Failed to build action: %s", action) |
2572 | - return |
2573 | + return false |
2574 | } |
2575 | actions[2*i] = string(act) |
2576 | actions[2*i+1] = action |
2577 | } |
2578 | |
2579 | - mmu.addNotification(app.DesktopId(), notificationId, notification.Card, actions) |
2580 | + mmu.Log.Debugf("[%s] creating notification centre entry for %s (summary: %s)", nid, app.Base(), card.Summary) |
2581 | + |
2582 | + mmu.addNotification(app, nid, notification.Tag, card, actions) |
2583 | + |
2584 | + return true |
2585 | } |
2586 | |
2587 | === modified file 'messaging/messaging_test.go' |
2588 | --- messaging/messaging_test.go 2014-07-16 17:54:32 +0000 |
2589 | +++ messaging/messaging_test.go 2014-07-29 16:56:22 +0000 |
2590 | @@ -17,6 +17,8 @@ |
2591 | package messaging |
2592 | |
2593 | import ( |
2594 | + "time" |
2595 | + |
2596 | . "launchpad.net/gocheck" |
2597 | "testing" |
2598 | |
2599 | @@ -24,6 +26,7 @@ |
2600 | clickhelp "launchpad.net/ubuntu-push/click/testing" |
2601 | "launchpad.net/ubuntu-push/launch_helper" |
2602 | "launchpad.net/ubuntu-push/messaging/cmessaging" |
2603 | + "launchpad.net/ubuntu-push/messaging/reply" |
2604 | helpers "launchpad.net/ubuntu-push/testing" |
2605 | ) |
2606 | |
2607 | @@ -41,6 +44,17 @@ |
2608 | cAddNotification = func(a string, n string, c *launch_helper.Card, payload *cmessaging.Payload) { |
2609 | ms.log.Debugf("ADD: app: %s, not: %s, card: %v, chan: %v", a, n, c, payload) |
2610 | } |
2611 | + cRemoveNotification = func(a, n string) { |
2612 | + ms.log.Debugf("REMOVE: app: %s, not: %s", a, n) |
2613 | + } |
2614 | + // just in case |
2615 | + cNotificationExists = nil |
2616 | +} |
2617 | + |
2618 | +func (ms *MessagingSuite) TearDownSuite(c *C) { |
2619 | + cAddNotification = cmessaging.AddNotification |
2620 | + cRemoveNotification = cmessaging.RemoveNotification |
2621 | + cNotificationExists = cmessaging.NotificationExists |
2622 | } |
2623 | |
2624 | func (ms *MessagingSuite) SetUpTest(c *C) { |
2625 | @@ -53,7 +67,7 @@ |
2626 | card := launch_helper.Card{Summary: "ehlo", Persist: true} |
2627 | notif := launch_helper.Notification{Card: &card} |
2628 | |
2629 | - mmu.Present(ms.app, "notif-id", ¬if) |
2630 | + c.Check(mmu.Present(ms.app, "notif-id", ¬if), Equals, true) |
2631 | |
2632 | c.Check(ms.log.Captured(), Matches, `(?s).* ADD:.*notif-id.*`) |
2633 | } |
2634 | @@ -63,7 +77,7 @@ |
2635 | card := launch_helper.Card{Persist: true} |
2636 | notif := launch_helper.Notification{Card: &card} |
2637 | |
2638 | - mmu.Present(ms.app, "notif-id", ¬if) |
2639 | + c.Check(mmu.Present(ms.app, "notif-id", ¬if), Equals, false) |
2640 | |
2641 | c.Check(ms.log.Captured(), Matches, "(?sm).*has no persistable card.*") |
2642 | } |
2643 | @@ -73,54 +87,248 @@ |
2644 | card := launch_helper.Card{Summary: "ehlo"} |
2645 | notif := launch_helper.Notification{Card: &card} |
2646 | |
2647 | - mmu.Present(ms.app, "notif-id", ¬if) |
2648 | + c.Check(mmu.Present(ms.app, "notif-id", ¬if), Equals, false) |
2649 | |
2650 | c.Check(ms.log.Captured(), Matches, "(?sm).*has no persistable card.*") |
2651 | } |
2652 | |
2653 | -func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNil(c *C) { |
2654 | +func (ms *MessagingSuite) TestPresentPanicsIfNil(c *C) { |
2655 | mmu := New(ms.log) |
2656 | - mmu.Present(ms.app, "notif-id", nil) |
2657 | - c.Check(ms.log.Captured(), Matches, "(?sm).*no notification.*") |
2658 | + c.Check(func() { mmu.Present(ms.app, "notif-id", nil) }, Panics, `please check notification is not nil before calling present`) |
2659 | } |
2660 | |
2661 | func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNilCard(c *C) { |
2662 | mmu := New(ms.log) |
2663 | - mmu.Present(ms.app, "notif-id", &launch_helper.Notification{}) |
2664 | - c.Check(ms.log.Captured(), Matches, "(?sm).*no notification.*") |
2665 | + c.Check(mmu.Present(ms.app, "notif-id", &launch_helper.Notification{}), Equals, false) |
2666 | + c.Check(ms.log.Captured(), Matches, "(?sm).*no persistable card.*") |
2667 | } |
2668 | |
2669 | func (ms *MessagingSuite) TestPresentWithActions(c *C) { |
2670 | mmu := New(ms.log) |
2671 | card := launch_helper.Card{Summary: "ehlo", Persist: true, Actions: []string{"action-1"}} |
2672 | - notif := launch_helper.Notification{Card: &card} |
2673 | + notif := launch_helper.Notification{Card: &card, Tag: "a-tag"} |
2674 | |
2675 | - mmu.Present(ms.app, "notif-id", ¬if) |
2676 | + c.Check(mmu.Present(ms.app, "notif-id", ¬if), Equals, true) |
2677 | |
2678 | c.Check(ms.log.Captured(), Matches, `(?s).* ADD:.*notif-id.*`) |
2679 | |
2680 | payload, _ := mmu.notifications["notif-id"] |
2681 | c.Check(payload.Ch, Equals, mmu.Ch) |
2682 | c.Check(len(payload.Actions), Equals, 2) |
2683 | + c.Check(payload.Tag, Equals, "a-tag") |
2684 | rawAction := "{\"app\":\"com.example.test_test_0\",\"act\":\"action-1\",\"nid\":\"notif-id\"}" |
2685 | c.Check(payload.Actions[0], Equals, rawAction) |
2686 | c.Check(payload.Actions[1], Equals, "action-1") |
2687 | } |
2688 | |
2689 | +func (ms *MessagingSuite) TestTagsListsTags(c *C) { |
2690 | + mmu := New(ms.log) |
2691 | + f := func(s string) *launch_helper.Notification { |
2692 | + card := launch_helper.Card{Summary: "tag: \"" + s + "\"", Persist: true} |
2693 | + return &launch_helper.Notification{Card: &card, Tag: s} |
2694 | + } |
2695 | + |
2696 | + c.Check(mmu.Tags(ms.app), IsNil) |
2697 | + c.Assert(mmu.Present(ms.app, "notif1", f("one")), Equals, true) |
2698 | + c.Check(mmu.Tags(ms.app), DeepEquals, []string{"one"}) |
2699 | + c.Assert(mmu.Present(ms.app, "notif2", f("")), Equals, true) |
2700 | + c.Check(mmu.Tags(ms.app), DeepEquals, []string{"one", ""}) |
2701 | + // and an empty notification doesn't count |
2702 | + c.Assert(mmu.Present(ms.app, "notif3", &launch_helper.Notification{Tag: "X"}), Equals, false) |
2703 | + c.Check(mmu.Tags(ms.app), DeepEquals, []string{"one", ""}) |
2704 | + // and they go away if we remove one |
2705 | + mmu.RemoveNotification("notif1", false) |
2706 | + c.Check(mmu.Tags(ms.app), DeepEquals, []string{""}) |
2707 | + mmu.RemoveNotification("notif2", false) |
2708 | + c.Check(mmu.Tags(ms.app), IsNil) |
2709 | +} |
2710 | + |
2711 | +func (ms *MessagingSuite) TestClearClears(c *C) { |
2712 | + app1 := ms.app |
2713 | + app2 := clickhelp.MustParseAppId("com.example.test_test-2_0") |
2714 | + app3 := clickhelp.MustParseAppId("com.example.test_test-3_0") |
2715 | + mm := New(ms.log) |
2716 | + f := func(app *click.AppId, nid string, tag string, withCard bool) bool { |
2717 | + notif := launch_helper.Notification{Tag: tag} |
2718 | + card := launch_helper.Card{Summary: "tag: \"" + tag + "\"", Persist: true} |
2719 | + if withCard { |
2720 | + notif.Card = &card |
2721 | + } |
2722 | + return mm.Present(app, nid, ¬if) |
2723 | + } |
2724 | + // create a bunch |
2725 | + c.Assert(f(app1, "notif1", "one", true), Equals, true) |
2726 | + c.Assert(f(app1, "notif2", "two", true), Equals, true) |
2727 | + c.Assert(f(app1, "notif3", "", true), Equals, true) |
2728 | + c.Assert(f(app2, "notif4", "one", true), Equals, true) |
2729 | + c.Assert(f(app2, "notif5", "two", true), Equals, true) |
2730 | + c.Assert(f(app3, "notif6", "one", true), Equals, true) |
2731 | + c.Assert(f(app3, "notif7", "", true), Equals, true) |
2732 | + |
2733 | + // that is: |
2734 | + // app 1: "one", "two", ""; |
2735 | + // app 2: "one", "two"; |
2736 | + // app 3: "one", "" |
2737 | + c.Assert(mm.Tags(app1), DeepEquals, []string{"one", "two", ""}) |
2738 | + c.Assert(mm.Tags(app2), DeepEquals, []string{"one", "two"}) |
2739 | + c.Assert(mm.Tags(app3), DeepEquals, []string{"one", ""}) |
2740 | + |
2741 | + // clearing a non-existent tag does nothing |
2742 | + c.Check(mm.Clear(app1, "foo"), Equals, 0) |
2743 | + c.Check(mm.Tags(app1), HasLen, 3) |
2744 | + c.Check(mm.Tags(app2), HasLen, 2) |
2745 | + c.Check(mm.Tags(app3), HasLen, 2) |
2746 | + |
2747 | + // asking to clear a tag that exists only for another app does nothing |
2748 | + c.Check(mm.Clear(app3, "two"), Equals, 0) |
2749 | + c.Check(mm.Tags(app1), HasLen, 3) |
2750 | + c.Check(mm.Tags(app2), HasLen, 2) |
2751 | + c.Check(mm.Tags(app3), HasLen, 2) |
2752 | + |
2753 | + // asking to clear a list of tags, only one of which is yours, only clears yours |
2754 | + c.Check(mm.Clear(app3, "one", "two"), Equals, 1) |
2755 | + c.Check(mm.Tags(app1), HasLen, 3) |
2756 | + c.Check(mm.Tags(app2), HasLen, 2) |
2757 | + c.Check(mm.Tags(app3), HasLen, 1) |
2758 | + |
2759 | + // clearing with no args just empties it |
2760 | + c.Check(mm.Clear(app1), Equals, 3) |
2761 | + c.Check(mm.Tags(app1), IsNil) |
2762 | + c.Check(mm.Tags(app2), HasLen, 2) |
2763 | + c.Check(mm.Tags(app3), HasLen, 1) |
2764 | + |
2765 | + // asking to clear all the tags from an already tagless app does nothing |
2766 | + c.Check(mm.Clear(app1), Equals, 0) |
2767 | + c.Check(mm.Tags(app1), IsNil) |
2768 | + c.Check(mm.Tags(app2), HasLen, 2) |
2769 | + c.Check(mm.Tags(app3), HasLen, 1) |
2770 | + |
2771 | + // check we work ok with a "" tag, too. |
2772 | + c.Check(mm.Clear(app1, ""), Equals, 0) |
2773 | + c.Check(mm.Clear(app2, ""), Equals, 0) |
2774 | + c.Check(mm.Clear(app3, ""), Equals, 1) |
2775 | + c.Check(mm.Tags(app1), IsNil) |
2776 | + c.Check(mm.Tags(app2), HasLen, 2) |
2777 | + c.Check(mm.Tags(app3), HasLen, 0) |
2778 | +} |
2779 | + |
2780 | func (ms *MessagingSuite) TestRemoveNotification(c *C) { |
2781 | mmu := New(ms.log) |
2782 | card := launch_helper.Card{Summary: "ehlo", Persist: true, Actions: []string{"action-1"}} |
2783 | actions := []string{"{\"app\":\"com.example.test_test_0\",\"act\":\"action-1\",\"nid\":\"notif-id\"}", "action-1"} |
2784 | - mmu.addNotification(ms.app.DesktopId(), "notif-id", &card, actions) |
2785 | + mmu.addNotification(ms.app, "notif-id", "a-tag", &card, actions) |
2786 | |
2787 | // check it's there |
2788 | payload, ok := mmu.notifications["notif-id"] |
2789 | c.Check(ok, Equals, true) |
2790 | c.Check(payload.Actions, DeepEquals, actions) |
2791 | + c.Check(payload.Tag, Equals, "a-tag") |
2792 | c.Check(payload.Ch, Equals, mmu.Ch) |
2793 | + // remove the notification (internal only) |
2794 | + mmu.RemoveNotification("notif-id", false) |
2795 | + // check it's gone |
2796 | + _, ok = mmu.notifications["notif-id"] |
2797 | + c.Check(ok, Equals, false) |
2798 | +} |
2799 | + |
2800 | +func (ms *MessagingSuite) TestRemoveNotificationsFromUI(c *C) { |
2801 | + mmu := New(ms.log) |
2802 | + card := launch_helper.Card{Summary: "ehlo", Persist: true, Actions: []string{"action-1"}} |
2803 | + actions := []string{"{\"app\":\"com.example.test_test_0\",\"act\":\"action-1\",\"nid\":\"notif-id\"}", "action-1"} |
2804 | + mmu.addNotification(ms.app, "notif-id", "a-tag", &card, actions) |
2805 | + |
2806 | + // check it's there |
2807 | + _, ok := mmu.notifications["notif-id"] |
2808 | + c.Assert(ok, Equals, true) |
2809 | + // remove the notification (both internal and from UI) |
2810 | + mmu.RemoveNotification("notif-id", true) |
2811 | + // check it's gone |
2812 | + _, ok = mmu.notifications["notif-id"] |
2813 | + c.Check(ok, Equals, false) |
2814 | + |
2815 | + // and check it's been removed from the UI too |
2816 | + c.Check(ms.log.Captured(), Matches, `(?s).* REMOVE:.*notif-id.*`) |
2817 | +} |
2818 | + |
2819 | +func (ms *MessagingSuite) TestCleanupStaleNotification(c *C) { |
2820 | + mmu := New(ms.log) |
2821 | + card := launch_helper.Card{Summary: "ehlo", Persist: true, Actions: []string{"action-1"}} |
2822 | + actions := []string{"{\"app\":\"com.example.test_test_0\",\"act\":\"action-1\",\"nid\":\"notif-id\"}", "action-1"} |
2823 | + mmu.addNotification(ms.app, "notif-id", "", &card, actions) |
2824 | + |
2825 | + // check it's there |
2826 | + _, ok := mmu.notifications["notif-id"] |
2827 | + c.Check(ok, Equals, true) |
2828 | + |
2829 | + // patch cnotificationexists to return true |
2830 | + cNotificationExists = func(did string, nid string) bool { |
2831 | + return true |
2832 | + } |
2833 | // remove the notification |
2834 | - mmu.RemoveNotification("notif-id") |
2835 | + mmu.cleanUpNotifications() |
2836 | + // check it's still there |
2837 | + _, ok = mmu.notifications["notif-id"] |
2838 | + c.Check(ok, Equals, true) |
2839 | + // patch cnotificationexists to return false |
2840 | + cNotificationExists = func(did string, nid string) bool { |
2841 | + return false |
2842 | + } |
2843 | + // mark the notification |
2844 | + mmu.cleanUpNotifications() |
2845 | + // check it's gone |
2846 | + _, ok = mmu.notifications["notif-id"] |
2847 | + c.Check(ok, Equals, true) |
2848 | + // sweep the notification |
2849 | + mmu.cleanUpNotifications() |
2850 | // check it's gone |
2851 | _, ok = mmu.notifications["notif-id"] |
2852 | c.Check(ok, Equals, false) |
2853 | } |
2854 | + |
2855 | +func (ms *MessagingSuite) TestCleanupLoop(c *C) { |
2856 | + mmu := New(ms.log) |
2857 | + tickerCh := make(chan time.Time) |
2858 | + mmu.tickerCh = tickerCh |
2859 | + cleanupCh := make(chan bool) |
2860 | + cleanupFunc := func() { |
2861 | + cleanupCh <- true |
2862 | + } |
2863 | + // start the cleanup loop |
2864 | + mmu.doStartCleanupLoop(cleanupFunc) |
2865 | + // mark |
2866 | + tickerCh <- time.Now() |
2867 | + // check it was called |
2868 | + <-cleanupCh |
2869 | + // stop the loop and check that it's actually stopped. |
2870 | + mmu.StopCleanupLoop() |
2871 | + c.Check(ms.log.Captured(), Matches, "(?s).*DEBUG CleanupLoop stopped.*") |
2872 | +} |
2873 | + |
2874 | +func (ms *MessagingSuite) TestStartCleanupLoop(c *C) { |
2875 | + mmu := New(ms.log) |
2876 | + tickerCh := make(chan time.Time) |
2877 | + mmu.tickerCh = tickerCh |
2878 | + card := launch_helper.Card{Summary: "ehlo", Persist: true, Actions: []string{"action-1"}} |
2879 | + actions := []string{"{\"app\":\"com.example.test_test_0\",\"act\":\"action-1\",\"nid\":\"notif-id\"}", "action-1"} |
2880 | + mmu.addNotification(ms.app, "notif-id", "", &card, actions) |
2881 | + // patch cnotificationexists to return true and signal when it's called |
2882 | + notifExistsCh := make(chan bool) |
2883 | + cNotificationExists = func(did string, nid string) bool { |
2884 | + notifExistsCh <- true |
2885 | + return true |
2886 | + } |
2887 | + // statr the cleanup loop |
2888 | + mmu.StartCleanupLoop() |
2889 | + // mark |
2890 | + tickerCh <- time.Now() |
2891 | + // check it's there, and marked |
2892 | + <-notifExistsCh |
2893 | + // stop the loop |
2894 | + mmu.StopCleanupLoop() |
2895 | +} |
2896 | + |
2897 | +func (ms *MessagingSuite) TestGetCh(c *C) { |
2898 | + mmu := New(ms.log) |
2899 | + mmu.Ch = make(chan *reply.MMActionReply) |
2900 | + c.Check(mmu.GetCh(), Equals, mmu.Ch) |
2901 | +} |
2902 | |
2903 | === modified file 'messaging/reply/reply.go' |
2904 | --- messaging/reply/reply.go 2014-07-16 16:10:03 +0000 |
2905 | +++ messaging/reply/reply.go 2014-07-29 16:56:22 +0000 |
2906 | @@ -18,8 +18,11 @@ |
2907 | // messaging and cmessaging without going circular about it |
2908 | package reply |
2909 | |
2910 | +import "launchpad.net/ubuntu-push/click" |
2911 | + |
2912 | // MMActionReply holds the reply from a MessagingMenu action |
2913 | type MMActionReply struct { |
2914 | Notification string |
2915 | Action string |
2916 | + App *click.AppId |
2917 | } |
2918 | |
2919 | === modified file 'sounds/sounds.go' |
2920 | --- sounds/sounds.go 2014-07-09 00:13:19 +0000 |
2921 | +++ sounds/sounds.go 2014-07-29 16:56:22 +0000 |
2922 | @@ -19,7 +19,7 @@ |
2923 | import ( |
2924 | "os" |
2925 | "os/exec" |
2926 | - "path" |
2927 | + "path/filepath" |
2928 | |
2929 | "launchpad.net/go-xdg/v0" |
2930 | |
2931 | @@ -40,8 +40,12 @@ |
2932 | } |
2933 | |
2934 | func (snd *Sound) Present(app *click.AppId, nid string, notification *launch_helper.Notification) bool { |
2935 | - if notification == nil || notification.Sound == "" { |
2936 | - snd.log.Debugf("[%s] no notification or no Sound in the notification; doing nothing: %#v", nid, notification) |
2937 | + if notification == nil { |
2938 | + panic("please check notification is not nil before calling present") |
2939 | + } |
2940 | + |
2941 | + if notification.Sound == "" { |
2942 | + snd.log.Debugf("[%s] notification has no Sound: %#v", nid, notification.Sound) |
2943 | return false |
2944 | } |
2945 | absPath := snd.findSoundFile(app, nid, notification.Sound) |
2946 | @@ -68,7 +72,7 @@ |
2947 | func (snd *Sound) findSoundFile(app *click.AppId, nid string, sound string) string { |
2948 | // XXX also support legacy appIds? |
2949 | // first, check package-specific |
2950 | - absPath, err := snd.dataFind(path.Join(app.Package, sound)) |
2951 | + absPath, err := snd.dataFind(filepath.Join(app.Package, sound)) |
2952 | if err == nil { |
2953 | // ffffound |
2954 | return absPath |
2955 | @@ -76,7 +80,7 @@ |
2956 | // next, check the XDG data dirs (but skip the first one -- that's "home") |
2957 | // XXX should we only check in $XDG/sounds ? (that's for sound *themes*...) |
2958 | for _, dir := range snd.dataDirs()[1:] { |
2959 | - absPath := path.Join(dir, sound) |
2960 | + absPath := filepath.Join(dir, sound) |
2961 | _, err := os.Stat(absPath) |
2962 | if err == nil { |
2963 | return absPath |
2964 | |
2965 | === modified file 'sounds/sounds_test.go' |
2966 | --- sounds/sounds_test.go 2014-07-11 19:42:57 +0000 |
2967 | +++ sounds/sounds_test.go 2014-07-29 16:56:22 +0000 |
2968 | @@ -70,7 +70,7 @@ |
2969 | } |
2970 | |
2971 | // nil notification |
2972 | - c.Check(s.Present(ss.app, "", nil), Equals, false) |
2973 | + c.Check(func() { s.Present(ss.app, "", nil) }, Panics, `please check notification is not nil before calling present`) |
2974 | // no Sound |
2975 | c.Check(s.Present(ss.app, "", &launch_helper.Notification{}), Equals, false) |
2976 | // bad player |