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: | 107 |
Proposed branch: | lp:~chipaca/ubuntu-push/the-push-automatic |
Merge into: | lp:ubuntu-push |
Diff against target: |
3302 lines (+1773/-357) 35 files modified
PACKAGE_DEPS (+1/-0) bus/connectivity/connectivity.go (+1/-1) bus/connectivity/connectivity_test.go (+1/-0) bus/emblemcounter/emblemcounter.go (+69/-0) bus/emblemcounter/emblemcounter_test.go (+79/-0) bus/endpoint.go (+11/-0) bus/haptic/haptic.go (+70/-0) bus/haptic/haptic_test.go (+111/-0) bus/notifications/app_helper/app_helper_c.go (+18/-13) bus/testing/testing_endpoint.go (+15/-0) bus/testing/testing_endpoint_test.go (+11/-0) click/cclick/cclick.go (+76/-0) click/click.go (+86/-0) click/click_test.go (+86/-0) client/client.go (+114/-31) client/client_test.go (+227/-32) client/service/common.go (+28/-6) client/service/common_test.go (+60/-0) client/service/postal.go (+55/-38) client/service/postal_test.go (+111/-52) client/service/service.go (+78/-78) client/service/service_test.go (+181/-96) client/session/session.go (+11/-0) client/session/session_test.go (+61/-3) debian/changelog (+15/-0) debian/config.json (+1/-1) debian/control (+1/-0) launch_helper/helper_output.go (+2/-2) messaging/messaging.go (+1/-0) messaging/messaging_test.go (+4/-4) server/session/session_test.go (+1/-0) sounds/sounds.go (+91/-0) sounds/sounds_test.go (+85/-0) testing/helpers.go (+10/-0) whoopsie/identifier/identifier.go (+1/-0) |
To merge this branch: | bzr merge lp:~chipaca/ubuntu-push/the-push-automatic |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John Lenton (community) | Approve | ||
Review via email: mp+225715@code.launchpad.net |
Commit message
Description of the change
Merging automatic:
[ Samuele Pedroni ]
* Logic to support unregistering tokens lazily for uninstalled apps
* Minimal wrapping of libclick to check if a package is installed for a user
[ John Lenton ]
* Finalize dbus api
* First version of emblem counters.
To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) : | # |
review:
Approve
- 107. By PS Jenkins bot
-
* Logic to support unregistering tokens lazily for uninstalled apps
* Minimal wrapping of libclick to check if a package is installed for a user
* Refactor and cleanup of cleanup/service
* Finalized DBus API (hopefully)
* Support emblem counter notifications
* Support haptic (vibration) notifications
* Support sound notifications
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'PACKAGE_DEPS' |
2 | --- PACKAGE_DEPS 2014-06-24 14:19:31 +0000 |
3 | +++ PACKAGE_DEPS 2014-07-07 14:23:18 +0000 |
4 | @@ -10,3 +10,4 @@ |
5 | libwhoopsie-dev |
6 | libmessaging-menu-dev |
7 | libubuntu-app-launch2-dev |
8 | +libclick-0.4-dev |
9 | |
10 | === modified file 'bus/connectivity/connectivity.go' |
11 | --- bus/connectivity/connectivity.go 2014-04-28 01:56:21 +0000 |
12 | +++ bus/connectivity/connectivity.go 2014-07-07 14:23:18 +0000 |
13 | @@ -129,7 +129,7 @@ |
14 | case v, ok := <-cs.networkStateCh: |
15 | if !ok { |
16 | // tear it all down and start over |
17 | - return false, errors.New("Got not-OK from StateChanged watch") |
18 | + return false, errors.New("got not-OK from StateChanged watch") |
19 | } |
20 | cs.webgetCh = nil |
21 | cs.currentState = v |
22 | |
23 | === modified file 'bus/connectivity/connectivity_test.go' |
24 | --- bus/connectivity/connectivity_test.go 2014-07-01 11:55:30 +0000 |
25 | +++ bus/connectivity/connectivity_test.go 2014-07-07 14:23:18 +0000 |
26 | @@ -167,6 +167,7 @@ |
27 | func (*racyEndpoint) GrabName(bool) <-chan error { return nil } |
28 | func (*racyEndpoint) WatchMethod(bus.DispatchMap, string, ...interface{}) {} |
29 | func (*racyEndpoint) Signal(member string, suffix string, args []interface{}) error { return nil } |
30 | +func (*racyEndpoint) SetProperty(string, string, interface{}) error { return nil } |
31 | |
32 | var _ bus.Endpoint = (*racyEndpoint)(nil) |
33 | |
34 | |
35 | === added directory 'bus/emblemcounter' |
36 | === added file 'bus/emblemcounter/emblemcounter.go' |
37 | --- bus/emblemcounter/emblemcounter.go 1970-01-01 00:00:00 +0000 |
38 | +++ bus/emblemcounter/emblemcounter.go 2014-07-07 14:23:18 +0000 |
39 | @@ -0,0 +1,69 @@ |
40 | +/* |
41 | + Copyright 2014 Canonical Ltd. |
42 | + |
43 | + This program is free software: you can redistribute it and/or modify it |
44 | + under the terms of the GNU General Public License version 3, as published |
45 | + by the Free Software Foundation. |
46 | + |
47 | + This program is distributed in the hope that it will be useful, but |
48 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
49 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
50 | + PURPOSE. See the GNU General Public License for more details. |
51 | + |
52 | + You should have received a copy of the GNU General Public License along |
53 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
54 | +*/ |
55 | + |
56 | +// Package emblemcounter can present notifications as a counter on an |
57 | +// emblem on an item in the launcher. |
58 | +package emblemcounter |
59 | + |
60 | +import ( |
61 | + "launchpad.net/go-dbus/v1" |
62 | + |
63 | + "launchpad.net/ubuntu-push/bus" |
64 | + "launchpad.net/ubuntu-push/click" |
65 | + "launchpad.net/ubuntu-push/launch_helper" |
66 | + "launchpad.net/ubuntu-push/logger" |
67 | + "launchpad.net/ubuntu-push/nih" |
68 | +) |
69 | + |
70 | +// emblemcounter works by setting properties on a well-known dbus name. |
71 | +var BusAddress = bus.Address{ |
72 | + Interface: "com.canonical.Unity.Launcher.Item", |
73 | + Path: "/com/canonical/Unity/Launcher", |
74 | + Name: "com.canonical.Unity.Launcher", |
75 | +} |
76 | + |
77 | +// EmblemCounter is a little tool that fiddles with the unity launcher |
78 | +// to put emblems with counters on launcher icons. |
79 | +type EmblemCounter struct { |
80 | + bus bus.Endpoint |
81 | + log logger.Logger |
82 | +} |
83 | + |
84 | +// Build an EmblemCounter using the given bus and log. |
85 | +func New(endp bus.Endpoint, log logger.Logger) *EmblemCounter { |
86 | + return &EmblemCounter{bus: endp, log: log} |
87 | +} |
88 | + |
89 | +// Look for an EmblemCounter section in a Notification and, if |
90 | +// present, presents it to the user. |
91 | +func (ctr *EmblemCounter) Present(appId string, notificationId string, notification *launch_helper.Notification) { |
92 | + if notification == nil || notification.EmblemCounter == nil { |
93 | + ctr.log.Debugf("no notification or no EmblemCounter in the notification; doing nothing: %#v", notification) |
94 | + return |
95 | + } |
96 | + parsed, err := click.ParseAppId(appId) |
97 | + if err != nil { |
98 | + ctr.log.Debugf("no appId in %#v", appId) |
99 | + return |
100 | + } |
101 | + ec := notification.EmblemCounter |
102 | + ctr.log.Debugf("setting emblem counter for %s to %d (visible: %t)", appId, ec.Count, ec.Visible) |
103 | + |
104 | + quoted := string(nih.Quote([]byte(parsed.Application))) |
105 | + |
106 | + ctr.bus.SetProperty("count", "/"+quoted, dbus.Variant{ec.Count}) |
107 | + ctr.bus.SetProperty("countVisible", "/"+quoted, dbus.Variant{ec.Visible}) |
108 | +} |
109 | |
110 | === added file 'bus/emblemcounter/emblemcounter_test.go' |
111 | --- bus/emblemcounter/emblemcounter_test.go 1970-01-01 00:00:00 +0000 |
112 | +++ bus/emblemcounter/emblemcounter_test.go 2014-07-07 14:23:18 +0000 |
113 | @@ -0,0 +1,79 @@ |
114 | +/* |
115 | + Copyright 2014 Canonical Ltd. |
116 | + |
117 | + This program is free software: you can redistribute it and/or modify it |
118 | + under the terms of the GNU General Public License version 3, as published |
119 | + by the Free Software Foundation. |
120 | + |
121 | + This program is distributed in the hope that it will be useful, but |
122 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
123 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
124 | + PURPOSE. See the GNU General Public License for more details. |
125 | + |
126 | + You should have received a copy of the GNU General Public License along |
127 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
128 | +*/ |
129 | + |
130 | +package emblemcounter |
131 | + |
132 | +import ( |
133 | + "testing" |
134 | + |
135 | + "launchpad.net/go-dbus/v1" |
136 | + . "launchpad.net/gocheck" |
137 | + |
138 | + testibus "launchpad.net/ubuntu-push/bus/testing" |
139 | + "launchpad.net/ubuntu-push/launch_helper" |
140 | + helpers "launchpad.net/ubuntu-push/testing" |
141 | + "launchpad.net/ubuntu-push/testing/condition" |
142 | +) |
143 | + |
144 | +func TestEmblemCounter(t *testing.T) { TestingT(t) } |
145 | + |
146 | +type ecSuite struct { |
147 | + log *helpers.TestLogger |
148 | +} |
149 | + |
150 | +var _ = Suite(&ecSuite{}) |
151 | + |
152 | +func (ecs *ecSuite) SetUpTest(c *C) { |
153 | + ecs.log = helpers.NewTestLogger(c, "debug") |
154 | +} |
155 | + |
156 | +// checks that Present() actually calls SetProperty on the launcher |
157 | +func (ecs *ecSuite) TestPresentPresents(c *C) { |
158 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
159 | + |
160 | + ec := New(endp, ecs.log) |
161 | + notif := launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{Count: 42, Visible: true}} |
162 | + ec.Present("com.example.test_test-app", "nid", ¬if) |
163 | + callArgs := testibus.GetCallArgs(endp) |
164 | + c.Assert(callArgs, HasLen, 2) |
165 | + c.Check(callArgs[0].Member, Equals, "::SetProperty") |
166 | + c.Check(callArgs[1].Member, Equals, "::SetProperty") |
167 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", "/test_2dapp", dbus.Variant{Value: int32(42)}}) |
168 | + c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", "/test_2dapp", dbus.Variant{Value: true}}) |
169 | +} |
170 | + |
171 | +// check that Present() doesn't call SetProperty if no EmblemCounter in the Notification |
172 | +func (ecs *ecSuite) TestSkipIfMissing(c *C) { |
173 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
174 | + ec := New(endp, ecs.log) |
175 | + |
176 | + // nothing happens if nil Notification |
177 | + ec.Present("com.example.test_test-app", "nid", nil) |
178 | + c.Assert(testibus.GetCallArgs(endp), HasLen, 0) |
179 | + |
180 | + // nothing happens if no EmblemCounter in Notification |
181 | + ec.Present("com.example.test_test-app", "nid", &launch_helper.Notification{}) |
182 | + c.Assert(testibus.GetCallArgs(endp), HasLen, 0) |
183 | + |
184 | + // but an empty EmblemCounter is acted on |
185 | + ec.Present("com.example.test_test-app", "nid", &launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{}}) |
186 | + callArgs := testibus.GetCallArgs(endp) |
187 | + c.Assert(callArgs, HasLen, 2) |
188 | + c.Check(callArgs[0].Member, Equals, "::SetProperty") |
189 | + c.Check(callArgs[1].Member, Equals, "::SetProperty") |
190 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", "/test_2dapp", dbus.Variant{Value: int32(0)}}) |
191 | + c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", "/test_2dapp", dbus.Variant{Value: false}}) |
192 | +} |
193 | |
194 | === modified file 'bus/endpoint.go' |
195 | --- bus/endpoint.go 2014-07-01 11:55:30 +0000 |
196 | +++ bus/endpoint.go 2014-07-07 14:23:18 +0000 |
197 | @@ -42,6 +42,7 @@ |
198 | Signal(string, string, []interface{}) error |
199 | Call(member string, args []interface{}, rvs ...interface{}) error |
200 | GetProperty(property string) (interface{}, error) |
201 | + SetProperty(property string, suffix string, value interface{}) error |
202 | Dial() error |
203 | Close() |
204 | String() string |
205 | @@ -178,6 +179,16 @@ |
206 | return variant.Value, nil |
207 | } |
208 | |
209 | +// SetProperty calls org.freedesktop.DBus.Properties's Set method |
210 | +// |
211 | +// XXX: untested |
212 | +func (endp *endpoint) SetProperty(property string, suffix string, value interface{}) error { |
213 | + // can't use the pre-existing ObjectProxy for this one |
214 | + proxy := endp.bus.Object(endp.addr.Name, dbus.ObjectPath(endp.addr.Path+suffix)) |
215 | + _, err := proxy.Call("org.freedesktop.DBus.Properties", "Set", endp.addr.Interface, property, value) |
216 | + return err |
217 | +} |
218 | + |
219 | // Close the connection to dbus. |
220 | // |
221 | // XXX: untested |
222 | |
223 | === added directory 'bus/haptic' |
224 | === added file 'bus/haptic/haptic.go' |
225 | --- bus/haptic/haptic.go 1970-01-01 00:00:00 +0000 |
226 | +++ bus/haptic/haptic.go 2014-07-07 14:23:18 +0000 |
227 | @@ -0,0 +1,70 @@ |
228 | +/* |
229 | + Copyright 2014 Canonical Ltd. |
230 | + |
231 | + This program is free software: you can redistribute it and/or modify it |
232 | + under the terms of the GNU General Public License version 3, as published |
233 | + by the Free Software Foundation. |
234 | + |
235 | + This program is distributed in the hope that it will be useful, but |
236 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
237 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
238 | + PURPOSE. See the GNU General Public License for more details. |
239 | + |
240 | + You should have received a copy of the GNU General Public License along |
241 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
242 | +*/ |
243 | + |
244 | +// Package haptic can present notifications as a vibration pattern |
245 | +// using the usensord/haptic interface |
246 | +package haptic |
247 | + |
248 | +import ( |
249 | + "launchpad.net/ubuntu-push/bus" |
250 | + "launchpad.net/ubuntu-push/launch_helper" |
251 | + "launchpad.net/ubuntu-push/logger" |
252 | +) |
253 | + |
254 | +// usensord/haptic lives on a well-knwon bus.Address |
255 | +var BusAddress bus.Address = bus.Address{ |
256 | + Interface: "com.canonical.usensord.haptic", |
257 | + Path: "/com/canonical/usensord/haptic", |
258 | + Name: "com.canonical.usensord", |
259 | +} |
260 | + |
261 | +// Haptic encapsulates info needed to call out to usensord/haptic |
262 | +type Haptic struct { |
263 | + bus bus.Endpoint |
264 | + log logger.Logger |
265 | +} |
266 | + |
267 | +// New returns a new Haptic that'll use the provided bus.Endpoint |
268 | +func New(endp bus.Endpoint, log logger.Logger) *Haptic { |
269 | + return &Haptic{endp, log} |
270 | +} |
271 | + |
272 | +// Present presents the notification via a vibrate pattern |
273 | +func (haptic *Haptic) Present(_, _ string, notification *launch_helper.Notification) bool { |
274 | + if notification == nil || notification.Vibrate == nil { |
275 | + haptic.log.Debugf("no notification or no Vibrate in the notification; doing nothing: %#v", notification) |
276 | + return false |
277 | + } |
278 | + pattern := notification.Vibrate.Pattern |
279 | + repeat := notification.Vibrate.Repeat |
280 | + if repeat == 0 { |
281 | + repeat = 1 |
282 | + } |
283 | + if notification.Vibrate.Duration != 0 { |
284 | + pattern = []uint32{notification.Vibrate.Duration} |
285 | + } |
286 | + if len(pattern) == 0 { |
287 | + haptic.log.Debugf("not enough information in the notification's Vibrate section to create a pattern") |
288 | + return false |
289 | + } |
290 | + haptic.log.Debugf("vibrating %d times to the tune of %v", repeat, pattern) |
291 | + err := haptic.bus.Call("VibratePattern", bus.Args(pattern, repeat)) |
292 | + if err != nil { |
293 | + haptic.log.Debugf("VibratePattern call returned %v", err) |
294 | + return false |
295 | + } |
296 | + return true |
297 | +} |
298 | |
299 | === added file 'bus/haptic/haptic_test.go' |
300 | --- bus/haptic/haptic_test.go 1970-01-01 00:00:00 +0000 |
301 | +++ bus/haptic/haptic_test.go 2014-07-07 14:23:18 +0000 |
302 | @@ -0,0 +1,111 @@ |
303 | +/* |
304 | + Copyright 2014 Canonical Ltd. |
305 | + |
306 | + This program is free software: you can redistribute it and/or modify it |
307 | + under the terms of the GNU General Public License version 3, as published |
308 | + by the Free Software Foundation. |
309 | + |
310 | + This program is distributed in the hope that it will be useful, but |
311 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
312 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
313 | + PURPOSE. See the GNU General Public License for more details. |
314 | + |
315 | + You should have received a copy of the GNU General Public License along |
316 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
317 | +*/ |
318 | + |
319 | +package haptic |
320 | + |
321 | +import ( |
322 | + "testing" |
323 | + |
324 | + . "launchpad.net/gocheck" |
325 | + |
326 | + testibus "launchpad.net/ubuntu-push/bus/testing" |
327 | + "launchpad.net/ubuntu-push/launch_helper" |
328 | + helpers "launchpad.net/ubuntu-push/testing" |
329 | + "launchpad.net/ubuntu-push/testing/condition" |
330 | +) |
331 | + |
332 | +func TestHaptic(t *testing.T) { TestingT(t) } |
333 | + |
334 | +type hapticSuite struct { |
335 | + log *helpers.TestLogger |
336 | +} |
337 | + |
338 | +var _ = Suite(&hapticSuite{}) |
339 | + |
340 | +func (hs *hapticSuite) SetUpTest(c *C) { |
341 | + hs.log = helpers.NewTestLogger(c, "debug") |
342 | +} |
343 | + |
344 | +// checks that Present() actually calls VibratePattern |
345 | +func (hs *hapticSuite) TestPresentPresents(c *C) { |
346 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
347 | + |
348 | + ec := New(endp, hs.log) |
349 | + notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2}} |
350 | + c.Check(ec.Present("com.example.test_test-app", "nid", ¬if), Equals, true) |
351 | + callArgs := testibus.GetCallArgs(endp) |
352 | + c.Assert(callArgs, HasLen, 1) |
353 | + c.Check(callArgs[0].Member, Equals, "VibratePattern") |
354 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200, 100}, uint32(2)}) |
355 | +} |
356 | + |
357 | +// check that Present() defaults Repeat to 1 |
358 | +func (hs *hapticSuite) TestPresentDefaultsRepeatTo1(c *C) { |
359 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
360 | + |
361 | + ec := New(endp, hs.log) |
362 | + // note: no Repeat: |
363 | + notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Pattern: []uint32{200, 100}}} |
364 | + c.Check(ec.Present("com.example.test_test-app", "nid", ¬if), Equals, true) |
365 | + callArgs := testibus.GetCallArgs(endp) |
366 | + c.Assert(callArgs, HasLen, 1) |
367 | + c.Check(callArgs[0].Member, Equals, "VibratePattern") |
368 | + // note: Repeat of 1: |
369 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200, 100}, uint32(1)}) |
370 | +} |
371 | + |
372 | +// check that Present() makes a Pattern of [Duration] if Duration is given |
373 | +func (hs *hapticSuite) TestPresentBuildsPatternWithDuration(c *C) { |
374 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
375 | + |
376 | + ec := New(endp, hs.log) |
377 | + // note: no Repeat, no Pattern, just Duration: |
378 | + notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Duration: 200}} |
379 | + c.Check(ec.Present("com.example.test_test-app", "nid", ¬if), Equals, true) |
380 | + callArgs := testibus.GetCallArgs(endp) |
381 | + c.Assert(callArgs, HasLen, 1) |
382 | + c.Check(callArgs[0].Member, Equals, "VibratePattern") |
383 | + // note: Pattern of [Duration], Repeat of 1: |
384 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200}, uint32(1)}) |
385 | +} |
386 | + |
387 | +// check that Present() ignores Pattern and makes a Pattern of [Duration] if Duration is given |
388 | +func (hs *hapticSuite) TestPresentOverrides(c *C) { |
389 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
390 | + |
391 | + ec := New(endp, hs.log) |
392 | + // note: Duration given, as well as Pattern; Repeat given as 0: |
393 | + notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Duration: 200, Pattern: []uint32{500}, Repeat: 0}} |
394 | + c.Check(ec.Present("com.example.test_test-app", "nid", ¬if), Equals, true) |
395 | + callArgs := testibus.GetCallArgs(endp) |
396 | + c.Assert(callArgs, HasLen, 1) |
397 | + c.Check(callArgs[0].Member, Equals, "VibratePattern") |
398 | + // note: Pattern of [Duration], Repeat of 1: |
399 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200}, uint32(1)}) |
400 | +} |
401 | + |
402 | +// check that Present() doesn't call VibratePattern if things are not right |
403 | +func (hs *hapticSuite) TestSkipIfMissing(c *C) { |
404 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
405 | + |
406 | + ec := New(endp, hs.log) |
407 | + // no notification at all |
408 | + c.Check(ec.Present("", "", nil), Equals, false) |
409 | + // no Vibration in the notificaton |
410 | + c.Check(ec.Present("", "", &launch_helper.Notification{}), Equals, false) |
411 | + // empty Vibration |
412 | + c.Check(ec.Present("", "", &launch_helper.Notification{Vibrate: &launch_helper.Vibration{}}), Equals, false) |
413 | +} |
414 | |
415 | === modified file 'bus/notifications/app_helper/app_helper_c.go' |
416 | --- bus/notifications/app_helper/app_helper_c.go 2014-06-26 17:03:34 +0000 |
417 | +++ bus/notifications/app_helper/app_helper_c.go 2014-07-07 14:23:18 +0000 |
418 | @@ -19,23 +19,28 @@ |
419 | |
420 | /* |
421 | #cgo pkg-config: gio-unix-2.0 |
422 | -#cgo pkg-config: gio-2.0 |
423 | -#include <stdlib.h> |
424 | #include <glib.h> |
425 | #include <gio/gdesktopappinfo.h> |
426 | + |
427 | +gchar* app_icon_filename_from_id (gchar* app_id) { |
428 | + gchar* filename = NULL; |
429 | + GAppInfo* app_info = (GAppInfo*)g_desktop_app_info_new (app_id); |
430 | + if (app_info != NULL) { |
431 | + GIcon* icon = g_app_info_get_icon (app_info); |
432 | + if (icon != NULL) { |
433 | + filename = g_icon_to_string (icon); |
434 | + // g_app_info_get_icon has "transfer none" |
435 | + } |
436 | + g_object_unref (app_info); |
437 | + } |
438 | + g_free (app_id); |
439 | + return filename; |
440 | +} |
441 | */ |
442 | import "C" |
443 | -import "unsafe" |
444 | |
445 | func AppIconFromId(appId string) string { |
446 | - _id := C.CString(appId) |
447 | - defer C.free(unsafe.Pointer(_id)) |
448 | - _app_info := C.g_desktop_app_info_new(_id) |
449 | - defer C.g_app_info_delete(_app_info) |
450 | - _app_icon := C.g_app_info_get_icon(_app_info) |
451 | - defer C.g_object_unref((C.gpointer)(_app_icon)) |
452 | - _icon_string := C.g_icon_to_string(_app_icon) |
453 | - defer C.free(unsafe.Pointer(_icon_string)) |
454 | - name := C.GoString((*C.char)(_icon_string)) |
455 | - return name |
456 | + name := C.app_icon_filename_from_id((*C.gchar)(C.CString(appId))) |
457 | + defer C.g_free((C.gpointer)(name)) |
458 | + return C.GoString((*C.char)(name)) |
459 | } |
460 | |
461 | === modified file 'bus/testing/testing_endpoint.go' |
462 | --- bus/testing/testing_endpoint.go 2014-07-01 11:55:30 +0000 |
463 | +++ bus/testing/testing_endpoint.go 2014-07-07 14:23:18 +0000 |
464 | @@ -150,6 +150,21 @@ |
465 | return res, err |
466 | } |
467 | |
468 | +// See Endpoint's SetProperty. This one does nothing beyond |
469 | +// registering being called. |
470 | +func (tc *testingEndpoint) SetProperty(property string, suffix string, value interface{}) error { |
471 | + tc.callArgsLck.Lock() |
472 | + defer tc.callArgsLck.Unlock() |
473 | + |
474 | + args := callArgs{ |
475 | + Member: "::SetProperty", |
476 | + Args: []interface{}{property, suffix, value}, |
477 | + } |
478 | + tc.callArgs = append(tc.callArgs, args) |
479 | + |
480 | + return nil |
481 | +} |
482 | + |
483 | // See Endpoint's Dial. This one will check its dialCondition to |
484 | // decide whether to return an error or not. |
485 | func (endp *testingEndpoint) Dial() error { |
486 | |
487 | === modified file 'bus/testing/testing_endpoint_test.go' |
488 | --- bus/testing/testing_endpoint_test.go 2014-07-01 11:55:30 +0000 |
489 | +++ bus/testing/testing_endpoint_test.go 2014-07-07 14:23:18 +0000 |
490 | @@ -238,3 +238,14 @@ |
491 | Args: []interface{}{foomp, []interface{}(nil)}, |
492 | }}) |
493 | } |
494 | + |
495 | +// Test that SetProperty updates callArgs |
496 | +func (s *TestingEndpointSuite) TestSetPropertyUpdatesCallArgs(c *C) { |
497 | + endp := NewTestingEndpoint(nil, condition.Work(true)) |
498 | + endp.SetProperty("prop", "suffix", "value") |
499 | + c.Check(GetCallArgs(endp), DeepEquals, []callArgs{ |
500 | + { |
501 | + Member: "::SetProperty", |
502 | + Args: []interface{}{"prop", "suffix", "value"}, |
503 | + }}) |
504 | +} |
505 | |
506 | === added directory 'click' |
507 | === added directory 'click/cclick' |
508 | === added file 'click/cclick/cclick.go' |
509 | --- click/cclick/cclick.go 1970-01-01 00:00:00 +0000 |
510 | +++ click/cclick/cclick.go 2014-07-07 14:23:18 +0000 |
511 | @@ -0,0 +1,76 @@ |
512 | +/* |
513 | + Copyright 2013-2014 Canonical Ltd. |
514 | + |
515 | + This program is free software: you can redistribute it and/or modify it |
516 | + under the terms of the GNU General Public License version 3, as published |
517 | + by the Free Software Foundation. |
518 | + |
519 | + This program is distributed in the hope that it will be useful, but |
520 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
521 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
522 | + PURPOSE. See the GNU General Public License for more details. |
523 | + |
524 | + You should have received a copy of the GNU General Public License along |
525 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
526 | +*/ |
527 | + |
528 | +// Package cclick has the internal cgo wrapping libclick for package click. |
529 | +package cclick |
530 | + |
531 | +/* |
532 | +#cgo pkg-config: click-0.4 |
533 | +#cgo pkg-config: glib-2.0 gobject-2.0 |
534 | + |
535 | +#include <click-0.4/click.h> |
536 | +*/ |
537 | +import "C" |
538 | + |
539 | +import ( |
540 | + "fmt" |
541 | + "runtime" |
542 | +) |
543 | + |
544 | +type CClickUser struct { |
545 | + cref *C.ClickUser |
546 | +} |
547 | + |
548 | +func gchar(s string) *C.gchar { |
549 | + return (*C.gchar)(C.CString(s)) |
550 | +} |
551 | + |
552 | +func gfree(s *C.gchar) { |
553 | + C.g_free((C.gpointer)(s)) |
554 | +} |
555 | + |
556 | +func (ccu *CClickUser) CInit(holder interface{}) error { |
557 | + var gerr *C.GError |
558 | + cref := C.click_user_new_for_user(nil, nil, &gerr) |
559 | + defer C.g_clear_error(&gerr) |
560 | + if gerr != nil { |
561 | + return fmt.Errorf("faild to make ClickUser: %s", C.GoString((*C.char)(gerr.message))) |
562 | + } |
563 | + ccu.cref = cref |
564 | + runtime.SetFinalizer(holder, func(interface{}) { |
565 | + C.g_object_unref((C.gpointer)(cref)) |
566 | + }) |
567 | + return nil |
568 | +} |
569 | + |
570 | +func (ccu *CClickUser) CGetVersion(pkgName string) string { |
571 | + pkgname := gchar(pkgName) |
572 | + defer gfree(pkgname) |
573 | + var gerr *C.GError |
574 | + defer C.g_clear_error(&gerr) |
575 | + ver := C.click_user_get_version(ccu.cref, pkgname, &gerr) |
576 | + if gerr != nil { |
577 | + return "" |
578 | + } |
579 | + defer gfree(ver) |
580 | + return C.GoString((*C.char)(ver)) |
581 | +} |
582 | + |
583 | +func (ccu *CClickUser) CHasPackageName(pkgName string) bool { |
584 | + pkgname := gchar(pkgName) |
585 | + defer gfree(pkgname) |
586 | + return C.click_user_has_package_name(ccu.cref, pkgname) == C.TRUE |
587 | +} |
588 | |
589 | === added file 'click/click.go' |
590 | --- click/click.go 1970-01-01 00:00:00 +0000 |
591 | +++ click/click.go 2014-07-07 14:23:18 +0000 |
592 | @@ -0,0 +1,86 @@ |
593 | +/* |
594 | + Copyright 2014 Canonical Ltd. |
595 | + |
596 | + This program is free software: you can redistribute it and/or modify it |
597 | + under the terms of the GNU General Public License version 3, as published |
598 | + by the Free Software Foundation. |
599 | + |
600 | + This program is distributed in the hope that it will be useful, but |
601 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
602 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
603 | + PURPOSE. See the GNU General Public License for more details. |
604 | + |
605 | + You should have received a copy of the GNU General Public License along |
606 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
607 | +*/ |
608 | + |
609 | +// Package click exposes some utilities related to click packages and |
610 | +// wraps libclick to check if packages are installed. |
611 | +package click |
612 | + |
613 | +import ( |
614 | + "errors" |
615 | + "regexp" |
616 | + "sync" |
617 | + |
618 | + "launchpad.net/ubuntu-push/click/cclick" |
619 | +) |
620 | + |
621 | +// AppId holds a parsed application id. |
622 | +type AppId struct { |
623 | + Package string |
624 | + Application string |
625 | + Version string |
626 | +} |
627 | + |
628 | +// from https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId |
629 | +// except the version is made optional |
630 | +var rx = regexp.MustCompile(`^([a-z0-9][a-z0-9+.-]+)_([a-zA-Z0-9+.-]+)(?:_([0-9][a-zA-Z0-9.+:~-]*))?$`) |
631 | + |
632 | +var ( |
633 | + ErrInvalidAppId = errors.New("invalid application id") |
634 | +) |
635 | + |
636 | +func ParseAppId(id string) (*AppId, error) { |
637 | + m := rx.FindStringSubmatch(id) |
638 | + if len(m) == 0 { |
639 | + return nil, ErrInvalidAppId |
640 | + } |
641 | + return &AppId{Package: m[1], Application: m[2], Version: m[3]}, nil |
642 | +} |
643 | + |
644 | +func AppInPackage(appId, pkgname string) bool { |
645 | + id, _ := ParseAppId(appId) |
646 | + return id != nil && id.Package == pkgname |
647 | +} |
648 | + |
649 | +// ClickUser exposes the click package registry for the user. |
650 | +type ClickUser struct { |
651 | + ccu cclick.CClickUser |
652 | + lock sync.Mutex |
653 | +} |
654 | + |
655 | +// User makes a new ClickUser object for the current user. |
656 | +func User() (*ClickUser, error) { |
657 | + cu := new(ClickUser) |
658 | + err := cu.ccu.CInit(cu) |
659 | + if err != nil { |
660 | + return nil, err |
661 | + } |
662 | + return cu, nil |
663 | +} |
664 | + |
665 | +// HasPackage checks if the appId is installed for user. |
666 | +func (cu *ClickUser) HasPackage(appId string) bool { |
667 | + cu.lock.Lock() |
668 | + defer cu.lock.Unlock() |
669 | + id, err := ParseAppId(appId) |
670 | + if err != nil { |
671 | + return false |
672 | + } |
673 | + if id.Version != "" { |
674 | + return cu.ccu.CGetVersion(id.Package) == id.Version |
675 | + } else { |
676 | + return cu.ccu.CHasPackageName(id.Package) |
677 | + } |
678 | +} |
679 | |
680 | === added file 'click/click_test.go' |
681 | --- click/click_test.go 1970-01-01 00:00:00 +0000 |
682 | +++ click/click_test.go 2014-07-07 14:23:18 +0000 |
683 | @@ -0,0 +1,86 @@ |
684 | +/* |
685 | + Copyright 2013-2014 Canonical Ltd. |
686 | + |
687 | + This program is free software: you can redistribute it and/or modify it |
688 | + under the terms of the GNU General Public License version 3, as published |
689 | + by the Free Software Foundation. |
690 | + |
691 | + This program is distributed in the hope that it will be useful, but |
692 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
693 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
694 | + PURPOSE. See the GNU General Public License for more details. |
695 | + |
696 | + You should have received a copy of the GNU General Public License along |
697 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
698 | +*/ |
699 | + |
700 | +package click |
701 | + |
702 | +import ( |
703 | + "testing" |
704 | + |
705 | + . "launchpad.net/gocheck" |
706 | +) |
707 | + |
708 | +func TestClick(t *testing.T) { TestingT(t) } |
709 | + |
710 | +type clickSuite struct{} |
711 | + |
712 | +var _ = Suite(&clickSuite{}) |
713 | + |
714 | +func (cs *clickSuite) TestParseAppId(c *C) { |
715 | + id, err := ParseAppId("com.ubuntu.clock_clock") |
716 | + c.Assert(err, IsNil) |
717 | + c.Check(id.Package, Equals, "com.ubuntu.clock") |
718 | + c.Check(id.Application, Equals, "clock") |
719 | + c.Check(id.Version, Equals, "") |
720 | + |
721 | + id, err = ParseAppId("com.ubuntu.clock_clock_10") |
722 | + c.Assert(err, IsNil) |
723 | + c.Check(id.Package, Equals, "com.ubuntu.clock") |
724 | + c.Check(id.Application, Equals, "clock") |
725 | + c.Check(id.Version, Equals, "10") |
726 | + |
727 | + for _, s := range []string{"com.ubuntu.clock_clock_10_4", "com.ubuntu.clock", ""} { |
728 | + id, err = ParseAppId(s) |
729 | + c.Check(id, IsNil) |
730 | + c.Check(err, Equals, ErrInvalidAppId) |
731 | + } |
732 | +} |
733 | + |
734 | +func (cs *clickSuite) TestInPackage(c *C) { |
735 | + c.Check(AppInPackage("com.ubuntu.clock_clock", "com.ubuntu.clock"), Equals, true) |
736 | + c.Check(AppInPackage("com.ubuntu.clock_clock_10", "com.ubuntu.clock"), Equals, true) |
737 | + c.Check(AppInPackage("com.ubuntu.clock", "com.ubuntu.clock"), Equals, false) |
738 | + c.Check(AppInPackage("bananas", "fruit"), Equals, false) |
739 | +} |
740 | + |
741 | +func (s *clickSuite) TestUser(c *C) { |
742 | + u, err := User() |
743 | + c.Assert(err, IsNil) |
744 | + c.Assert(u, NotNil) |
745 | +} |
746 | + |
747 | +func (s *clickSuite) TestHasPackageNegative(c *C) { |
748 | + u, err := User() |
749 | + c.Assert(err, IsNil) |
750 | + c.Check(u.HasPackage("com.foo.bar"), Equals, false) |
751 | + c.Check(u.HasPackage("com.foo.bar_baz"), Equals, false) |
752 | +} |
753 | + |
754 | +func (s *clickSuite) TestHasPackageVersionNegative(c *C) { |
755 | + u, err := User() |
756 | + c.Assert(err, IsNil) |
757 | + c.Check(u.HasPackage("com.ubuntu.clock_clock_1000.0"), Equals, false) |
758 | +} |
759 | + |
760 | +func (s *clickSuite) TestHasPackageClock(c *C) { |
761 | + u, err := User() |
762 | + c.Assert(err, IsNil) |
763 | + ver := u.ccu.CGetVersion("com.ubuntu.clock") |
764 | + if ver == "" { |
765 | + c.Skip("no com.ubuntu.clock pkg installed") |
766 | + } |
767 | + c.Check(u.HasPackage("com.ubuntu.clock_clock"), Equals, true) |
768 | + c.Check(u.HasPackage("com.ubuntu.clock_clock_"+ver), Equals, true) |
769 | +} |
770 | |
771 | === modified file 'client/client.go' |
772 | --- client/client.go 2014-07-01 11:55:30 +0000 |
773 | +++ client/client.go 2014-07-07 14:23:18 +0000 |
774 | @@ -26,16 +26,20 @@ |
775 | "errors" |
776 | "fmt" |
777 | "io/ioutil" |
778 | + "net/url" |
779 | "os" |
780 | "os/exec" |
781 | "strings" |
782 | |
783 | "launchpad.net/ubuntu-push/bus" |
784 | "launchpad.net/ubuntu-push/bus/connectivity" |
785 | + "launchpad.net/ubuntu-push/bus/emblemcounter" |
786 | + "launchpad.net/ubuntu-push/bus/haptic" |
787 | "launchpad.net/ubuntu-push/bus/networkmanager" |
788 | "launchpad.net/ubuntu-push/bus/notifications" |
789 | "launchpad.net/ubuntu-push/bus/systemimage" |
790 | "launchpad.net/ubuntu-push/bus/urldispatcher" |
791 | + "launchpad.net/ubuntu-push/click" |
792 | "launchpad.net/ubuntu-push/client/service" |
793 | "launchpad.net/ubuntu-push/client/session" |
794 | "launchpad.net/ubuntu-push/client/session/seenstate" |
795 | @@ -68,6 +72,14 @@ |
796 | LogLevel logger.ConfigLogLevel `json:"log_level"` |
797 | } |
798 | |
799 | +// PushService is the interface we use of service.PushService. |
800 | +type PushService interface { |
801 | + // Start starts the service. |
802 | + Start() error |
803 | + // Unregister unregisters the token for appId. |
804 | + Unregister(appId string) error |
805 | +} |
806 | + |
807 | // PushClient is the Ubuntu Push Notifications client-side daemon. |
808 | type PushClient struct { |
809 | leveldbPath string |
810 | @@ -80,6 +92,8 @@ |
811 | notificationsEndp bus.Endpoint |
812 | urlDispatcherEndp bus.Endpoint |
813 | connectivityEndp bus.Endpoint |
814 | + emblemcounterEndp bus.Endpoint |
815 | + hapticEndp bus.Endpoint |
816 | systemImageEndp bus.Endpoint |
817 | systemImageInfo *systemimage.InfoResult |
818 | connCh chan bool |
819 | @@ -88,9 +102,13 @@ |
820 | session *session.ClientSession |
821 | sessionConnectedCh chan uint32 |
822 | pushServiceEndpoint bus.Endpoint |
823 | - pushService *service.PushService |
824 | + pushService PushService |
825 | postalServiceEndpoint bus.Endpoint |
826 | postalService *service.PostalService |
827 | + unregisterCh chan string |
828 | + trackAddressees map[string]bool |
829 | + clickUser *click.ClickUser |
830 | + hasPackage func(string) bool |
831 | } |
832 | |
833 | // Creates a new Ubuntu Push Notifications client-side daemon that will use |
834 | @@ -122,14 +140,26 @@ |
835 | // later, we'll be specifying more logging options in the config file |
836 | client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel.Level()) |
837 | |
838 | + clickUser, err := click.User() |
839 | + if err != nil { |
840 | + return fmt.Errorf("libclick: %v", err) |
841 | + } |
842 | + client.clickUser = clickUser |
843 | + // overridden for testing |
844 | + client.hasPackage = clickUser.HasPackage |
845 | + |
846 | + client.unregisterCh = make(chan string, 10) |
847 | + |
848 | // overridden for testing |
849 | client.idder = identifier.New() |
850 | client.urlDispatcherEndp = bus.SessionBus.Endpoint(urldispatcher.BusAddress, client.log) |
851 | client.connectivityEndp = bus.SystemBus.Endpoint(networkmanager.BusAddress, client.log) |
852 | client.systemImageEndp = bus.SystemBus.Endpoint(systemimage.BusAddress, client.log) |
853 | - if client.notificationsEndp == nil { |
854 | - client.notificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, client.log) |
855 | - } |
856 | + client.notificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, client.log) |
857 | + client.emblemcounterEndp = bus.SessionBus.Endpoint(emblemcounter.BusAddress, client.log) |
858 | + client.hapticEndp = bus.SessionBus.Endpoint(haptic.BusAddress, client.log) |
859 | + client.postalServiceEndpoint = bus.SessionBus.Endpoint(service.PostalServiceBusAddress, client.log) |
860 | + client.pushServiceEndpoint = bus.SessionBus.Endpoint(service.PushServiceBusAddress, client.log) |
861 | |
862 | client.connCh = make(chan bool, 1) |
863 | client.sessionConnectedCh = make(chan uint32, 1) |
864 | @@ -156,11 +186,25 @@ |
865 | ExchangeTimeout: client.config.ExchangeTimeout.TimeDuration(), |
866 | HostsCachingExpiryTime: client.config.HostsCachingExpiryTime.TimeDuration(), |
867 | ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(), |
868 | - PEM: client.pem, |
869 | - Info: info, |
870 | - AuthGetter: client.getAuthorization, |
871 | - AuthURL: client.config.SessionURL, |
872 | - } |
873 | + PEM: client.pem, |
874 | + Info: info, |
875 | + AuthGetter: client.getAuthorization, |
876 | + AuthURL: client.config.SessionURL, |
877 | + AddresseeChecker: client, |
878 | + } |
879 | +} |
880 | + |
881 | +// derivePushServiceSetup derives the service setup from the client configuration bits. |
882 | +func (client *PushClient) derivePushServiceSetup() (*service.PushServiceSetup, error) { |
883 | + setup := new(service.PushServiceSetup) |
884 | + purl, err := url.Parse(client.config.RegistrationURL) |
885 | + if err != nil { |
886 | + return nil, fmt.Errorf("cannot parse registration url: %v", err) |
887 | + } |
888 | + setup.RegURL = purl |
889 | + setup.DeviceId = client.deviceId |
890 | + setup.AuthGetter = client.getAuthorization |
891 | + return setup, nil |
892 | } |
893 | |
894 | // getAuthorization gets the authorization blob to send to the server |
895 | @@ -249,6 +293,42 @@ |
896 | } |
897 | } |
898 | |
899 | +// StartAddresseeBatch starts a batch of checks for addressees. |
900 | +func (client *PushClient) StartAddresseeBatch() { |
901 | + client.trackAddressees = make(map[string]bool, 10) |
902 | +} |
903 | + |
904 | +// CheckForAddressee check for the addressee presence. |
905 | +func (client *PushClient) CheckForAddressee(notif *protocol.Notification) bool { |
906 | + appId := notif.AppId |
907 | + present, ok := client.trackAddressees[appId] |
908 | + if ok { |
909 | + return present |
910 | + } |
911 | + present = client.hasPackage(appId) |
912 | + client.trackAddressees[appId] = present |
913 | + if !present { |
914 | + client.unregisterCh <- appId |
915 | + } |
916 | + return present |
917 | +} |
918 | + |
919 | +// handleUnregister deals with tokens of uninstalled apps |
920 | +func (client *PushClient) handleUnregister(appId string) { |
921 | + if !client.hasPackage(appId) { |
922 | + // xxx small chance of race here, in case the app gets |
923 | + // reinstalled and registers itself before we finish |
924 | + // the unregister; we need click and app launching |
925 | + // collaboration to do better. we redo the hasPackage |
926 | + // check here just before to keep the race window as |
927 | + // small as possible |
928 | + err := client.pushService.Unregister(appId) |
929 | + if err != nil { |
930 | + client.log.Errorf("unregistering %s: %s", appId, err) |
931 | + } |
932 | + } |
933 | +} |
934 | + |
935 | // handleConnState deals with connectivity events |
936 | func (client *PushClient) handleConnState(hasConnectivity bool) { |
937 | if client.hasConnectivity == hasConnectivity { |
938 | @@ -320,9 +400,7 @@ |
939 | if !client.filterBroadcastNotification(msg) { |
940 | return nil |
941 | } |
942 | - not_id, err := client.postalService.SendNotification(service.ACTION_ID_BROADCAST, |
943 | - "update_manager_icon", "There's an updated system image.", |
944 | - "Tap to open the system updater.") |
945 | + not_id, err := client.postalService.InjectBroadcast() |
946 | if err != nil { |
947 | client.log.Errorf("showing notification: %s", err) |
948 | return err |
949 | @@ -333,20 +411,30 @@ |
950 | |
951 | // handleUnicastNotification deals with receiving a unicast notification |
952 | func (client *PushClient) handleUnicastNotification(msg *protocol.Notification) error { |
953 | + appId, err := click.ParseAppId(msg.AppId) |
954 | + if err != nil { |
955 | + client.log.Debugf("notification %#v for invalid app id %#v.", msg.MsgId, msg.AppId) |
956 | + return errors.New("invalid app id in notification") |
957 | + } |
958 | client.log.Debugf("sending notification %#v for %#v.", msg.MsgId, msg.AppId) |
959 | - return client.postalService.Inject(msg.AppId, msg.MsgId, string(msg.Payload)) |
960 | + return client.postalService.Inject(appId.Package, msg.AppId, msg.MsgId, string(msg.Payload)) |
961 | } |
962 | |
963 | // handleClick deals with the user clicking a notification |
964 | -func (client *PushClient) handleClick(action_id string) error { |
965 | +func (client *PushClient) handleClick(actionId string) error { |
966 | // “The string is a stark data structure and everywhere it is passed |
967 | // there is much duplication of process. It is a perfect vehicle for |
968 | // hiding information.” |
969 | // |
970 | // From ACM's SIGPLAN publication, (September, 1982), Article |
971 | // "Epigrams in Programming", by Alan J. Perlis of Yale University. |
972 | - url := strings.TrimPrefix(action_id, service.ACTION_ID_SNOWFLAKE) |
973 | - if len(url) == len(action_id) || len(url) == 0 { |
974 | + url := actionId |
975 | + // XXX: branch for the broadcast notifications |
976 | + if strings.HasPrefix(actionId, service.ACTION_ID_PREFIX) { |
977 | + parts := strings.Split(actionId, "::") |
978 | + url = parts[1] |
979 | + } |
980 | + if len(url) == len(actionId) || len(url) == 0 { |
981 | // it didn't start with the prefix |
982 | return nil |
983 | } |
984 | @@ -356,7 +444,7 @@ |
985 | } |
986 | |
987 | // doLoop connects events with their handlers |
988 | -func (client *PushClient) doLoop(connhandler func(bool), clickhandler func(string) error, bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(*protocol.Notification) error, errhandler func(error)) { |
989 | +func (client *PushClient) doLoop(connhandler func(bool), clickhandler func(string) error, bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(*protocol.Notification) error, errhandler func(error), unregisterhandler func(string)) { |
990 | for { |
991 | select { |
992 | case state := <-client.connCh: |
993 | @@ -371,6 +459,8 @@ |
994 | errhandler(err) |
995 | case count := <-client.sessionConnectedCh: |
996 | client.log.Debugf("Session connected after %d attempts", count) |
997 | + case appId := <-client.unregisterCh: |
998 | + unregisterhandler(appId) |
999 | } |
1000 | } |
1001 | } |
1002 | @@ -392,21 +482,17 @@ |
1003 | client.handleClick, |
1004 | client.handleBroadcastNotification, |
1005 | client.handleUnicastNotification, |
1006 | - client.handleErr) |
1007 | + client.handleErr, |
1008 | + client.handleUnregister) |
1009 | } |
1010 | |
1011 | func (client *PushClient) startService() error { |
1012 | - if client.pushServiceEndpoint == nil { |
1013 | - client.pushServiceEndpoint = bus.SessionBus.Endpoint(service.PushServiceBusAddress, client.log) |
1014 | - } |
1015 | - if client.postalServiceEndpoint == nil { |
1016 | - client.postalServiceEndpoint = bus.SessionBus.Endpoint(service.PostalServiceBusAddress, client.log) |
1017 | + setup, err := client.derivePushServiceSetup() |
1018 | + if err != nil { |
1019 | + return err |
1020 | } |
1021 | |
1022 | - client.pushService = service.NewPushService(client.pushServiceEndpoint, client.log) |
1023 | - client.pushService.SetRegistrationURL(client.config.RegistrationURL) |
1024 | - client.pushService.SetAuthGetter(client.getAuthorization) |
1025 | - client.pushService.SetDeviceId(client.deviceId) |
1026 | + client.pushService = service.NewPushService(client.pushServiceEndpoint, setup, client.log) |
1027 | if err := client.pushService.Start(); err != nil { |
1028 | return err |
1029 | } |
1030 | @@ -414,10 +500,7 @@ |
1031 | } |
1032 | |
1033 | func (client *PushClient) setupPostalService() error { |
1034 | - if client.notificationsEndp == nil { |
1035 | - client.notificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, client.log) |
1036 | - } |
1037 | - client.postalService = service.NewPostalService(client.postalServiceEndpoint, client.notificationsEndp, client.log) |
1038 | + client.postalService = service.NewPostalService(client.postalServiceEndpoint, client.notificationsEndp, client.emblemcounterEndp, client.hapticEndp, client.log) |
1039 | return nil |
1040 | } |
1041 | |
1042 | |
1043 | === modified file 'client/client_test.go' |
1044 | --- client/client_test.go 2014-07-01 11:55:30 +0000 |
1045 | +++ client/client_test.go 2014-07-07 14:23:18 +0000 |
1046 | @@ -187,14 +187,26 @@ |
1047 | |
1048 | func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) { |
1049 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1050 | + |
1051 | + // keep these in the same order as in the client struct, for sanity |
1052 | c.Check(cli.notificationsEndp, IsNil) |
1053 | c.Check(cli.urlDispatcherEndp, IsNil) |
1054 | c.Check(cli.connectivityEndp, IsNil) |
1055 | + c.Check(cli.emblemcounterEndp, IsNil) |
1056 | + c.Check(cli.hapticEndp, IsNil) |
1057 | + c.Check(cli.systemImageEndp, IsNil) |
1058 | + c.Check(cli.pushServiceEndpoint, IsNil) |
1059 | + c.Check(cli.postalServiceEndpoint, IsNil) |
1060 | err := cli.configure() |
1061 | c.Assert(err, IsNil) |
1062 | - c.Assert(cli.notificationsEndp, NotNil) |
1063 | - c.Assert(cli.urlDispatcherEndp, NotNil) |
1064 | - c.Assert(cli.connectivityEndp, NotNil) |
1065 | + c.Check(cli.notificationsEndp, NotNil) |
1066 | + c.Check(cli.urlDispatcherEndp, NotNil) |
1067 | + c.Check(cli.connectivityEndp, NotNil) |
1068 | + c.Check(cli.emblemcounterEndp, NotNil) |
1069 | + c.Check(cli.hapticEndp, NotNil) |
1070 | + c.Check(cli.systemImageEndp, NotNil) |
1071 | + c.Check(cli.pushServiceEndpoint, NotNil) |
1072 | + c.Check(cli.postalServiceEndpoint, NotNil) |
1073 | } |
1074 | |
1075 | func (cs *clientSuite) TestConfigureSetsUpConnCh(c *C) { |
1076 | @@ -205,6 +217,15 @@ |
1077 | c.Assert(cli.connCh, NotNil) |
1078 | } |
1079 | |
1080 | +func (cs *clientSuite) TestConfigureSetsUpAddresseeChecks(c *C) { |
1081 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1082 | + c.Check(cli.unregisterCh, IsNil) |
1083 | + err := cli.configure() |
1084 | + c.Assert(err, IsNil) |
1085 | + c.Assert(cli.unregisterCh, NotNil) |
1086 | + c.Assert(cli.hasPackage("com.bar.baz_foo"), Equals, false) |
1087 | +} |
1088 | + |
1089 | func (cs *clientSuite) TestConfigureBailsOnBadFilename(c *C) { |
1090 | cli := NewPushClient("/does/not/exist", cs.leveldbPath) |
1091 | err := cli.configure() |
1092 | @@ -255,6 +276,35 @@ |
1093 | } |
1094 | |
1095 | /***************************************************************** |
1096 | + addresses checking tests |
1097 | +******************************************************************/ |
1098 | + |
1099 | +func (cs *clientSuite) TestCheckForAddressee(c *C) { |
1100 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1101 | + cli.unregisterCh = make(chan string, 5) |
1102 | + cli.StartAddresseeBatch() |
1103 | + calls := 0 |
1104 | + cli.hasPackage = func(appId string) bool { |
1105 | + calls++ |
1106 | + if appId == "app1" { |
1107 | + return false |
1108 | + } |
1109 | + return true |
1110 | + } |
1111 | + c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: "app1"}), Equals, false) |
1112 | + c.Check(calls, Equals, 1) |
1113 | + c.Assert(cli.unregisterCh, HasLen, 1) |
1114 | + c.Check(<-cli.unregisterCh, Equals, "app1") |
1115 | + c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: "app2"}), Equals, true) |
1116 | + c.Check(calls, Equals, 2) |
1117 | + c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: "app1"}), Equals, false) |
1118 | + c.Check(calls, Equals, 2) |
1119 | + c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: "app2"}), Equals, true) |
1120 | + c.Check(calls, Equals, 2) |
1121 | + c.Check(cli.unregisterCh, HasLen, 0) |
1122 | +} |
1123 | + |
1124 | +/***************************************************************** |
1125 | deriveSessionConfig tests |
1126 | ******************************************************************/ |
1127 | |
1128 | @@ -273,10 +323,11 @@ |
1129 | ExchangeTimeout: 10 * time.Millisecond, |
1130 | HostsCachingExpiryTime: 1 * time.Hour, |
1131 | ExpectAllRepairedTime: 30 * time.Minute, |
1132 | - PEM: cli.pem, |
1133 | - Info: info, |
1134 | - AuthGetter: func(string) string { return "" }, |
1135 | - AuthURL: "xyzzy://", |
1136 | + PEM: cli.pem, |
1137 | + Info: info, |
1138 | + AuthGetter: func(string) string { return "" }, |
1139 | + AuthURL: "xyzzy://", |
1140 | + AddresseeChecker: cli, |
1141 | } |
1142 | // sanity check that we are looking at all fields |
1143 | vExpected := reflect.ValueOf(expected) |
1144 | @@ -289,7 +340,7 @@ |
1145 | // finally compare |
1146 | conf := cli.deriveSessionConfig(info) |
1147 | // compare authGetter by string |
1148 | - c.Check(fmt.Sprintf("%v", conf.AuthGetter), Equals, fmt.Sprintf("%v", cli.getAuthorization)) |
1149 | + c.Check(fmt.Sprintf("%#v", conf.AuthGetter), Equals, fmt.Sprintf("%#v", cli.getAuthorization)) |
1150 | // and set it to nil |
1151 | conf.AuthGetter = nil |
1152 | expected.AuthGetter = nil |
1153 | @@ -297,6 +348,51 @@ |
1154 | } |
1155 | |
1156 | /***************************************************************** |
1157 | + derivePushServiceSetup tests |
1158 | +******************************************************************/ |
1159 | + |
1160 | +func (cs *clientSuite) TestDerivePushServiceSetup(c *C) { |
1161 | + cs.writeTestConfig(map[string]interface{}{}) |
1162 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1163 | + err := cli.configure() |
1164 | + c.Assert(err, IsNil) |
1165 | + cli.deviceId = "zoo" |
1166 | + expected := &service.PushServiceSetup{ |
1167 | + DeviceId: "zoo", |
1168 | + AuthGetter: func(string) string { return "" }, |
1169 | + RegURL: helpers.ParseURL("reg://"), |
1170 | + } |
1171 | + // sanity check that we are looking at all fields |
1172 | + vExpected := reflect.ValueOf(expected).Elem() |
1173 | + nf := vExpected.NumField() |
1174 | + for i := 0; i < nf; i++ { |
1175 | + fv := vExpected.Field(i) |
1176 | + // field isn't empty/zero |
1177 | + c.Assert(fv.Interface(), Not(DeepEquals), reflect.Zero(fv.Type()).Interface(), Commentf("forgot about: %s", vExpected.Type().Field(i).Name)) |
1178 | + } |
1179 | + // finally compare |
1180 | + setup, err := cli.derivePushServiceSetup() |
1181 | + c.Assert(err, IsNil) |
1182 | + // compare authGetter by string |
1183 | + c.Check(fmt.Sprintf("%#v", setup.AuthGetter), Equals, fmt.Sprintf("%#v", cli.getAuthorization)) |
1184 | + // and set it to nil |
1185 | + setup.AuthGetter = nil |
1186 | + expected.AuthGetter = nil |
1187 | + c.Check(setup, DeepEquals, expected) |
1188 | +} |
1189 | + |
1190 | +func (cs *clientSuite) TestDerivePushServiceSetupError(c *C) { |
1191 | + cs.writeTestConfig(map[string]interface{}{ |
1192 | + "registration_url": "%gh", |
1193 | + }) |
1194 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1195 | + err := cli.configure() |
1196 | + c.Assert(err, IsNil) |
1197 | + _, err = cli.derivePushServiceSetup() |
1198 | + c.Check(err, ErrorMatches, "cannot parse registration url:.*") |
1199 | +} |
1200 | + |
1201 | +/***************************************************************** |
1202 | startService tests |
1203 | ******************************************************************/ |
1204 | |
1205 | @@ -305,7 +401,8 @@ |
1206 | "auth_helper": helpers.ScriptAbsPath("dummyauth.sh"), |
1207 | }) |
1208 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1209 | - cli.configure() |
1210 | + err := cli.configure() |
1211 | + c.Assert(err, IsNil) |
1212 | cli.log = cs.log |
1213 | cli.deviceId = "fake-id" |
1214 | cli.pushServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil) |
1215 | @@ -313,16 +410,26 @@ |
1216 | c.Check(cli.pushService, IsNil) |
1217 | c.Check(cli.startService(), IsNil) |
1218 | c.Assert(cli.pushService, NotNil) |
1219 | - c.Check(cli.pushService.IsRunning(), Equals, true) |
1220 | - c.Check(cli.pushService.GetDeviceId(), Equals, "fake-id") |
1221 | - c.Check(cli.pushService.GetRegistrationAuthorization(), Equals, "hello reg://") |
1222 | + pushService := cli.pushService.(*service.PushService) |
1223 | + c.Check(pushService.IsRunning(), Equals, true) |
1224 | c.Assert(cli.setupPostalService(), IsNil) |
1225 | c.Assert(cli.startPostalService(), IsNil) |
1226 | c.Check(cli.postalService.IsRunning(), Equals, true) |
1227 | - cli.pushService.Stop() |
1228 | + pushService.Stop() |
1229 | cli.postalService.Stop() |
1230 | } |
1231 | |
1232 | +func (cs *clientSuite) TestStartServiceSetupError(c *C) { |
1233 | + cs.writeTestConfig(map[string]interface{}{ |
1234 | + "registration_url": "%gh", |
1235 | + }) |
1236 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1237 | + err := cli.configure() |
1238 | + c.Assert(err, IsNil) |
1239 | + err = cli.startService() |
1240 | + c.Check(err, ErrorMatches, "cannot parse registration url:.*") |
1241 | +} |
1242 | + |
1243 | func (cs *clientSuite) TestStartServiceErrorsOnNilLog(c *C) { |
1244 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1245 | c.Check(cli.log, IsNil) |
1246 | @@ -404,6 +511,10 @@ |
1247 | siCond := condition.Fail2Work(2) |
1248 | siEndp := testibus.NewMultiValuedTestingEndpoint(siCond, condition.Work(true), []interface{}{int32(101), "mako", "daily", "Unknown", map[string]string{}}) |
1249 | testibus.SetWatchTicker(cEndp, make(chan bool)) |
1250 | + ecCond := condition.Fail2Work(13) |
1251 | + ecEndp := testibus.NewTestingEndpoint(ecCond, condition.Work(true)) |
1252 | + haCond := condition.Fail2Work(2) |
1253 | + haEndp := testibus.NewTestingEndpoint(haCond, condition.Work(true)) |
1254 | // ok, create the thing |
1255 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1256 | cli.log = cs.log |
1257 | @@ -418,6 +529,8 @@ |
1258 | cli.notificationsEndp = nEndp |
1259 | cli.urlDispatcherEndp = uEndp |
1260 | cli.connectivityEndp = cEndp |
1261 | + cli.emblemcounterEndp = ecEndp |
1262 | + cli.hapticEndp = haEndp |
1263 | cli.systemImageEndp = siEndp |
1264 | |
1265 | c.Assert(cli.takeTheBus(), IsNil) |
1266 | @@ -434,6 +547,10 @@ |
1267 | c.Check(cCond.OK(), Equals, true) |
1268 | // the systemimage endpoint retried until connected |
1269 | c.Check(siCond.OK(), Equals, true) |
1270 | + // the emblemcounter endpoint retried until connected |
1271 | + c.Check(ecCond.OK(), Equals, true) |
1272 | + // the haptic endpoint retried until connected |
1273 | + c.Check(haCond.OK(), Equals, true) |
1274 | } |
1275 | |
1276 | // takeTheBus can, in fact, fail |
1277 | @@ -449,6 +566,8 @@ |
1278 | cli.notificationsEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
1279 | cli.urlDispatcherEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
1280 | cli.connectivityEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
1281 | + cli.emblemcounterEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
1282 | + cli.hapticEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
1283 | cli.systemImageEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
1284 | |
1285 | c.Check(cli.takeTheBus(), NotNil) |
1286 | @@ -654,7 +773,7 @@ |
1287 | args := testibus.GetCallArgs(endp) |
1288 | c.Assert(args, HasLen, 1) |
1289 | c.Check(args[0].Member, Equals, "Notify") |
1290 | - c.Check(cs.log.Captured(), Matches, `.* got notification id \d+\s*`) |
1291 | + c.Check(cs.log.Captured(), Matches, `(?s).* got notification id \d+\s*`) |
1292 | } |
1293 | |
1294 | func (cs *clientSuite) TestHandleBroadcastNotificationNothingToDo(c *C) { |
1295 | @@ -686,18 +805,13 @@ |
1296 | ******************************************************************/ |
1297 | |
1298 | var payload = `{"message": "aGVsbG8=", "notification": {"card": {"icon": "icon-value", "summary": "summary-value", "body": "body-value", "actions": []}}}` |
1299 | -var notif = &protocol.Notification{AppId: "hello", Payload: []byte(payload), MsgId: "42"} |
1300 | +var notif = &protocol.Notification{AppId: "com.example.test_hello", Payload: []byte(payload), MsgId: "42"} |
1301 | |
1302 | func (cs *clientSuite) TestHandleUcastNotification(c *C) { |
1303 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1304 | - svcEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1)) |
1305 | postEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1)) |
1306 | cli.log = cs.log |
1307 | - cli.pushServiceEndpoint = svcEndp |
1308 | cli.postalServiceEndpoint = postEndp |
1309 | - notsEndp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
1310 | - cli.notificationsEndp = notsEndp |
1311 | - c.Assert(cli.startService(), IsNil) |
1312 | c.Assert(cli.setupPostalService(), IsNil) |
1313 | c.Assert(cli.startPostalService(), IsNil) |
1314 | c.Check(cli.handleUnicastNotification(notif), IsNil) |
1315 | @@ -705,13 +819,22 @@ |
1316 | args := testibus.GetCallArgs(postEndp) |
1317 | c.Assert(len(args), Not(Equals), 0) |
1318 | c.Check(args[len(args)-1].Member, Equals, "::Signal") |
1319 | - c.Check(cs.log.Captured(), Matches, `(?m).*sending notification "42" for "hello".*`) |
1320 | + c.Check(cs.log.Captured(), Matches, `(?m).*sending notification "42" for "com.example.test_hello".*`) |
1321 | +} |
1322 | + |
1323 | +func (cs *clientSuite) TestHandleUcastFailsOnBadAppId(c *C) { |
1324 | + notif := &protocol.Notification{AppId: "bad-app-id", MsgId: "-1"} |
1325 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1326 | + cli.log = cs.log |
1327 | + c.Check(cli.handleUnicastNotification(notif), ErrorMatches, "invalid app id in notification") |
1328 | } |
1329 | |
1330 | /***************************************************************** |
1331 | handleClick tests |
1332 | ******************************************************************/ |
1333 | |
1334 | +var ACTION_ID_BROADCAST = service.ACTION_ID_PREFIX + service.SystemUpdateUrl + service.ACTION_ID_SUFFIX |
1335 | + |
1336 | func (cs *clientSuite) TestHandleClick(c *C) { |
1337 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1338 | cli.log = cs.log |
1339 | @@ -723,14 +846,14 @@ |
1340 | args := testibus.GetCallArgs(endp) |
1341 | c.Assert(args, HasLen, 0) |
1342 | // check we worked with the right action id |
1343 | - c.Check(cli.handleClick(service.ACTION_ID_BROADCAST), IsNil) |
1344 | + c.Check(cli.handleClick(ACTION_ID_BROADCAST), IsNil) |
1345 | // check we sent the notification |
1346 | args = testibus.GetCallArgs(endp) |
1347 | c.Assert(args, HasLen, 1) |
1348 | c.Check(args[0].Member, Equals, "DispatchURL") |
1349 | c.Check(args[0].Args, DeepEquals, []interface{}{service.SystemUpdateUrl}) |
1350 | // check we worked with the right action id |
1351 | - c.Check(cli.handleClick(service.ACTION_ID_SNOWFLAKE+"foo"), IsNil) |
1352 | + c.Check(cli.handleClick(service.ACTION_ID_PREFIX+"foo"), IsNil) |
1353 | // check we sent the notification |
1354 | args = testibus.GetCallArgs(endp) |
1355 | c.Assert(args, HasLen, 2) |
1356 | @@ -739,6 +862,64 @@ |
1357 | } |
1358 | |
1359 | /***************************************************************** |
1360 | + handleUnregister tests |
1361 | +******************************************************************/ |
1362 | + |
1363 | +type testPushService struct { |
1364 | + err error |
1365 | + unregistered string |
1366 | +} |
1367 | + |
1368 | +func (ps *testPushService) Start() error { |
1369 | + return nil |
1370 | +} |
1371 | + |
1372 | +func (ps *testPushService) Unregister(appId string) error { |
1373 | + ps.unregistered = appId |
1374 | + return ps.err |
1375 | +} |
1376 | + |
1377 | +func (cs *clientSuite) TestHandleUnregister(c *C) { |
1378 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1379 | + cli.log = cs.log |
1380 | + cli.hasPackage = func(appId string) bool { |
1381 | + c.Check(appId, Equals, "app1") |
1382 | + return false |
1383 | + } |
1384 | + ps := &testPushService{} |
1385 | + cli.pushService = ps |
1386 | + cli.handleUnregister("app1") |
1387 | + c.Assert(ps.unregistered, Equals, "app1") |
1388 | + c.Check(cs.log.Captured(), Equals, "") |
1389 | +} |
1390 | + |
1391 | +func (cs *clientSuite) TestHandleUnregisterNop(c *C) { |
1392 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1393 | + cli.log = cs.log |
1394 | + cli.hasPackage = func(appId string) bool { |
1395 | + c.Check(appId, Equals, "app1") |
1396 | + return true |
1397 | + } |
1398 | + ps := &testPushService{} |
1399 | + cli.pushService = ps |
1400 | + cli.handleUnregister("app1") |
1401 | + c.Assert(ps.unregistered, Equals, "") |
1402 | +} |
1403 | + |
1404 | +func (cs *clientSuite) TestHandleUnregisterError(c *C) { |
1405 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1406 | + cli.log = cs.log |
1407 | + cli.hasPackage = func(appId string) bool { |
1408 | + return false |
1409 | + } |
1410 | + fail := errors.New("BAD") |
1411 | + ps := &testPushService{err: fail} |
1412 | + cli.pushService = ps |
1413 | + cli.handleUnregister("app1") |
1414 | + c.Check(cs.log.Captured(), Matches, "ERROR unregistering app1: BAD\n") |
1415 | +} |
1416 | + |
1417 | +/***************************************************************** |
1418 | doLoop tests |
1419 | ******************************************************************/ |
1420 | |
1421 | @@ -747,6 +928,7 @@ |
1422 | var nopBcast = func(*session.BroadcastNotification) error { return nil } |
1423 | var nopUcast = func(*protocol.Notification) error { return nil } |
1424 | var nopError = func(error) {} |
1425 | +var nopUnregister = func(string) {} |
1426 | |
1427 | func (cs *clientSuite) TestDoLoopConn(c *C) { |
1428 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1429 | @@ -757,7 +939,7 @@ |
1430 | c.Assert(cli.initSession(), IsNil) |
1431 | |
1432 | ch := make(chan bool, 1) |
1433 | - go cli.doLoop(func(bool) { ch <- true }, nopClick, nopBcast, nopUcast, nopError) |
1434 | + go cli.doLoop(func(bool) { ch <- true }, nopClick, nopBcast, nopUcast, nopError, nopUnregister) |
1435 | c.Check(takeNextBool(ch), Equals, true) |
1436 | } |
1437 | |
1438 | @@ -771,7 +953,7 @@ |
1439 | cli.actionsCh = aCh |
1440 | |
1441 | ch := make(chan bool, 1) |
1442 | - go cli.doLoop(nopConn, func(_ string) error { ch <- true; return nil }, nopBcast, nopUcast, nopError) |
1443 | + go cli.doLoop(nopConn, func(_ string) error { ch <- true; return nil }, nopBcast, nopUcast, nopError, nopUnregister) |
1444 | c.Check(takeNextBool(ch), Equals, true) |
1445 | } |
1446 | |
1447 | @@ -784,7 +966,7 @@ |
1448 | cli.session.BroadcastCh <- &session.BroadcastNotification{} |
1449 | |
1450 | ch := make(chan bool, 1) |
1451 | - go cli.doLoop(nopConn, nopClick, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopError) |
1452 | + go cli.doLoop(nopConn, nopClick, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopError, nopUnregister) |
1453 | c.Check(takeNextBool(ch), Equals, true) |
1454 | } |
1455 | |
1456 | @@ -797,7 +979,7 @@ |
1457 | cli.session.NotificationsCh <- &protocol.Notification{} |
1458 | |
1459 | ch := make(chan bool, 1) |
1460 | - go cli.doLoop(nopConn, nopClick, nopBcast, func(*protocol.Notification) error { ch <- true; return nil }, nopError) |
1461 | + go cli.doLoop(nopConn, nopClick, nopBcast, func(*protocol.Notification) error { ch <- true; return nil }, nopError, nopUnregister) |
1462 | c.Check(takeNextBool(ch), Equals, true) |
1463 | } |
1464 | |
1465 | @@ -810,7 +992,20 @@ |
1466 | cli.session.ErrCh <- nil |
1467 | |
1468 | ch := make(chan bool, 1) |
1469 | - go cli.doLoop(nopConn, nopClick, nopBcast, nopUcast, func(error) { ch <- true }) |
1470 | + go cli.doLoop(nopConn, nopClick, nopBcast, nopUcast, func(error) { ch <- true }, nopUnregister) |
1471 | + c.Check(takeNextBool(ch), Equals, true) |
1472 | +} |
1473 | + |
1474 | +func (cs *clientSuite) TestDoLoopUnregister(c *C) { |
1475 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1476 | + cli.log = cs.log |
1477 | + cli.systemImageInfo = siInfoRes |
1478 | + c.Assert(cli.initSession(), IsNil) |
1479 | + cli.unregisterCh = make(chan string, 1) |
1480 | + cli.unregisterCh <- "app1" |
1481 | + |
1482 | + ch := make(chan bool, 1) |
1483 | + go cli.doLoop(nopConn, nopClick, nopBcast, nopUcast, nopError, func(appId string) { c.Check(appId, Equals, "app1"); ch <- true }) |
1484 | c.Check(takeNextBool(ch), Equals, true) |
1485 | } |
1486 | |
1487 | @@ -879,7 +1074,7 @@ |
1488 | c.Check(cs.log.Captured(), Matches, "(?ms).*Session connected after 42 attempts$") |
1489 | |
1490 | // * actionsCh to the click handler/url dispatcher |
1491 | - aCh <- notifications.RawActionReply{ActionId: service.ACTION_ID_BROADCAST} |
1492 | + aCh <- notifications.RawActionReply{ActionId: ACTION_ID_BROADCAST} |
1493 | tick() |
1494 | uargs := testibus.GetCallArgs(cli.urlDispatcherEndp) |
1495 | c.Assert(uargs, HasLen, 1) |
1496 | @@ -943,7 +1138,7 @@ |
1497 | // so we start, |
1498 | err := cli.Start() |
1499 | // and it works |
1500 | - c.Check(err, IsNil) |
1501 | + c.Assert(err, IsNil) |
1502 | |
1503 | // and now everthing is better! We have a config, |
1504 | c.Check(string(cli.config.Addr), Equals, ":0") |
1505 | @@ -956,8 +1151,8 @@ |
1506 | // and a service, |
1507 | c.Check(cli.pushService, NotNil) |
1508 | // and everthying us just peachy! |
1509 | - cli.pushService.Stop() // cleanup |
1510 | - cli.postalService.Stop() // cleanup |
1511 | + cli.pushService.(*service.PushService).Stop() // cleanup |
1512 | + cli.postalService.Stop() // cleanup |
1513 | } |
1514 | |
1515 | func (cs *clientSuite) TestStartCanFail(c *C) { |
1516 | |
1517 | === modified file 'client/service/common.go' |
1518 | --- client/service/common.go 2014-07-01 11:55:30 +0000 |
1519 | +++ client/service/common.go 2014-07-07 14:23:18 +0000 |
1520 | @@ -20,10 +20,13 @@ |
1521 | |
1522 | import ( |
1523 | "errors" |
1524 | + "strings" |
1525 | "sync" |
1526 | |
1527 | "launchpad.net/ubuntu-push/bus" |
1528 | + "launchpad.net/ubuntu-push/click" |
1529 | "launchpad.net/ubuntu-push/logger" |
1530 | + "launchpad.net/ubuntu-push/nih" |
1531 | ) |
1532 | |
1533 | type DBusService struct { |
1534 | @@ -43,10 +46,11 @@ |
1535 | ) |
1536 | |
1537 | var ( |
1538 | - NotConfigured = errors.New("not configured") |
1539 | - AlreadyStarted = errors.New("already started") |
1540 | - BadArgCount = errors.New("Wrong number of arguments") |
1541 | - BadArgType = errors.New("Bad argument type") |
1542 | + ErrNotConfigured = errors.New("not configured") |
1543 | + ErrAlreadyStarted = errors.New("already started") |
1544 | + ErrBadArgCount = errors.New("wrong number of arguments") |
1545 | + ErrBadArgType = errors.New("bad argument type") |
1546 | + ErrBadAppId = errors.New("package must be prefix of app id") |
1547 | ) |
1548 | |
1549 | // IsRunning() returns whether the service's state is StateRunning |
1550 | @@ -61,10 +65,10 @@ |
1551 | svc.lock.Lock() |
1552 | defer svc.lock.Unlock() |
1553 | if svc.state != StateUnknown { |
1554 | - return AlreadyStarted |
1555 | + return ErrAlreadyStarted |
1556 | } |
1557 | if svc.Log == nil || svc.Bus == nil { |
1558 | - return NotConfigured |
1559 | + return ErrNotConfigured |
1560 | } |
1561 | err := svc.Bus.Dial() |
1562 | if err != nil { |
1563 | @@ -97,3 +101,21 @@ |
1564 | } |
1565 | svc.state = StateFinished |
1566 | } |
1567 | + |
1568 | +// grabDBusPackageAndAppId() extracts the appId from a dbus-provided |
1569 | +// []interface{}, and checks it against the package in the last |
1570 | +// element of the dbus path. |
1571 | +func grabDBusPackageAndAppId(path string, args []interface{}, numExtra int) (pkgname string, appId string, err error) { |
1572 | + if len(args) != 1+numExtra { |
1573 | + return "", "", ErrBadArgCount |
1574 | + } |
1575 | + appId, ok := args[0].(string) |
1576 | + if !ok { |
1577 | + return "", "", ErrBadArgType |
1578 | + } |
1579 | + pkgname = string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:]))) |
1580 | + if !click.AppInPackage(appId, pkgname) { |
1581 | + return "", "", ErrBadAppId |
1582 | + } |
1583 | + return |
1584 | +} |
1585 | |
1586 | === added file 'client/service/common_test.go' |
1587 | --- client/service/common_test.go 1970-01-01 00:00:00 +0000 |
1588 | +++ client/service/common_test.go 2014-07-07 14:23:18 +0000 |
1589 | @@ -0,0 +1,60 @@ |
1590 | +/* |
1591 | + Copyright 2014 Canonical Ltd. |
1592 | + |
1593 | + This program is free software: you can redistribute it and/or modify it |
1594 | + under the terms of the GNU General Public License version 3, as published |
1595 | + by the Free Software Foundation. |
1596 | + |
1597 | + This program is distributed in the hope that it will be useful, but |
1598 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
1599 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1600 | + PURPOSE. See the GNU General Public License for more details. |
1601 | + |
1602 | + You should have received a copy of the GNU General Public License along |
1603 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
1604 | +*/ |
1605 | + |
1606 | +package service |
1607 | + |
1608 | +import ( |
1609 | + . "launchpad.net/gocheck" |
1610 | +) |
1611 | + |
1612 | +type commonSuite struct{} |
1613 | + |
1614 | +var _ = Suite(&commonSuite{}) |
1615 | + |
1616 | +func (cs *commonSuite) TestGrabDBusPackageAndAppIdWorks(c *C) { |
1617 | + aDBusPath := "/com/ubuntu/Postal/com_2eexample_2etest" |
1618 | + aPackage := "com.example.test" |
1619 | + anAppId := aPackage + "_test" |
1620 | + pkg, app, err := grabDBusPackageAndAppId(aDBusPath, []interface{}{anAppId}, 0) |
1621 | + c.Check(err, IsNil) |
1622 | + c.Check(pkg, Equals, aPackage) |
1623 | + c.Check(app, Equals, anAppId) |
1624 | +} |
1625 | + |
1626 | +func (cs *commonSuite) TestGrabDBusPackageAndAppIdFails(c *C) { |
1627 | + aDBusPath := "/com/ubuntu/Postal/com_2eexample_2etest" |
1628 | + aPackage := "com.example.test" |
1629 | + anAppId := aPackage + "_test" |
1630 | + |
1631 | + for i, s := range []struct { |
1632 | + path string |
1633 | + args []interface{} |
1634 | + numExtra int |
1635 | + errt error |
1636 | + }{ |
1637 | + {aDBusPath, []interface{}{}, 0, ErrBadArgCount}, |
1638 | + {aDBusPath, []interface{}{anAppId}, 1, ErrBadArgCount}, |
1639 | + {aDBusPath, []interface{}{anAppId, anAppId}, 0, ErrBadArgCount}, |
1640 | + {aDBusPath, []interface{}{1}, 0, ErrBadArgType}, |
1641 | + {aDBusPath, []interface{}{aPackage}, 0, ErrBadAppId}, |
1642 | + } { |
1643 | + comment := Commentf("iteration #%d", i) |
1644 | + pkg, app, err := grabDBusPackageAndAppId(s.path, s.args, s.numExtra) |
1645 | + c.Check(err, Equals, s.errt, comment) |
1646 | + c.Check(pkg, Equals, "", comment) |
1647 | + c.Check(app, Equals, "", comment) |
1648 | + } |
1649 | +} |
1650 | |
1651 | === modified file 'client/service/postal.go' |
1652 | --- client/service/postal.go 2014-07-01 11:55:30 +0000 |
1653 | +++ client/service/postal.go 2014-07-07 14:23:18 +0000 |
1654 | @@ -17,17 +17,19 @@ |
1655 | package service |
1656 | |
1657 | import ( |
1658 | - "strings" |
1659 | + "sync" |
1660 | |
1661 | "code.google.com/p/go-uuid/uuid" |
1662 | - "launchpad.net/go-dbus/v1" |
1663 | |
1664 | "launchpad.net/ubuntu-push/bus" |
1665 | + "launchpad.net/ubuntu-push/bus/emblemcounter" |
1666 | + "launchpad.net/ubuntu-push/bus/haptic" |
1667 | "launchpad.net/ubuntu-push/bus/notifications" |
1668 | "launchpad.net/ubuntu-push/launch_helper" |
1669 | "launchpad.net/ubuntu-push/logger" |
1670 | "launchpad.net/ubuntu-push/messaging" |
1671 | "launchpad.net/ubuntu-push/nih" |
1672 | + "launchpad.net/ubuntu-push/sounds" |
1673 | "launchpad.net/ubuntu-push/util" |
1674 | ) |
1675 | |
1676 | @@ -38,6 +40,8 @@ |
1677 | msgHandler func(string, string, *launch_helper.HelperOutput) error |
1678 | HelperLauncher launch_helper.HelperLauncher |
1679 | messagingMenu *messaging.MessagingMenu |
1680 | + emblemcounterEndp bus.Endpoint |
1681 | + hapticEndp bus.Endpoint |
1682 | notificationsEndp bus.Endpoint |
1683 | } |
1684 | |
1685 | @@ -50,19 +54,21 @@ |
1686 | ) |
1687 | |
1688 | var ( |
1689 | - SystemUpdateUrl = "settings:///system/system-update" |
1690 | - ACTION_ID_SNOWFLAKE = "::ubuntu-push-client::" |
1691 | - ACTION_ID_BROADCAST = ACTION_ID_SNOWFLAKE + SystemUpdateUrl |
1692 | + SystemUpdateUrl = "settings:///system/system-update" |
1693 | + ACTION_ID_PREFIX = "ubuntu-push-client::" |
1694 | + ACTION_ID_SUFFIX = "::0" |
1695 | ) |
1696 | |
1697 | // NewPostalService() builds a new service and returns it. |
1698 | -func NewPostalService(busEndp bus.Endpoint, notificationsEndp bus.Endpoint, log logger.Logger) *PostalService { |
1699 | +func NewPostalService(busEndp bus.Endpoint, notificationsEndp bus.Endpoint, emblemcounterEndp bus.Endpoint, hapticEndp bus.Endpoint, log logger.Logger) *PostalService { |
1700 | var svc = &PostalService{} |
1701 | svc.Log = log |
1702 | svc.Bus = busEndp |
1703 | svc.messagingMenu = messaging.New(log) |
1704 | svc.HelperLauncher = launch_helper.NewTrivialHelperLauncher(log) |
1705 | svc.notificationsEndp = notificationsEndp |
1706 | + svc.emblemcounterEndp = emblemcounterEndp |
1707 | + svc.hapticEndp = hapticEndp |
1708 | svc.msgHandler = svc.messageHandler |
1709 | return svc |
1710 | } |
1711 | @@ -84,24 +90,36 @@ |
1712 | // Start() dials the bus, grab the name, and listens for method calls. |
1713 | func (svc *PostalService) Start() error { |
1714 | return svc.DBusService.Start(bus.DispatchMap{ |
1715 | - "Notifications": svc.notifications, |
1716 | - "Inject": svc.inject, |
1717 | + "Messages": svc.notifications, |
1718 | + "Post": svc.inject, |
1719 | }, PostalServiceBusAddress) |
1720 | } |
1721 | |
1722 | func (svc *PostalService) TakeTheBus() (<-chan notifications.RawActionReply, error) { |
1723 | - iniCh := make(chan uint32) |
1724 | - go func() { iniCh <- util.NewAutoRedialer(svc.notificationsEndp).Redial() }() |
1725 | - <-iniCh |
1726 | + var wg sync.WaitGroup |
1727 | + endps := []bus.Endpoint{ |
1728 | + svc.notificationsEndp, |
1729 | + svc.emblemcounterEndp, |
1730 | + svc.hapticEndp, |
1731 | + } |
1732 | + wg.Add(len(endps)) |
1733 | + for _, endp := range endps { |
1734 | + go func(endp bus.Endpoint) { |
1735 | + util.NewAutoRedialer(endp).Redial() |
1736 | + wg.Done() |
1737 | + }(endp) |
1738 | + } |
1739 | + wg.Wait() |
1740 | actionsCh, err := notifications.Raw(svc.notificationsEndp, svc.Log).WatchActions() |
1741 | + |
1742 | return actionsCh, err |
1743 | } |
1744 | |
1745 | func (svc *PostalService) notifications(path string, args, _ []interface{}) ([]interface{}, error) { |
1746 | - if len(args) != 0 { |
1747 | - return nil, BadArgCount |
1748 | + _, appId, err := grabDBusPackageAndAppId(path, args, 0) |
1749 | + if err != nil { |
1750 | + return nil, err |
1751 | } |
1752 | - appname := string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:]))) |
1753 | |
1754 | svc.lock.Lock() |
1755 | defer svc.lock.Unlock() |
1756 | @@ -109,8 +127,8 @@ |
1757 | if svc.mbox == nil { |
1758 | return []interface{}{[]string(nil)}, nil |
1759 | } |
1760 | - msgs := svc.mbox[appname] |
1761 | - delete(svc.mbox, appname) |
1762 | + msgs := svc.mbox[appId] |
1763 | + delete(svc.mbox, appId) |
1764 | |
1765 | return []interface{}{msgs}, nil |
1766 | } |
1767 | @@ -118,23 +136,23 @@ |
1768 | var newNid = uuid.New |
1769 | |
1770 | func (svc *PostalService) inject(path string, args, _ []interface{}) ([]interface{}, error) { |
1771 | - if len(args) != 1 { |
1772 | - return nil, BadArgCount |
1773 | + pkg, appId, err := grabDBusPackageAndAppId(path, args, 1) |
1774 | + if err != nil { |
1775 | + return nil, err |
1776 | } |
1777 | - notif, ok := args[0].(string) |
1778 | + notif, ok := args[1].(string) |
1779 | if !ok { |
1780 | - return nil, BadArgType |
1781 | + return nil, ErrBadArgType |
1782 | } |
1783 | - appname := string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:]))) |
1784 | |
1785 | nid := newNid() |
1786 | |
1787 | - return nil, svc.Inject(appname, nid, notif) |
1788 | + return nil, svc.Inject(pkg, appId, nid, notif) |
1789 | } |
1790 | |
1791 | // Inject() signals to an application over dbus that a notification |
1792 | // has arrived. |
1793 | -func (svc *PostalService) Inject(appname string, nid string, notif string) error { |
1794 | +func (svc *PostalService) Inject(pkgname string, appname string, nid string, notif string) error { |
1795 | svc.lock.Lock() |
1796 | defer svc.lock.Unlock() |
1797 | if svc.mbox == nil { |
1798 | @@ -153,29 +171,28 @@ |
1799 | svc.DBusService.Log.Debugf("call to msgHandler successful") |
1800 | } |
1801 | |
1802 | - return svc.Bus.Signal("Notification", "/"+string(nih.Quote([]byte(appname))), []interface{}{appname}) |
1803 | + return svc.Bus.Signal("Post", "/"+string(nih.Quote([]byte(pkgname))), []interface{}{appname}) |
1804 | } |
1805 | |
1806 | func (svc *PostalService) messageHandler(appname string, nid string, output *launch_helper.HelperOutput) error { |
1807 | svc.messagingMenu.Present(appname, nid, output.Notification) |
1808 | nots := notifications.Raw(svc.notificationsEndp, svc.Log) |
1809 | _, err := nots.Present(appname, nid, output.Notification) |
1810 | + emblemcounter.New(svc.emblemcounterEndp, svc.Log).Present(appname, nid, output.Notification) |
1811 | + haptic.New(svc.hapticEndp, svc.Log).Present(appname, nid, output.Notification) |
1812 | + sounds.New(svc.Log).Present(appname, nid, output.Notification) |
1813 | |
1814 | return err |
1815 | } |
1816 | |
1817 | -func (svc *PostalService) SendNotification(action_id, icon, summary, body string) (uint32, error) { |
1818 | - a := []string{action_id, "Switch to app"} // action value not visible on the phone |
1819 | - h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}} |
1820 | - nots := notifications.Raw(svc.notificationsEndp, svc.Log) |
1821 | - return nots.Notify( |
1822 | - "ubuntu-push-client", // app name |
1823 | - uint32(0), // id |
1824 | - icon, // icon |
1825 | - summary, // summary |
1826 | - body, // body |
1827 | - a, // actions |
1828 | - h, // hints |
1829 | - int32(10*1000), // timeout (ms) |
1830 | - ) |
1831 | +func (svc *PostalService) InjectBroadcast() (uint32, error) { |
1832 | + // XXX: call a helper? |
1833 | + // XXX: Present force us to send the url as the notificationId |
1834 | + icon := "update_manager_icon" |
1835 | + summary := "There's an updated system image." |
1836 | + body := "Tap to open the system updater." |
1837 | + actions := []string{"Switch to app"} // action value not visible on the phone |
1838 | + card := &launch_helper.Card{Icon: icon, Summary: summary, Body: body, Actions: actions, Popup: true} |
1839 | + output := &launch_helper.HelperOutput{[]byte(""), &launch_helper.Notification{Card: card}} |
1840 | + return 0, svc.msgHandler("ubuntu-push-client", SystemUpdateUrl, output) |
1841 | } |
1842 | |
1843 | === modified file 'client/service/postal_test.go' |
1844 | --- client/service/postal_test.go 2014-07-01 11:55:30 +0000 |
1845 | +++ client/service/postal_test.go 2014-07-07 14:23:18 +0000 |
1846 | @@ -18,6 +18,7 @@ |
1847 | |
1848 | import ( |
1849 | "errors" |
1850 | + "sort" |
1851 | |
1852 | . "launchpad.net/gocheck" |
1853 | |
1854 | @@ -29,9 +30,11 @@ |
1855 | ) |
1856 | |
1857 | type postalSuite struct { |
1858 | - log *helpers.TestLogger |
1859 | - bus bus.Endpoint |
1860 | - notifBus bus.Endpoint |
1861 | + log *helpers.TestLogger |
1862 | + bus bus.Endpoint |
1863 | + notifBus bus.Endpoint |
1864 | + counterBus bus.Endpoint |
1865 | + hapticBus bus.Endpoint |
1866 | } |
1867 | |
1868 | var _ = Suite(&postalSuite{}) |
1869 | @@ -40,10 +43,12 @@ |
1870 | ss.log = helpers.NewTestLogger(c, "debug") |
1871 | ss.bus = testibus.NewTestingEndpoint(condition.Work(true), nil) |
1872 | ss.notifBus = testibus.NewTestingEndpoint(condition.Work(true), nil) |
1873 | + ss.counterBus = testibus.NewTestingEndpoint(condition.Work(true), nil) |
1874 | + ss.hapticBus = testibus.NewTestingEndpoint(condition.Work(true), nil) |
1875 | } |
1876 | |
1877 | func (ss *postalSuite) TestStart(c *C) { |
1878 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.log) |
1879 | + svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
1880 | c.Check(svc.IsRunning(), Equals, false) |
1881 | c.Check(svc.Start(), IsNil) |
1882 | c.Check(svc.IsRunning(), Equals, true) |
1883 | @@ -51,45 +56,45 @@ |
1884 | } |
1885 | |
1886 | func (ss *postalSuite) TestStartTwice(c *C) { |
1887 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.log) |
1888 | + svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
1889 | c.Check(svc.Start(), IsNil) |
1890 | - c.Check(svc.Start(), Equals, AlreadyStarted) |
1891 | + c.Check(svc.Start(), Equals, ErrAlreadyStarted) |
1892 | svc.Stop() |
1893 | } |
1894 | |
1895 | func (ss *postalSuite) TestStartNoLog(c *C) { |
1896 | - svc := NewPostalService(ss.bus, ss.notifBus, nil) |
1897 | - c.Check(svc.Start(), Equals, NotConfigured) |
1898 | + svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, nil) |
1899 | + c.Check(svc.Start(), Equals, ErrNotConfigured) |
1900 | } |
1901 | |
1902 | func (ss *postalSuite) TestStartNoBus(c *C) { |
1903 | - svc := NewPostalService(nil, ss.notifBus, ss.log) |
1904 | - c.Check(svc.Start(), Equals, NotConfigured) |
1905 | + svc := NewPostalService(nil, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
1906 | + c.Check(svc.Start(), Equals, ErrNotConfigured) |
1907 | } |
1908 | |
1909 | func (ss *postalSuite) TestTakeTheBustFail(c *C) { |
1910 | nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(false), []interface{}{uint32(1), "hello"}) |
1911 | - svc := NewPostalService(ss.bus, nEndp, ss.log) |
1912 | + svc := NewPostalService(ss.bus, nEndp, ss.counterBus, ss.hapticBus, ss.log) |
1913 | _, err := svc.TakeTheBus() |
1914 | c.Check(err, NotNil) |
1915 | } |
1916 | |
1917 | func (ss *postalSuite) TestTakeTheBustOk(c *C) { |
1918 | nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(true), []interface{}{uint32(1), "hello"}) |
1919 | - svc := NewPostalService(ss.bus, nEndp, ss.log) |
1920 | + svc := NewPostalService(ss.bus, nEndp, ss.counterBus, ss.hapticBus, ss.log) |
1921 | _, err := svc.TakeTheBus() |
1922 | c.Check(err, IsNil) |
1923 | } |
1924 | |
1925 | func (ss *postalSuite) TestStartFailsOnBusDialFailure(c *C) { |
1926 | bus := testibus.NewTestingEndpoint(condition.Work(false), nil) |
1927 | - svc := NewPostalService(bus, ss.notifBus, ss.log) |
1928 | + svc := NewPostalService(bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
1929 | c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`) |
1930 | svc.Stop() |
1931 | } |
1932 | |
1933 | func (ss *postalSuite) TestStartGrabsName(c *C) { |
1934 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.log) |
1935 | + svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
1936 | c.Assert(svc.Start(), IsNil) |
1937 | callArgs := testibus.GetCallArgs(ss.bus) |
1938 | defer svc.Stop() |
1939 | @@ -98,7 +103,7 @@ |
1940 | } |
1941 | |
1942 | func (ss *postalSuite) TestStopClosesBus(c *C) { |
1943 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.log) |
1944 | + svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
1945 | c.Assert(svc.Start(), IsNil) |
1946 | svc.Stop() |
1947 | callArgs := testibus.GetCallArgs(ss.bus) |
1948 | @@ -110,31 +115,31 @@ |
1949 | // Injection tests |
1950 | |
1951 | func (ss *postalSuite) TestInjectWorks(c *C) { |
1952 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.log) |
1953 | + svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
1954 | svc.msgHandler = nil |
1955 | - rvs, err := svc.inject("/hello", []interface{}{"world"}, nil) |
1956 | + rvs, err := svc.inject(aPackageOnBus, []interface{}{anAppId, "world"}, nil) |
1957 | c.Assert(err, IsNil) |
1958 | c.Check(rvs, IsNil) |
1959 | - rvs, err = svc.inject("/hello", []interface{}{"there"}, nil) |
1960 | + rvs, err = svc.inject(aPackageOnBus, []interface{}{anAppId, "there"}, nil) |
1961 | c.Assert(err, IsNil) |
1962 | c.Check(rvs, IsNil) |
1963 | c.Assert(svc.mbox, HasLen, 1) |
1964 | - c.Assert(svc.mbox["hello"], HasLen, 2) |
1965 | - c.Check(svc.mbox["hello"][0], Equals, "world") |
1966 | - c.Check(svc.mbox["hello"][1], Equals, "there") |
1967 | + c.Assert(svc.mbox[anAppId], HasLen, 2) |
1968 | + c.Check(svc.mbox[anAppId][0], Equals, "world") |
1969 | + c.Check(svc.mbox[anAppId][1], Equals, "there") |
1970 | |
1971 | // and check it fired the right signal (twice) |
1972 | callArgs := testibus.GetCallArgs(ss.bus) |
1973 | c.Assert(callArgs, HasLen, 2) |
1974 | c.Check(callArgs[0].Member, Equals, "::Signal") |
1975 | - c.Check(callArgs[0].Args, DeepEquals, []interface{}{"Notification", "/hello", []interface{}{"hello"}}) |
1976 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{"Post", aPackageOnBus, []interface{}{anAppId}}) |
1977 | c.Check(callArgs[1], DeepEquals, callArgs[0]) |
1978 | } |
1979 | |
1980 | func (ss *postalSuite) TestInjectFailsIfInjectFails(c *C) { |
1981 | bus := testibus.NewTestingEndpoint(condition.Work(true), |
1982 | condition.Work(false)) |
1983 | - svc := NewPostalService(bus, ss.notifBus, ss.log) |
1984 | + svc := NewPostalService(bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
1985 | svc.SetMessageHandler(func(string, string, *launch_helper.HelperOutput) error { return errors.New("fail") }) |
1986 | _, err := svc.inject("/hello", []interface{}{"xyzzy"}, nil) |
1987 | c.Check(err, NotNil) |
1988 | @@ -145,22 +150,57 @@ |
1989 | args []interface{} |
1990 | errt error |
1991 | }{ |
1992 | - {nil, BadArgCount}, |
1993 | - {[]interface{}{}, BadArgCount}, |
1994 | - {[]interface{}{1}, BadArgType}, |
1995 | - {[]interface{}{1, 2}, BadArgCount}, |
1996 | + {nil, ErrBadArgCount}, |
1997 | + {[]interface{}{}, ErrBadArgCount}, |
1998 | + {[]interface{}{1}, ErrBadArgCount}, |
1999 | + {[]interface{}{anAppId, 1}, ErrBadArgType}, |
2000 | + {[]interface{}{1, "hello"}, ErrBadArgType}, |
2001 | + {[]interface{}{1, 2, 3}, ErrBadArgCount}, |
2002 | + {[]interface{}{"bar", "hello"}, ErrBadAppId}, |
2003 | } { |
2004 | - reg, err := new(PostalService).inject("", s.args, nil) |
2005 | + reg, err := new(PostalService).inject(aPackageOnBus, s.args, nil) |
2006 | c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
2007 | c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
2008 | } |
2009 | } |
2010 | |
2011 | // |
2012 | +// Injection (Broadcast) tests |
2013 | + |
2014 | +func (ss *postalSuite) TestInjectBroadcast(c *C) { |
2015 | + bus := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
2016 | + svc := NewPostalService(ss.bus, bus, ss.counterBus, ss.hapticBus, ss.log) |
2017 | + //svc.msgHandler = nil |
2018 | + rvs, err := svc.InjectBroadcast() |
2019 | + c.Assert(err, IsNil) |
2020 | + c.Check(rvs, Equals, uint32(0)) |
2021 | + c.Assert(err, IsNil) |
2022 | + // and check it fired the right signal (twice) |
2023 | + callArgs := testibus.GetCallArgs(bus) |
2024 | + c.Assert(callArgs, HasLen, 1) |
2025 | + c.Check(callArgs[0].Member, Equals, "Notify") |
2026 | + c.Check(callArgs[0].Args[0:6], DeepEquals, []interface{}{"ubuntu-push-client", uint32(0), "update_manager_icon", |
2027 | + "There's an updated system image.", "Tap to open the system updater.", |
2028 | + []string{"ubuntu-push-client::settings:///system/system-update::0", "Switch to app"}}) |
2029 | + // TODO: check the map in callArgs? |
2030 | + // c.Check(callArgs[0].Args[7]["x-canonical-secondary-icon"], NotNil) |
2031 | + // c.Check(callArgs[0].Args[7]["x-canonical-snap-decisions"], NotNil) |
2032 | +} |
2033 | + |
2034 | +func (ss *postalSuite) TestInjectBroadcastFails(c *C) { |
2035 | + bus := testibus.NewTestingEndpoint(condition.Work(true), |
2036 | + condition.Work(false)) |
2037 | + svc := NewPostalService(ss.bus, bus, ss.counterBus, ss.hapticBus, ss.log) |
2038 | + svc.SetMessageHandler(func(string, string, *launch_helper.HelperOutput) error { return errors.New("fail") }) |
2039 | + _, err := svc.InjectBroadcast() |
2040 | + c.Check(err, NotNil) |
2041 | +} |
2042 | + |
2043 | +// |
2044 | // Notifications tests |
2045 | func (ss *postalSuite) TestNotificationsWorks(c *C) { |
2046 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.log) |
2047 | - nots, err := svc.notifications("/hello", nil, nil) |
2048 | + svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
2049 | + nots, err := svc.notifications(aPackageOnBus, []interface{}{anAppId}, nil) |
2050 | c.Assert(err, IsNil) |
2051 | c.Assert(nots, NotNil) |
2052 | c.Assert(nots, HasLen, 1) |
2053 | @@ -168,8 +208,8 @@ |
2054 | if svc.mbox == nil { |
2055 | svc.mbox = make(map[string][]string) |
2056 | } |
2057 | - svc.mbox["hello"] = append(svc.mbox["hello"], "this", "thing") |
2058 | - nots, err = svc.notifications("/hello", nil, nil) |
2059 | + svc.mbox[anAppId] = append(svc.mbox[anAppId], "this", "thing") |
2060 | + nots, err = svc.notifications(aPackageOnBus, []interface{}{anAppId}, nil) |
2061 | c.Assert(err, IsNil) |
2062 | c.Assert(nots, NotNil) |
2063 | c.Assert(nots, HasLen, 1) |
2064 | @@ -177,9 +217,19 @@ |
2065 | } |
2066 | |
2067 | func (ss *postalSuite) TestNotificationsFailsIfBadArgs(c *C) { |
2068 | - reg, err := new(PostalService).notifications("/foo", []interface{}{1}, nil) |
2069 | - c.Check(reg, IsNil) |
2070 | - c.Check(err, Equals, BadArgCount) |
2071 | + for i, s := range []struct { |
2072 | + args []interface{} |
2073 | + errt error |
2074 | + }{ |
2075 | + {nil, ErrBadArgCount}, |
2076 | + {[]interface{}{}, ErrBadArgCount}, |
2077 | + {[]interface{}{1}, ErrBadArgType}, |
2078 | + {[]interface{}{"potato"}, ErrBadAppId}, |
2079 | + } { |
2080 | + reg, err := new(PostalService).notifications(aPackageOnBus, s.args, nil) |
2081 | + c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
2082 | + c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
2083 | + } |
2084 | } |
2085 | |
2086 | func (ss *postalSuite) TestMessageHandlerPublicAPI(c *C) { |
2087 | @@ -198,35 +248,44 @@ |
2088 | |
2089 | func (ss *postalSuite) TestInjectCallsMessageHandler(c *C) { |
2090 | var ext = &launch_helper.HelperOutput{} |
2091 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.log) |
2092 | + svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
2093 | f := func(app string, nid string, s *launch_helper.HelperOutput) error { ext = s; return nil } |
2094 | svc.SetMessageHandler(f) |
2095 | - c.Check(svc.Inject("stuff", "thing", "{}"), IsNil) |
2096 | + c.Check(svc.Inject("pkg", "app", "thing", "{}"), IsNil) |
2097 | c.Check(ext, DeepEquals, &launch_helper.HelperOutput{}) |
2098 | err := errors.New("ouch") |
2099 | svc.SetMessageHandler(func(string, string, *launch_helper.HelperOutput) error { return err }) |
2100 | - c.Check(svc.Inject("stuff", "", "{}"), Equals, err) |
2101 | + c.Check(svc.Inject("pkg", "app", "", "{}"), Equals, err) |
2102 | } |
2103 | |
2104 | -func (ss *postalSuite) TestMessageHandler(c *C) { |
2105 | +func (ss *postalSuite) TestMessageHandlerPresents(c *C) { |
2106 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
2107 | - svc := NewPostalService(ss.bus, endp, ss.log) |
2108 | - card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true} |
2109 | - output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card}} |
2110 | - err := svc.messageHandler("xyzzy", "", output) |
2111 | + svc := NewPostalService(endp, endp, endp, endp, ss.log) |
2112 | + // Persist is false so we just check the log |
2113 | + card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true, Persist: false} |
2114 | + vib := &launch_helper.Vibration{Duration: 500} |
2115 | + emb := &launch_helper.EmblemCounter{Count: 2, Visible: true} |
2116 | + output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card, EmblemCounter: emb, Vibrate: vib}} |
2117 | + err := svc.messageHandler("com.example.test_test", "", output) |
2118 | c.Assert(err, IsNil) |
2119 | args := testibus.GetCallArgs(endp) |
2120 | - c.Assert(args, HasLen, 1) |
2121 | - c.Check(args[0].Member, Equals, "Notify") |
2122 | - c.Check(args[0].Args[0], Equals, "xyzzy") |
2123 | - c.Check(args[0].Args[2], Equals, "icon-value") |
2124 | - c.Check(args[0].Args[3], Equals, "summary-value") |
2125 | - c.Check(args[0].Args[4], Equals, "body-value") |
2126 | + c.Assert(args, HasLen, 4) |
2127 | + mm := make([]string, len(args)) |
2128 | + for i, m := range args { |
2129 | + mm[i] = m.Member |
2130 | + } |
2131 | + sort.Strings(mm) |
2132 | + // check the Present() methods were called. |
2133 | + // For dbus-backed presenters, just check the right dbus methods are called |
2134 | + c.Check(mm, DeepEquals, []string{"::SetProperty", "::SetProperty", "Notify", "VibratePattern"}) |
2135 | + // For the other ones, check the logs |
2136 | + c.Check(ss.log.Captured(), Matches, `(?sm).* no persistable card:.*`) |
2137 | + c.Check(ss.log.Captured(), Matches, `(?sm).* no Sound in the notification.*`) |
2138 | } |
2139 | |
2140 | func (ss *postalSuite) TestMessageHandlerReportsFailedNotifies(c *C) { |
2141 | endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) |
2142 | - svc := NewPostalService(ss.bus, endp, ss.log) |
2143 | + svc := NewPostalService(ss.bus, endp, ss.counterBus, ss.hapticBus, ss.log) |
2144 | card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true} |
2145 | notif := &launch_helper.Notification{Card: card} |
2146 | output := &launch_helper.HelperOutput{Notification: notif} |
2147 | @@ -235,7 +294,7 @@ |
2148 | } |
2149 | |
2150 | func (ss *postalSuite) TestMessageHandlerReportsButIgnoresUnmarshalErrors(c *C) { |
2151 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.log) |
2152 | + svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
2153 | output := &launch_helper.HelperOutput{[]byte(`broken`), nil} |
2154 | err := svc.messageHandler("", "", output) |
2155 | c.Check(err, IsNil) |
2156 | @@ -244,7 +303,7 @@ |
2157 | |
2158 | func (ss *postalSuite) TestMessageHandlerReportsButIgnoresNilNotifies(c *C) { |
2159 | endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) |
2160 | - svc := NewPostalService(ss.bus, endp, ss.log) |
2161 | + svc := NewPostalService(ss.bus, endp, ss.counterBus, ss.hapticBus, ss.log) |
2162 | output := &launch_helper.HelperOutput{[]byte(`{}`), nil} |
2163 | err := svc.messageHandler("", "", output) |
2164 | c.Assert(err, IsNil) |
2165 | |
2166 | === modified file 'client/service/service.go' |
2167 | --- client/service/service.go 2014-07-01 11:55:30 +0000 |
2168 | +++ client/service/service.go 2014-07-07 14:23:18 +0000 |
2169 | @@ -23,8 +23,8 @@ |
2170 | "fmt" |
2171 | "io/ioutil" |
2172 | "net/http" |
2173 | + "net/url" |
2174 | "os" |
2175 | - "strings" |
2176 | |
2177 | "launchpad.net/ubuntu-push/bus" |
2178 | http13 "launchpad.net/ubuntu-push/http13client" |
2179 | @@ -32,10 +32,17 @@ |
2180 | "launchpad.net/ubuntu-push/nih" |
2181 | ) |
2182 | |
2183 | +// PushServiceSetup encapsulates the params for setting up a PushService. |
2184 | +type PushServiceSetup struct { |
2185 | + RegURL *url.URL |
2186 | + DeviceId string |
2187 | + AuthGetter func(string) string |
2188 | +} |
2189 | + |
2190 | // PushService is the dbus api |
2191 | type PushService struct { |
2192 | DBusService |
2193 | - regURL string |
2194 | + regURL *url.URL |
2195 | deviceId string |
2196 | authGetter func(string) string |
2197 | httpCli http13.Client |
2198 | @@ -50,72 +57,42 @@ |
2199 | ) |
2200 | |
2201 | // NewPushService() builds a new service and returns it. |
2202 | -func NewPushService(bus bus.Endpoint, log logger.Logger) *PushService { |
2203 | +func NewPushService(bus bus.Endpoint, setup *PushServiceSetup, log logger.Logger) *PushService { |
2204 | var svc = &PushService{} |
2205 | svc.Log = log |
2206 | svc.Bus = bus |
2207 | + svc.regURL = setup.RegURL |
2208 | + svc.deviceId = setup.DeviceId |
2209 | + svc.authGetter = setup.AuthGetter |
2210 | return svc |
2211 | } |
2212 | |
2213 | -// SetRegistrationURL() sets the registration url for the service |
2214 | -func (svc *PushService) SetRegistrationURL(url string) { |
2215 | - svc.lock.Lock() |
2216 | - defer svc.lock.Unlock() |
2217 | - svc.regURL = url |
2218 | -} |
2219 | - |
2220 | -// SetAuthGetter() sets the authorization getter for the service |
2221 | -func (svc *PushService) SetAuthGetter(authGetter func(string) string) { |
2222 | - svc.lock.Lock() |
2223 | - defer svc.lock.Unlock() |
2224 | - svc.authGetter = authGetter |
2225 | -} |
2226 | - |
2227 | -// getRegistrationAuthorization() returns the authorization header for |
2228 | -// POSTing to the registration HTTP endpoint |
2229 | -// |
2230 | -// (this is for calling with the lock held) |
2231 | -func (svc *PushService) getRegistrationAuthorization() string { |
2232 | - if svc.authGetter != nil && svc.regURL != "" { |
2233 | - return svc.authGetter(svc.regURL) |
2234 | - } else { |
2235 | - return "" |
2236 | - } |
2237 | -} |
2238 | - |
2239 | -// GetRegistrationAuthorization() returns the authorization header for |
2240 | -// POSTing to the registration HTTP endpoint |
2241 | -func (svc *PushService) GetRegistrationAuthorization() string { |
2242 | - svc.lock.RLock() |
2243 | - defer svc.lock.RUnlock() |
2244 | - return svc.getRegistrationAuthorization() |
2245 | -} |
2246 | - |
2247 | -// SetDeviceId() sets the device id |
2248 | -func (svc *PushService) SetDeviceId(deviceId string) { |
2249 | - svc.lock.Lock() |
2250 | - defer svc.lock.Unlock() |
2251 | - svc.deviceId = deviceId |
2252 | -} |
2253 | - |
2254 | -// GetDeviceId() returns the device id |
2255 | -func (svc *PushService) GetDeviceId() string { |
2256 | - svc.lock.RLock() |
2257 | - defer svc.lock.RUnlock() |
2258 | - return svc.deviceId |
2259 | +// getAuthorization() returns the URL and the authorization header for |
2260 | +// POSTing to the registration HTTP endpoint for op |
2261 | +func (svc *PushService) getAuthorization(op string) (string, string) { |
2262 | + if svc.authGetter == nil || svc.regURL == nil { |
2263 | + return "", "" |
2264 | + } |
2265 | + purl, err := svc.regURL.Parse(op) |
2266 | + if err != nil { |
2267 | + panic("op to getAuthorization was invalid") |
2268 | + } |
2269 | + url := purl.String() |
2270 | + return url, svc.authGetter(url) |
2271 | } |
2272 | |
2273 | func (svc *PushService) Start() error { |
2274 | return svc.DBusService.Start(bus.DispatchMap{ |
2275 | - "Register": svc.register, |
2276 | + "Register": svc.register, |
2277 | + "Unregister": svc.unregister, |
2278 | }, PushServiceBusAddress) |
2279 | } |
2280 | |
2281 | var ( |
2282 | - BadServer = errors.New("Bad server") |
2283 | - BadRequest = errors.New("Bad request") |
2284 | - BadToken = errors.New("Bad token") |
2285 | - BadAuth = errors.New("Bad auth") |
2286 | + ErrBadServer = errors.New("bad server") |
2287 | + ErrBadRequest = errors.New("bad request") |
2288 | + ErrBadToken = errors.New("bad token") |
2289 | + ErrBadAuth = errors.New("bad auth") |
2290 | ) |
2291 | |
2292 | type registrationRequest struct { |
2293 | @@ -130,31 +107,20 @@ |
2294 | Message string `json:"message"` // |
2295 | } |
2296 | |
2297 | -func (svc *PushService) register(path string, args, _ []interface{}) ([]interface{}, error) { |
2298 | - svc.lock.RLock() |
2299 | - defer svc.lock.RUnlock() |
2300 | - if len(args) != 0 { |
2301 | - return nil, BadArgCount |
2302 | - } |
2303 | - raw_appname := path[strings.LastIndex(path, "/")+1:] |
2304 | - appname := string(nih.Unquote([]byte(raw_appname))) |
2305 | - |
2306 | - rv := os.Getenv("PUSH_REG_" + raw_appname) |
2307 | - if rv != "" { |
2308 | - return []interface{}{rv}, nil |
2309 | - } |
2310 | - |
2311 | - req_body, err := json.Marshal(registrationRequest{svc.deviceId, appname}) |
2312 | +func (svc *PushService) manageReg(op, appId string) (*registrationReply, error) { |
2313 | + req_body, err := json.Marshal(registrationRequest{svc.deviceId, appId}) |
2314 | if err != nil { |
2315 | return nil, fmt.Errorf("unable to marshal register request body: %v", err) |
2316 | } |
2317 | - req, err := http13.NewRequest("POST", svc.regURL, bytes.NewReader(req_body)) |
2318 | - if err != nil { |
2319 | - return nil, fmt.Errorf("unable to build register request: %v", err) |
2320 | - } |
2321 | - auth := svc.getRegistrationAuthorization() |
2322 | + |
2323 | + url, auth := svc.getAuthorization(op) |
2324 | if auth == "" { |
2325 | - return nil, BadAuth |
2326 | + return nil, ErrBadAuth |
2327 | + } |
2328 | + |
2329 | + req, err := http13.NewRequest("POST", url, bytes.NewReader(req_body)) |
2330 | + if err != nil { |
2331 | + panic(fmt.Errorf("unable to build register request: %v", err)) |
2332 | } |
2333 | req.Header.Add("Authorization", auth) |
2334 | req.Header.Add("Content-Type", "application/json") |
2335 | @@ -169,9 +135,9 @@ |
2336 | switch { |
2337 | case resp.StatusCode >= http.StatusInternalServerError: |
2338 | // XXX retry on 503 |
2339 | - return nil, BadServer |
2340 | + return nil, ErrBadServer |
2341 | default: |
2342 | - return nil, BadRequest |
2343 | + return nil, ErrBadRequest |
2344 | } |
2345 | } |
2346 | // errors below here Can't Happen (tm). |
2347 | @@ -188,10 +154,44 @@ |
2348 | return nil, fmt.Errorf("unable to unmarshal register response: %v", err) |
2349 | } |
2350 | |
2351 | + return &reply, nil |
2352 | +} |
2353 | + |
2354 | +func (svc *PushService) register(path string, args, _ []interface{}) ([]interface{}, error) { |
2355 | + _, appId, err := grabDBusPackageAndAppId(path, args, 0) |
2356 | + if err != nil { |
2357 | + return nil, err |
2358 | + } |
2359 | + |
2360 | + rawAppId := string(nih.Quote([]byte(appId))) |
2361 | + rv := os.Getenv("PUSH_REG_" + rawAppId) |
2362 | + if rv != "" { |
2363 | + return []interface{}{rv}, nil |
2364 | + } |
2365 | + |
2366 | + reply, err := svc.manageReg("/register", appId) |
2367 | + if err != nil { |
2368 | + return nil, err |
2369 | + } |
2370 | + |
2371 | if !reply.Ok || reply.Token == "" { |
2372 | svc.Log.Errorf("Unexpected response: %#v", reply) |
2373 | - return nil, BadToken |
2374 | + return nil, ErrBadToken |
2375 | } |
2376 | |
2377 | return []interface{}{reply.Token}, nil |
2378 | } |
2379 | + |
2380 | +func (svc *PushService) unregister(path string, args, _ []interface{}) ([]interface{}, error) { |
2381 | + _, appId, err := grabDBusPackageAndAppId(path, args, 0) |
2382 | + if err != nil { |
2383 | + return nil, err |
2384 | + } |
2385 | + |
2386 | + return nil, svc.Unregister(appId) |
2387 | +} |
2388 | + |
2389 | +func (svc *PushService) Unregister(appId string) error { |
2390 | + _, err := svc.manageReg("/unregister", appId) |
2391 | + return err |
2392 | +} |
2393 | |
2394 | === modified file 'client/service/service_test.go' |
2395 | --- client/service/service_test.go 2014-06-20 12:33:03 +0000 |
2396 | +++ client/service/service_test.go 2014-07-07 14:23:18 +0000 |
2397 | @@ -29,6 +29,7 @@ |
2398 | "launchpad.net/ubuntu-push/bus" |
2399 | testibus "launchpad.net/ubuntu-push/bus/testing" |
2400 | "launchpad.net/ubuntu-push/logger" |
2401 | + "launchpad.net/ubuntu-push/nih" |
2402 | helpers "launchpad.net/ubuntu-push/testing" |
2403 | "launchpad.net/ubuntu-push/testing/condition" |
2404 | ) |
2405 | @@ -42,13 +43,36 @@ |
2406 | |
2407 | var _ = Suite(&serviceSuite{}) |
2408 | |
2409 | +var ( |
2410 | + aPackage = "com.example.test" |
2411 | + anAppId = aPackage + "_test-number-one" |
2412 | + aPackageOnBus = "/" + string(nih.Quote([]byte(aPackage))) |
2413 | +) |
2414 | + |
2415 | func (ss *serviceSuite) SetUpTest(c *C) { |
2416 | ss.log = helpers.NewTestLogger(c, "debug") |
2417 | ss.bus = testibus.NewTestingEndpoint(condition.Work(true), nil) |
2418 | } |
2419 | |
2420 | +var testSetup = &PushServiceSetup{} |
2421 | + |
2422 | +func (ss *serviceSuite) TestBuild(c *C) { |
2423 | + setup := &PushServiceSetup{ |
2424 | + RegURL: helpers.ParseURL("http://reg"), |
2425 | + DeviceId: "FOO", |
2426 | + AuthGetter: func(s string) string { |
2427 | + return "" |
2428 | + }, |
2429 | + } |
2430 | + svc := NewPushService(ss.bus, setup, ss.log) |
2431 | + c.Check(svc.Bus, Equals, ss.bus) |
2432 | + c.Check(svc.regURL, DeepEquals, helpers.ParseURL("http://reg")) |
2433 | + c.Check(fmt.Sprintf("%#v", svc.authGetter), Equals, fmt.Sprintf("%#v", setup.AuthGetter)) |
2434 | + // ... |
2435 | +} |
2436 | + |
2437 | func (ss *serviceSuite) TestStart(c *C) { |
2438 | - svc := NewPushService(ss.bus, ss.log) |
2439 | + svc := NewPushService(ss.bus, testSetup, ss.log) |
2440 | c.Check(svc.IsRunning(), Equals, false) |
2441 | c.Check(svc.Start(), IsNil) |
2442 | c.Check(svc.IsRunning(), Equals, true) |
2443 | @@ -56,31 +80,31 @@ |
2444 | } |
2445 | |
2446 | func (ss *serviceSuite) TestStartTwice(c *C) { |
2447 | - svc := NewPushService(ss.bus, ss.log) |
2448 | + svc := NewPushService(ss.bus, testSetup, ss.log) |
2449 | c.Check(svc.Start(), IsNil) |
2450 | - c.Check(svc.Start(), Equals, AlreadyStarted) |
2451 | + c.Check(svc.Start(), Equals, ErrAlreadyStarted) |
2452 | svc.Stop() |
2453 | } |
2454 | |
2455 | func (ss *serviceSuite) TestStartNoLog(c *C) { |
2456 | - svc := NewPushService(ss.bus, nil) |
2457 | - c.Check(svc.Start(), Equals, NotConfigured) |
2458 | + svc := NewPushService(ss.bus, testSetup, nil) |
2459 | + c.Check(svc.Start(), Equals, ErrNotConfigured) |
2460 | } |
2461 | |
2462 | func (ss *serviceSuite) TestStartNoBus(c *C) { |
2463 | - svc := NewPushService(nil, ss.log) |
2464 | - c.Check(svc.Start(), Equals, NotConfigured) |
2465 | + svc := NewPushService(nil, testSetup, ss.log) |
2466 | + c.Check(svc.Start(), Equals, ErrNotConfigured) |
2467 | } |
2468 | |
2469 | func (ss *serviceSuite) TestStartFailsOnBusDialFailure(c *C) { |
2470 | bus := testibus.NewTestingEndpoint(condition.Work(false), nil) |
2471 | - svc := NewPushService(bus, ss.log) |
2472 | + svc := NewPushService(bus, testSetup, ss.log) |
2473 | c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`) |
2474 | svc.Stop() |
2475 | } |
2476 | |
2477 | func (ss *serviceSuite) TestStartGrabsName(c *C) { |
2478 | - svc := NewPushService(ss.bus, ss.log) |
2479 | + svc := NewPushService(ss.bus, testSetup, ss.log) |
2480 | c.Assert(svc.Start(), IsNil) |
2481 | callArgs := testibus.GetCallArgs(ss.bus) |
2482 | defer svc.Stop() |
2483 | @@ -89,7 +113,7 @@ |
2484 | } |
2485 | |
2486 | func (ss *serviceSuite) TestStopClosesBus(c *C) { |
2487 | - svc := NewPushService(ss.bus, ss.log) |
2488 | + svc := NewPushService(ss.bus, testSetup, ss.log) |
2489 | c.Assert(svc.Start(), IsNil) |
2490 | svc.Stop() |
2491 | callArgs := testibus.GetCallArgs(ss.bus) |
2492 | @@ -99,41 +123,48 @@ |
2493 | |
2494 | // registration tests |
2495 | |
2496 | -func (ss *serviceSuite) TestSetRegURLWorks(c *C) { |
2497 | - svc := NewPushService(ss.bus, ss.log) |
2498 | - c.Check(svc.regURL, Equals, "") |
2499 | - svc.SetRegistrationURL("xyzzy://") |
2500 | - c.Check(svc.regURL, Equals, "xyzzy://") |
2501 | -} |
2502 | - |
2503 | -func (ss *serviceSuite) TestSetAuthGetterWorks(c *C) { |
2504 | - svc := NewPushService(ss.bus, ss.log) |
2505 | - c.Check(svc.authGetter, IsNil) |
2506 | - f := func(string) string { return "" } |
2507 | - svc.SetAuthGetter(f) |
2508 | - c.Check(fmt.Sprintf("%#v", svc.authGetter), Equals, fmt.Sprintf("%#v", f)) |
2509 | -} |
2510 | - |
2511 | func (ss *serviceSuite) TestGetRegAuthWorks(c *C) { |
2512 | - svc := NewPushService(ss.bus, ss.log) |
2513 | - svc.SetRegistrationURL("xyzzy://") |
2514 | ch := make(chan string, 1) |
2515 | - f := func(s string) string { ch <- s; return "Auth " + s } |
2516 | - svc.SetAuthGetter(f) |
2517 | - c.Check(svc.getRegistrationAuthorization(), Equals, "Auth xyzzy://") |
2518 | + setup := &PushServiceSetup{ |
2519 | + RegURL: helpers.ParseURL("http://foo"), |
2520 | + AuthGetter: func(s string) string { |
2521 | + ch <- s |
2522 | + return "Auth " + s |
2523 | + }, |
2524 | + } |
2525 | + svc := NewPushService(ss.bus, setup, ss.log) |
2526 | + url, auth := svc.getAuthorization("/op") |
2527 | + c.Check(auth, Equals, "Auth http://foo/op") |
2528 | c.Assert(len(ch), Equals, 1) |
2529 | - c.Check(<-ch, Equals, "xyzzy://") |
2530 | + c.Check(<-ch, Equals, "http://foo/op") |
2531 | + c.Check(url, Equals, "http://foo/op") |
2532 | } |
2533 | |
2534 | func (ss *serviceSuite) TestGetRegAuthDoesNotPanic(c *C) { |
2535 | - svc := NewPushService(ss.bus, ss.log) |
2536 | - c.Check(svc.getRegistrationAuthorization(), Equals, "") |
2537 | + svc := NewPushService(ss.bus, testSetup, ss.log) |
2538 | + _, auth := svc.getAuthorization("/op") |
2539 | + c.Check(auth, Equals, "") |
2540 | } |
2541 | |
2542 | -func (ss *serviceSuite) TestRegistrationFailsIfBadArgs(c *C) { |
2543 | - reg, err := new(PushService).register("", []interface{}{1}, nil) |
2544 | - c.Check(reg, IsNil) |
2545 | - c.Check(err, Equals, BadArgCount) |
2546 | +func (ss *serviceSuite) TestRegistrationAndUnregistrationFailIfBadArgs(c *C) { |
2547 | + for i, s := range []struct { |
2548 | + args []interface{} |
2549 | + errt error |
2550 | + }{ |
2551 | + {nil, ErrBadArgCount}, |
2552 | + {[]interface{}{}, ErrBadArgCount}, |
2553 | + {[]interface{}{1}, ErrBadArgType}, |
2554 | + {[]interface{}{"foo"}, ErrBadAppId}, |
2555 | + {[]interface{}{"foo", "bar"}, ErrBadArgCount}, |
2556 | + } { |
2557 | + reg, err := new(PushService).register("/bar", s.args, nil) |
2558 | + c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
2559 | + c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
2560 | + |
2561 | + reg, err = new(PushService).unregister("/bar", s.args, nil) |
2562 | + c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
2563 | + c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
2564 | + } |
2565 | } |
2566 | |
2567 | func (ss *serviceSuite) TestRegistrationWorks(c *C) { |
2568 | @@ -143,19 +174,20 @@ |
2569 | c.Assert(e, IsNil) |
2570 | req := registrationRequest{} |
2571 | c.Assert(json.Unmarshal(buf[:n], &req), IsNil) |
2572 | - c.Check(req, DeepEquals, registrationRequest{"fake-device-id", "an-app-id"}) |
2573 | - |
2574 | + c.Check(req, DeepEquals, registrationRequest{"fake-device-id", anAppId}) |
2575 | + c.Check(r.URL.Path, Equals, "/register") |
2576 | w.Header().Set("Content-Type", "application/json") |
2577 | fmt.Fprintln(w, `{"ok":true,"token":"blob-of-bytes"}`) |
2578 | })) |
2579 | defer ts.Close() |
2580 | - |
2581 | - svc := NewPushService(ss.bus, ss.log) |
2582 | - svc.SetAuthGetter(func(string) string { return "tok" }) |
2583 | - svc.SetRegistrationURL(ts.URL) |
2584 | - svc.SetDeviceId("fake-device-id") |
2585 | + setup := &PushServiceSetup{ |
2586 | + DeviceId: "fake-device-id", |
2587 | + RegURL: helpers.ParseURL(ts.URL), |
2588 | + AuthGetter: func(string) string { return "tok" }, |
2589 | + } |
2590 | + svc := NewPushService(ss.bus, setup, ss.log) |
2591 | // this'll check (un)quoting, too |
2592 | - reg, err := svc.register("/an_2dapp_2did", nil, nil) |
2593 | + reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
2594 | c.Assert(err, IsNil) |
2595 | c.Assert(reg, HasLen, 1) |
2596 | regs, ok := reg[0].(string) |
2597 | @@ -164,10 +196,11 @@ |
2598 | } |
2599 | |
2600 | func (ss *serviceSuite) TestRegistrationOverrideWorks(c *C) { |
2601 | - os.Setenv("PUSH_REG_stuff", "42") |
2602 | - defer os.Setenv("PUSH_REG_stuff", "") |
2603 | + envar := "PUSH_REG_" + string(nih.Quote([]byte(anAppId))) |
2604 | + os.Setenv(envar, "42") |
2605 | + defer os.Setenv(envar, "") |
2606 | |
2607 | - reg, err := new(PushService).register("/stuff", nil, nil) |
2608 | + reg, err := new(PushService).register(aPackageOnBus, []interface{}{anAppId}, nil) |
2609 | c.Assert(reg, HasLen, 1) |
2610 | regs, ok := reg[0].(string) |
2611 | c.Check(ok, Equals, true) |
2612 | @@ -175,103 +208,155 @@ |
2613 | c.Check(err, IsNil) |
2614 | } |
2615 | |
2616 | -func (ss *serviceSuite) TestRegistrationFailsOnBadReqURL(c *C) { |
2617 | - svc := NewPushService(ss.bus, ss.log) |
2618 | - svc.SetRegistrationURL("%gh") |
2619 | - reg, err := svc.register("thing", nil, nil) |
2620 | - c.Check(reg, IsNil) |
2621 | - c.Check(err, ErrorMatches, "unable to build register request: .*") |
2622 | -} |
2623 | - |
2624 | -func (ss *serviceSuite) TestRegistrationFailsOnBadAuth(c *C) { |
2625 | - svc := NewPushService(ss.bus, ss.log) |
2626 | +func (ss *serviceSuite) TestManageRegFailsOnBadAuth(c *C) { |
2627 | // ... no auth added |
2628 | - reg, err := svc.register("thing", nil, nil) |
2629 | + svc := NewPushService(ss.bus, testSetup, ss.log) |
2630 | + reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
2631 | c.Check(reg, IsNil) |
2632 | - c.Check(err, Equals, BadAuth) |
2633 | + c.Check(err, Equals, ErrBadAuth) |
2634 | } |
2635 | |
2636 | -func (ss *serviceSuite) TestRegistrationFailsOnNoServer(c *C) { |
2637 | - svc := NewPushService(ss.bus, ss.log) |
2638 | - svc.SetRegistrationURL("xyzzy://") |
2639 | - svc.SetAuthGetter(func(string) string { return "tok" }) |
2640 | - reg, err := svc.register("thing", nil, nil) |
2641 | +func (ss *serviceSuite) TestManageRegFailsOnNoServer(c *C) { |
2642 | + setup := &PushServiceSetup{ |
2643 | + DeviceId: "fake-device-id", |
2644 | + RegURL: helpers.ParseURL("xyzzy://"), |
2645 | + AuthGetter: func(string) string { return "tok" }, |
2646 | + } |
2647 | + svc := NewPushService(ss.bus, setup, ss.log) |
2648 | + reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
2649 | c.Check(reg, IsNil) |
2650 | c.Check(err, ErrorMatches, "unable to request registration: .*") |
2651 | } |
2652 | |
2653 | -func (ss *serviceSuite) TestRegistrationFailsOn40x(c *C) { |
2654 | +func (ss *serviceSuite) TestManageRegFailsOn40x(c *C) { |
2655 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2656 | http.Error(w, "I'm a teapot", 418) |
2657 | })) |
2658 | defer ts.Close() |
2659 | - |
2660 | - svc := NewPushService(ss.bus, ss.log) |
2661 | - svc.SetAuthGetter(func(string) string { return "tok" }) |
2662 | - svc.SetRegistrationURL(ts.URL) |
2663 | - reg, err := svc.register("/thing", nil, nil) |
2664 | - c.Check(err, Equals, BadRequest) |
2665 | + setup := &PushServiceSetup{ |
2666 | + DeviceId: "fake-device-id", |
2667 | + RegURL: helpers.ParseURL(ts.URL), |
2668 | + AuthGetter: func(string) string { return "tok" }, |
2669 | + } |
2670 | + svc := NewPushService(ss.bus, setup, ss.log) |
2671 | + reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
2672 | + c.Check(err, Equals, ErrBadRequest) |
2673 | c.Check(reg, IsNil) |
2674 | } |
2675 | |
2676 | -func (ss *serviceSuite) TestRegistrationFailsOn50x(c *C) { |
2677 | +func (ss *serviceSuite) TestManageRegFailsOn50x(c *C) { |
2678 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2679 | http.Error(w, "Not implemented", 501) |
2680 | })) |
2681 | defer ts.Close() |
2682 | - |
2683 | - svc := NewPushService(ss.bus, ss.log) |
2684 | - svc.SetAuthGetter(func(string) string { return "tok" }) |
2685 | - svc.SetRegistrationURL(ts.URL) |
2686 | - reg, err := svc.register("/thing", nil, nil) |
2687 | - c.Check(err, Equals, BadServer) |
2688 | + setup := &PushServiceSetup{ |
2689 | + DeviceId: "fake-device-id", |
2690 | + RegURL: helpers.ParseURL(ts.URL), |
2691 | + AuthGetter: func(string) string { return "tok" }, |
2692 | + } |
2693 | + svc := NewPushService(ss.bus, setup, ss.log) |
2694 | + reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
2695 | + c.Check(err, Equals, ErrBadServer) |
2696 | c.Check(reg, IsNil) |
2697 | } |
2698 | |
2699 | -func (ss *serviceSuite) TestRegistrationFailsOnBadJSON(c *C) { |
2700 | +func (ss *serviceSuite) TestManageRegFailsOnBadJSON(c *C) { |
2701 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2702 | buf := make([]byte, 256) |
2703 | n, e := r.Body.Read(buf) |
2704 | c.Assert(e, IsNil) |
2705 | req := registrationRequest{} |
2706 | c.Assert(json.Unmarshal(buf[:n], &req), IsNil) |
2707 | - c.Check(req, DeepEquals, registrationRequest{"fake-device-id", "an-app-id"}) |
2708 | + c.Check(req, DeepEquals, registrationRequest{"fake-device-id", anAppId}) |
2709 | |
2710 | w.Header().Set("Content-Type", "application/json") |
2711 | fmt.Fprintln(w, `{`) |
2712 | })) |
2713 | defer ts.Close() |
2714 | - |
2715 | - svc := NewPushService(ss.bus, ss.log) |
2716 | - svc.SetAuthGetter(func(string) string { return "tok" }) |
2717 | - svc.SetRegistrationURL(ts.URL) |
2718 | - svc.SetDeviceId("fake-device-id") |
2719 | + setup := &PushServiceSetup{ |
2720 | + DeviceId: "fake-device-id", |
2721 | + RegURL: helpers.ParseURL(ts.URL), |
2722 | + AuthGetter: func(string) string { return "tok" }, |
2723 | + } |
2724 | + svc := NewPushService(ss.bus, setup, ss.log) |
2725 | // this'll check (un)quoting, too |
2726 | - reg, err := svc.register("/an_2dapp_2did", nil, nil) |
2727 | + reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
2728 | c.Check(reg, IsNil) |
2729 | c.Check(err, ErrorMatches, "unable to unmarshal register response: .*") |
2730 | } |
2731 | |
2732 | -func (ss *serviceSuite) TestRegistrationFailsOnBadJSONDocument(c *C) { |
2733 | +func (ss *serviceSuite) TestManageRegFailsOnBadJSONDocument(c *C) { |
2734 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2735 | buf := make([]byte, 256) |
2736 | n, e := r.Body.Read(buf) |
2737 | c.Assert(e, IsNil) |
2738 | req := registrationRequest{} |
2739 | c.Assert(json.Unmarshal(buf[:n], &req), IsNil) |
2740 | - c.Check(req, DeepEquals, registrationRequest{"fake-device-id", "an-app-id"}) |
2741 | + c.Check(req, DeepEquals, registrationRequest{"fake-device-id", anAppId}) |
2742 | |
2743 | w.Header().Set("Content-Type", "application/json") |
2744 | fmt.Fprintln(w, `{"bananas": "very yes"}`) |
2745 | })) |
2746 | defer ts.Close() |
2747 | - |
2748 | - svc := NewPushService(ss.bus, ss.log) |
2749 | - svc.SetAuthGetter(func(string) string { return "tok" }) |
2750 | - svc.SetRegistrationURL(ts.URL) |
2751 | - svc.SetDeviceId("fake-device-id") |
2752 | + setup := &PushServiceSetup{ |
2753 | + DeviceId: "fake-device-id", |
2754 | + RegURL: helpers.ParseURL(ts.URL), |
2755 | + AuthGetter: func(string) string { return "tok" }, |
2756 | + } |
2757 | + svc := NewPushService(ss.bus, setup, ss.log) |
2758 | // this'll check (un)quoting, too |
2759 | - reg, err := svc.register("/an_2dapp_2did", nil, nil) |
2760 | + reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
2761 | c.Check(reg, IsNil) |
2762 | - c.Check(err, Equals, BadToken) |
2763 | + c.Check(err, Equals, ErrBadToken) |
2764 | +} |
2765 | + |
2766 | +func (ss *serviceSuite) TestDBusUnregisterWorks(c *C) { |
2767 | + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2768 | + buf := make([]byte, 256) |
2769 | + n, e := r.Body.Read(buf) |
2770 | + c.Assert(e, IsNil) |
2771 | + req := registrationRequest{} |
2772 | + c.Assert(json.Unmarshal(buf[:n], &req), IsNil) |
2773 | + c.Check(req, DeepEquals, registrationRequest{"fake-device-id", anAppId}) |
2774 | + c.Check(r.URL.Path, Equals, "/unregister") |
2775 | + w.Header().Set("Content-Type", "application/json") |
2776 | + fmt.Fprintln(w, `{"ok":true,"token":"blob-of-bytes"}`) |
2777 | + })) |
2778 | + defer ts.Close() |
2779 | + setup := &PushServiceSetup{ |
2780 | + DeviceId: "fake-device-id", |
2781 | + RegURL: helpers.ParseURL(ts.URL), |
2782 | + AuthGetter: func(string) string { return "tok" }, |
2783 | + } |
2784 | + svc := NewPushService(ss.bus, setup, ss.log) |
2785 | + // this'll check (un)quoting, too |
2786 | + reg, err := svc.unregister(aPackageOnBus, []interface{}{anAppId}, nil) |
2787 | + c.Assert(err, IsNil) |
2788 | + c.Assert(reg, HasLen, 0) |
2789 | +} |
2790 | + |
2791 | +func (ss *serviceSuite) TestUnregistrationWorks(c *C) { |
2792 | + invoked := make(chan bool, 1) |
2793 | + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
2794 | + buf := make([]byte, 256) |
2795 | + n, e := r.Body.Read(buf) |
2796 | + c.Assert(e, IsNil) |
2797 | + req := registrationRequest{} |
2798 | + c.Assert(json.Unmarshal(buf[:n], &req), IsNil) |
2799 | + c.Check(req, DeepEquals, registrationRequest{"fake-device-id", anAppId}) |
2800 | + c.Check(r.URL.Path, Equals, "/unregister") |
2801 | + invoked <- true |
2802 | + w.Header().Set("Content-Type", "application/json") |
2803 | + fmt.Fprintln(w, `{"ok":true}`) |
2804 | + })) |
2805 | + defer ts.Close() |
2806 | + setup := &PushServiceSetup{ |
2807 | + DeviceId: "fake-device-id", |
2808 | + RegURL: helpers.ParseURL(ts.URL), |
2809 | + AuthGetter: func(string) string { return "tok" }, |
2810 | + } |
2811 | + svc := NewPushService(ss.bus, setup, ss.log) |
2812 | + err := svc.Unregister(anAppId) |
2813 | + c.Assert(err, IsNil) |
2814 | + c.Check(invoked, HasLen, 1) |
2815 | } |
2816 | |
2817 | === modified file 'client/session/session.go' |
2818 | --- client/session/session.go 2014-06-13 12:19:26 +0000 |
2819 | +++ client/session/session.go 2014-07-07 14:23:18 +0000 |
2820 | @@ -78,6 +78,12 @@ |
2821 | Get() (*gethosts.Host, error) |
2822 | } |
2823 | |
2824 | +// AddresseeChecking can check if a notification can be delivered. |
2825 | +type AddresseeChecking interface { |
2826 | + StartAddresseeBatch() |
2827 | + CheckForAddressee(*protocol.Notification) bool |
2828 | +} |
2829 | + |
2830 | // ClientSessionConfig groups the client session configuration. |
2831 | type ClientSessionConfig struct { |
2832 | ConnectTimeout time.Duration |
2833 | @@ -88,6 +94,7 @@ |
2834 | Info map[string]interface{} |
2835 | AuthGetter func(string) string |
2836 | AuthURL string |
2837 | + AddresseeChecker AddresseeChecking |
2838 | } |
2839 | |
2840 | // ClientSession holds a client<->server session and its configuration. |
2841 | @@ -426,8 +433,12 @@ |
2842 | return err |
2843 | } |
2844 | sess.clearShouldDelay() |
2845 | + sess.AddresseeChecker.StartAddresseeBatch() |
2846 | for i := range notifs { |
2847 | notif := ¬ifs[i] |
2848 | + if !sess.AddresseeChecker.CheckForAddressee(notif) { |
2849 | + continue |
2850 | + } |
2851 | sess.Log.Debugf("unicast app:%v msg:%s payload:%s", |
2852 | notif.AppId, notif.MsgId, notif.Payload) |
2853 | sess.Log.Debugf("sending ucast over") |
2854 | |
2855 | === modified file 'client/session/session_test.go' |
2856 | --- client/session/session_test.go 2014-06-13 12:19:26 +0000 |
2857 | +++ client/session/session_test.go 2014-07-07 14:23:18 +0000 |
2858 | @@ -798,7 +798,23 @@ |
2859 | handleNotifications() tests |
2860 | ****************************************************************/ |
2861 | |
2862 | +type testAddresseeChecking struct { |
2863 | + ops chan string |
2864 | + missing string |
2865 | +} |
2866 | + |
2867 | +func (ac *testAddresseeChecking) StartAddresseeBatch() { |
2868 | + ac.ops <- "start" |
2869 | +} |
2870 | + |
2871 | +func (ac *testAddresseeChecking) CheckForAddressee(notif *protocol.Notification) bool { |
2872 | + ac.ops <- notif.AppId |
2873 | + return notif.AppId != ac.missing |
2874 | +} |
2875 | + |
2876 | func (s *msgSuite) TestHandleNotificationsWorks(c *C) { |
2877 | + ac := &testAddresseeChecking{ops: make(chan string, 10)} |
2878 | + s.sess.AddresseeChecker = ac |
2879 | s.sess.setShouldDelay() |
2880 | n1 := protocol.Notification{ |
2881 | AppId: "app1", |
2882 | @@ -820,12 +836,52 @@ |
2883 | s.upCh <- nil // ack ok |
2884 | c.Check(<-s.errCh, Equals, nil) |
2885 | c.Check(s.sess.ShouldDelay(), Equals, false) |
2886 | - c.Assert(len(s.sess.NotificationsCh), Equals, 2) |
2887 | + c.Assert(s.sess.NotificationsCh, HasLen, 2) |
2888 | c.Check(<-s.sess.NotificationsCh, DeepEquals, &n1) |
2889 | c.Check(<-s.sess.NotificationsCh, DeepEquals, &n2) |
2890 | + c.Check(ac.ops, HasLen, 3) |
2891 | + c.Check(<-ac.ops, Equals, "start") |
2892 | + c.Check(<-ac.ops, Equals, "app1") |
2893 | + c.Check(<-ac.ops, Equals, "app2") |
2894 | +} |
2895 | + |
2896 | +func (s *msgSuite) TestHandleNotificationsAddresseeCheck(c *C) { |
2897 | + ac := &testAddresseeChecking{ |
2898 | + ops: make(chan string, 10), |
2899 | + missing: "app1", |
2900 | + } |
2901 | + s.sess.AddresseeChecker = ac |
2902 | + s.sess.setShouldDelay() |
2903 | + n1 := protocol.Notification{ |
2904 | + AppId: "app1", |
2905 | + MsgId: "a", |
2906 | + Payload: json.RawMessage(`{"m": 1}`), |
2907 | + } |
2908 | + n2 := protocol.Notification{ |
2909 | + AppId: "app2", |
2910 | + MsgId: "b", |
2911 | + Payload: json.RawMessage(`{"m": 2}`), |
2912 | + } |
2913 | + msg := serverMsg{"notifications", |
2914 | + protocol.BroadcastMsg{}, |
2915 | + protocol.NotificationsMsg{ |
2916 | + Notifications: []protocol.Notification{n1, n2}, |
2917 | + }, protocol.ConnBrokenMsg{}} |
2918 | + go func() { s.errCh <- s.sess.handleNotifications(&msg) }() |
2919 | + c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
2920 | + s.upCh <- nil // ack ok |
2921 | + c.Check(<-s.errCh, Equals, nil) |
2922 | + c.Check(s.sess.ShouldDelay(), Equals, false) |
2923 | + c.Assert(s.sess.NotificationsCh, HasLen, 1) |
2924 | + c.Check(<-s.sess.NotificationsCh, DeepEquals, &n2) |
2925 | + c.Check(ac.ops, HasLen, 3) |
2926 | + c.Check(<-ac.ops, Equals, "start") |
2927 | + c.Check(<-ac.ops, Equals, "app1") |
2928 | } |
2929 | |
2930 | func (s *msgSuite) TestHandleNotificationsFiltersSeen(c *C) { |
2931 | + ac := &testAddresseeChecking{ops: make(chan string, 10)} |
2932 | + s.sess.AddresseeChecker = ac |
2933 | n1 := protocol.Notification{ |
2934 | AppId: "app1", |
2935 | MsgId: "a", |
2936 | @@ -845,16 +901,18 @@ |
2937 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
2938 | s.upCh <- nil // ack ok |
2939 | c.Check(<-s.errCh, Equals, nil) |
2940 | - c.Assert(len(s.sess.NotificationsCh), Equals, 2) |
2941 | + c.Assert(s.sess.NotificationsCh, HasLen, 2) |
2942 | c.Check(<-s.sess.NotificationsCh, DeepEquals, &n1) |
2943 | c.Check(<-s.sess.NotificationsCh, DeepEquals, &n2) |
2944 | + c.Check(ac.ops, HasLen, 3) |
2945 | |
2946 | // second time they get ignored |
2947 | go func() { s.errCh <- s.sess.handleNotifications(&msg) }() |
2948 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
2949 | s.upCh <- nil // ack ok |
2950 | c.Check(<-s.errCh, Equals, nil) |
2951 | - c.Assert(len(s.sess.NotificationsCh), Equals, 0) |
2952 | + c.Assert(s.sess.NotificationsCh, HasLen, 0) |
2953 | + c.Check(ac.ops, HasLen, 4) |
2954 | } |
2955 | |
2956 | func (s *msgSuite) TestHandleNotificationsBadAckWrite(c *C) { |
2957 | |
2958 | === modified file 'debian/changelog' |
2959 | --- debian/changelog 2014-07-02 13:14:04 +0000 |
2960 | +++ debian/changelog 2014-07-07 14:23:18 +0000 |
2961 | @@ -1,3 +1,18 @@ |
2962 | +ubuntu-push (0.43) UNRELEASED; urgency=medium |
2963 | + |
2964 | + [ Samuele Pedroni ] |
2965 | + * Logic to support unregistering tokens lazily for uninstalled apps |
2966 | + * Minimal wrapping of libclick to check if a package is installed for a user |
2967 | + * Refactor and cleanup of cleanup/service |
2968 | + |
2969 | + [ John R. Lenton ] |
2970 | + * Finalized DBus API (hopefully) |
2971 | + * Support emblem counter notifications |
2972 | + * Support haptic (vibration) notifications |
2973 | + * Support sound notifications |
2974 | + |
2975 | + -- John R. Lenton <john.lenton@canonical.com> Mon, 07 Jul 2014 15:22:42 +0100 |
2976 | + |
2977 | ubuntu-push (0.42+14.10.20140702-0ubuntu1) utopic; urgency=medium |
2978 | |
2979 | [ Samuele Pedroni ] |
2980 | |
2981 | === modified file 'debian/config.json' |
2982 | --- debian/config.json 2014-06-19 21:12:04 +0000 |
2983 | +++ debian/config.json 2014-07-07 14:23:18 +0000 |
2984 | @@ -1,7 +1,7 @@ |
2985 | { |
2986 | "auth_helper": "/usr/lib/ubuntu-push-client/signing-helper", |
2987 | "session_url": "https://push.ubuntu.com/", |
2988 | - "registration_url": "https://push.ubuntu.com/register", |
2989 | + "registration_url": "https://push.ubuntu.com", |
2990 | "connect_timeout": "20s", |
2991 | "exchange_timeout": "30s", |
2992 | "hosts_cache_expiry": "12h", |
2993 | |
2994 | === modified file 'debian/control' |
2995 | --- debian/control 2014-06-23 13:29:39 +0000 |
2996 | +++ debian/control 2014-07-07 14:23:18 +0000 |
2997 | @@ -20,6 +20,7 @@ |
2998 | libubuntuoneauth-2.0-dev, |
2999 | libdbus-1-dev, |
3000 | libnih-dbus-dev, |
3001 | + libclick-0.4-dev, |
3002 | cmake, |
3003 | Standards-Version: 3.9.5 |
3004 | Homepage: http://launchpad.net/ubuntu-push |
3005 | |
3006 | === modified file 'launch_helper/helper_output.go' |
3007 | --- launch_helper/helper_output.go 2014-06-30 12:19:14 +0000 |
3008 | +++ launch_helper/helper_output.go 2014-07-07 14:23:18 +0000 |
3009 | @@ -37,9 +37,9 @@ |
3010 | // a Vibration generates a vibration in the form of a Pattern set in |
3011 | // duration a pattern of on off states, repeated a number of times |
3012 | type Vibration struct { |
3013 | - Duration uint `json:"duration"` // if Duration is present and not 0, it's like a Pattern of [Duration] and a Repeat of 1; otherwise, Pattern and Repeat are used. |
3014 | + Duration uint32 `json:"duration"` // if Duration is present and not 0, it's like a Pattern of [Duration]; otherwise, Pattern is used. |
3015 | Pattern []uint32 `json:"pattern"` |
3016 | - Repeat uint32 `json:"repeat"` |
3017 | + Repeat uint32 `json:"repeat"` // defaults to 1. A value of zero is ignored (so it's like 1). |
3018 | } |
3019 | |
3020 | // a Notification can be any of the above |
3021 | |
3022 | === modified file 'messaging/messaging.go' |
3023 | --- messaging/messaging.go 2014-07-01 00:51:59 +0000 |
3024 | +++ messaging/messaging.go 2014-07-07 14:23:18 +0000 |
3025 | @@ -43,6 +43,7 @@ |
3026 | |
3027 | func (mmu *MessagingMenu) Present(appId string, notificationId string, notification *launch_helper.Notification) { |
3028 | if notification == nil || notification.Card == nil || !notification.Card.Persist || notification.Card.Summary == "" { |
3029 | + mmu.Log.Debugf("[%s] no notification or notification has no persistable card: %#v", notificationId, notification) |
3030 | return |
3031 | } |
3032 | |
3033 | |
3034 | === modified file 'messaging/messaging_test.go' |
3035 | --- messaging/messaging_test.go 2014-07-01 00:51:59 +0000 |
3036 | +++ messaging/messaging_test.go 2014-07-07 14:23:18 +0000 |
3037 | @@ -61,7 +61,7 @@ |
3038 | |
3039 | mmu.Present("app-id", "notif-id", ¬if) |
3040 | |
3041 | - c.Check(ms.log.Captured(), Equals, "") |
3042 | + c.Check(ms.log.Captured(), Matches, "(?sm).*has no persistable card.*") |
3043 | } |
3044 | |
3045 | func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNotPersist(c *C) { |
3046 | @@ -71,17 +71,17 @@ |
3047 | |
3048 | mmu.Present("app-id", "notif-id", ¬if) |
3049 | |
3050 | - c.Check(ms.log.Captured(), Equals, "") |
3051 | + c.Check(ms.log.Captured(), Matches, "(?sm).*has no persistable card.*") |
3052 | } |
3053 | |
3054 | func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNil(c *C) { |
3055 | mmu := New(ms.log) |
3056 | mmu.Present("app-id", "notif-id", nil) |
3057 | - c.Check(ms.log.Captured(), Equals, "") |
3058 | + c.Check(ms.log.Captured(), Matches, "(?sm).*no notification.*") |
3059 | } |
3060 | |
3061 | func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNilCard(c *C) { |
3062 | mmu := New(ms.log) |
3063 | mmu.Present("app-id", "notif-id", &launch_helper.Notification{}) |
3064 | - c.Check(ms.log.Captured(), Equals, "") |
3065 | + c.Check(ms.log.Captured(), Matches, "(?sm).*no notification.*") |
3066 | } |
3067 | |
3068 | === modified file 'server/session/session_test.go' |
3069 | --- server/session/session_test.go 2014-06-19 13:06:52 +0000 |
3070 | +++ server/session/session_test.go 2014-07-07 14:23:18 +0000 |
3071 | @@ -373,6 +373,7 @@ |
3072 | pingTimer: time.NewTimer(pingInterval), |
3073 | intervalStart: now, |
3074 | } |
3075 | + time.Sleep(10 * time.Millisecond) |
3076 | l.pingTimer.Stop() |
3077 | done := l.pingTimerReset(true) |
3078 | c.Assert(done, Equals, true) |
3079 | |
3080 | === added directory 'sounds' |
3081 | === added file 'sounds/sounds.go' |
3082 | --- sounds/sounds.go 1970-01-01 00:00:00 +0000 |
3083 | +++ sounds/sounds.go 2014-07-07 14:23:18 +0000 |
3084 | @@ -0,0 +1,91 @@ |
3085 | +/* |
3086 | + Copyright 2014 Canonical Ltd. |
3087 | + |
3088 | + This program is free software: you can redistribute it and/or modify it |
3089 | + under the terms of the GNU General Public License version 3, as published |
3090 | + by the Free Software Foundation. |
3091 | + |
3092 | + This program is distributed in the hope that it will be useful, but |
3093 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
3094 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3095 | + PURPOSE. See the GNU General Public License for more details. |
3096 | + |
3097 | + You should have received a copy of the GNU General Public License along |
3098 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
3099 | +*/ |
3100 | + |
3101 | +package sounds |
3102 | + |
3103 | +import ( |
3104 | + "os" |
3105 | + "os/exec" |
3106 | + "path" |
3107 | + |
3108 | + "launchpad.net/go-xdg/v0" |
3109 | + |
3110 | + "launchpad.net/ubuntu-push/click" |
3111 | + "launchpad.net/ubuntu-push/launch_helper" |
3112 | + "launchpad.net/ubuntu-push/logger" |
3113 | +) |
3114 | + |
3115 | +type Sound struct { |
3116 | + player string |
3117 | + log logger.Logger |
3118 | + dataDirs func() []string |
3119 | + dataFind func(string) (string, error) |
3120 | +} |
3121 | + |
3122 | +func New(log logger.Logger) *Sound { |
3123 | + return &Sound{player: "paplay", log: log, dataDirs: xdg.Data.Dirs, dataFind: xdg.Data.Find} |
3124 | +} |
3125 | + |
3126 | +func (snd *Sound) Present(appId string, nid string, notification *launch_helper.Notification) bool { |
3127 | + if notification == nil || notification.Sound == "" { |
3128 | + snd.log.Debugf("[%s] no notification or no Sound in the notification; doing nothing: %#v", nid, notification) |
3129 | + return false |
3130 | + } |
3131 | + absPath := snd.findSoundFile(appId, nid, notification.Sound) |
3132 | + if absPath == "" { |
3133 | + snd.log.Debugf("[%s] unable to find sound %s", nid, notification.Sound) |
3134 | + return false |
3135 | + } |
3136 | + snd.log.Debugf("[%s] playing sound %s using %s", nid, absPath, snd.player) |
3137 | + cmd := exec.Command(snd.player, absPath) |
3138 | + err := cmd.Start() |
3139 | + if err != nil { |
3140 | + snd.log.Debugf("[%s] unable to play: %v", nid, err) |
3141 | + return false |
3142 | + } |
3143 | + go func() { |
3144 | + err := cmd.Wait() |
3145 | + if err != nil { |
3146 | + snd.log.Debugf("[%s] error playing sound %s: %v", nid, absPath, err) |
3147 | + } |
3148 | + }() |
3149 | + return true |
3150 | +} |
3151 | + |
3152 | +func (snd *Sound) findSoundFile(appId string, nid string, sound string) string { |
3153 | + parsed, err := click.ParseAppId(appId) |
3154 | + if err != nil { |
3155 | + snd.log.Debugf("[%s] no appId in %#v", nid, appId) |
3156 | + return "" |
3157 | + } |
3158 | + // XXX also support legacy appIds? |
3159 | + // first, check package-specific |
3160 | + absPath, err := snd.dataFind(path.Join(parsed.Package, sound)) |
3161 | + if err == nil { |
3162 | + // ffffound |
3163 | + return absPath |
3164 | + } |
3165 | + // next, check the XDG data dirs (but skip the first one -- that's "home") |
3166 | + // XXX should we only check in $XDG/sounds ? (that's for sound *themes*...) |
3167 | + for _, dir := range snd.dataDirs()[1:] { |
3168 | + absPath := path.Join(dir, sound) |
3169 | + _, err := os.Stat(absPath) |
3170 | + if err == nil { |
3171 | + return absPath |
3172 | + } |
3173 | + } |
3174 | + return "" |
3175 | +} |
3176 | |
3177 | === added file 'sounds/sounds_test.go' |
3178 | --- sounds/sounds_test.go 1970-01-01 00:00:00 +0000 |
3179 | +++ sounds/sounds_test.go 2014-07-07 14:23:18 +0000 |
3180 | @@ -0,0 +1,85 @@ |
3181 | +/* |
3182 | + Copyright 2014 Canonical Ltd. |
3183 | + |
3184 | + This program is free software: you can redistribute it and/or modify it |
3185 | + under the terms of the GNU General Public License version 3, as published |
3186 | + by the Free Software Foundation. |
3187 | + |
3188 | + This program is distributed in the hope that it will be useful, but |
3189 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
3190 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3191 | + PURPOSE. See the GNU General Public License for more details. |
3192 | + |
3193 | + You should have received a copy of the GNU General Public License along |
3194 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
3195 | +*/ |
3196 | + |
3197 | +package sounds |
3198 | + |
3199 | +import ( |
3200 | + "errors" |
3201 | + "os" |
3202 | + "path" |
3203 | + "testing" |
3204 | + |
3205 | + . "launchpad.net/gocheck" |
3206 | + |
3207 | + "launchpad.net/ubuntu-push/launch_helper" |
3208 | + helpers "launchpad.net/ubuntu-push/testing" |
3209 | +) |
3210 | + |
3211 | +func TestSounds(t *testing.T) { TestingT(t) } |
3212 | + |
3213 | +type soundsSuite struct { |
3214 | + log *helpers.TestLogger |
3215 | +} |
3216 | + |
3217 | +var _ = Suite(&soundsSuite{}) |
3218 | + |
3219 | +func (ss *soundsSuite) SetUpTest(c *C) { |
3220 | + ss.log = helpers.NewTestLogger(c, "debug") |
3221 | +} |
3222 | + |
3223 | +func (ss *soundsSuite) TestNew(c *C) { |
3224 | + s := New(ss.log) |
3225 | + c.Check(s.log, Equals, ss.log) |
3226 | + c.Check(s.player, Equals, "paplay") |
3227 | +} |
3228 | + |
3229 | +func (ss *soundsSuite) TestPresent(c *C) { |
3230 | + s := &Sound{ |
3231 | + player: "echo", log: ss.log, |
3232 | + dataFind: func(s string) (string, error) { return s, nil }, |
3233 | + } |
3234 | + |
3235 | + c.Check(s.Present("com.example.test_test", "", |
3236 | + &launch_helper.Notification{Sound: "hello"}), Equals, true) |
3237 | + c.Check(ss.log.Captured(), Matches, `(?sm).* playing sound com.example.test/hello using echo`) |
3238 | +} |
3239 | + |
3240 | +func (ss *soundsSuite) TestPresentFails(c *C) { |
3241 | + s := &Sound{player: "/", log: ss.log} |
3242 | + |
3243 | + // nil notification |
3244 | + c.Check(s.Present("", "", nil), Equals, false) |
3245 | + // no Sound |
3246 | + c.Check(s.Present("", "", &launch_helper.Notification{}), Equals, false) |
3247 | + // bad player |
3248 | + c.Check(s.Present("", "", &launch_helper.Notification{Sound: "hello"}), Equals, false) |
3249 | + s.player = "echo" |
3250 | + // bad app id |
3251 | + c.Check(s.Present("", "", &launch_helper.Notification{Sound: "hello"}), Equals, false) |
3252 | + s.dataFind = func(string) (string, error) { return "", errors.New("nope") } |
3253 | + s.dataDirs = func() []string { return []string{""} } |
3254 | + // no file found |
3255 | + c.Check(s.Present("com.example.test_test", "", &launch_helper.Notification{Sound: "hello"}), Equals, false) |
3256 | + |
3257 | + // and now, just to prove it would've worked, |
3258 | + |
3259 | + d := c.MkDir() |
3260 | + f, err := os.Create(path.Join(d, "hello")) |
3261 | + c.Assert(err, IsNil) |
3262 | + f.Close() |
3263 | + s.dataDirs = func() []string { return []string{"", d} } |
3264 | + c.Check(s.Present("com.example.test_test", "", &launch_helper.Notification{Sound: "hello"}), Equals, true) |
3265 | +} |
3266 | |
3267 | === modified file 'testing/helpers.go' |
3268 | --- testing/helpers.go 2014-06-23 12:46:28 +0000 |
3269 | +++ testing/helpers.go 2014-07-07 14:23:18 +0000 |
3270 | @@ -20,6 +20,7 @@ |
3271 | import ( |
3272 | "encoding/json" |
3273 | "fmt" |
3274 | + "net/url" |
3275 | "os" |
3276 | "path" |
3277 | "path/filepath" |
3278 | @@ -160,3 +161,12 @@ |
3279 | } |
3280 | return res |
3281 | } |
3282 | + |
3283 | +// ParseURL parses a URL conveniently. |
3284 | +func ParseURL(s string) *url.URL { |
3285 | + purl, err := url.Parse(s) |
3286 | + if err != nil { |
3287 | + panic(err) |
3288 | + } |
3289 | + return purl |
3290 | +} |
3291 | |
3292 | === modified file 'whoopsie/identifier/identifier.go' |
3293 | --- whoopsie/identifier/identifier.go 2014-06-13 12:23:03 +0000 |
3294 | +++ whoopsie/identifier/identifier.go 2014-07-07 14:23:18 +0000 |
3295 | @@ -62,6 +62,7 @@ |
3296 | if gerr == nil && cs != nil { |
3297 | goto Success |
3298 | } |
3299 | + C.g_clear_error(&gerr) |
3300 | time.Sleep(600 * time.Millisecond) |
3301 | } |
3302 | return errors.New("whoopsie_identifier_generate still bad after 2m; giving up") |