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