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