Merge lp:~chipaca/ubuntu-push/merge-automatic into lp:ubuntu-push
- merge-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: | 119 |
Proposed branch: | lp:~chipaca/ubuntu-push/merge-automatic |
Merge into: | lp:ubuntu-push |
Diff against target: |
3155 lines (+1310/-582) 44 files modified
Makefile (+6/-4) bus/haptic/haptic.go (+10/-11) bus/haptic/haptic_test.go (+26/-37) click/click.go (+3/-3) click/click_test.go (+1/-1) client/client.go (+25/-10) client/client_test.go (+38/-8) client/service/common.go (+3/-3) client/service/common_test.go (+12/-3) client/service/postal.go (+16/-4) client/service/postal_test.go (+57/-47) client/service/service_test.go (+2/-1) debian/changelog (+22/-0) debian/config.json (+3/-1) debian/control (+1/-1) debian/ubuntu-push-client.conf (+4/-2) debian/ubuntu-push-client.install (+1/-1) docs/highlevel.txt (+325/-0) docs/lowlevel.txt (+11/-12) identifier/identifier.go (+59/-0) identifier/identifier_test.go (+59/-0) identifier/testing/testing.go (+58/-0) identifier/testing/testing_test.go (+57/-0) launch_helper/helper_output.go (+70/-15) launch_helper/helper_output_test.go (+96/-0) launch_helper/helper_test.go (+1/-1) launch_helper/kindpool_test.go (+2/-1) messaging/cmessaging/cmessaging.go (+1/-1) scripts/connect-many.py (+29/-0) scripts/goctest (+46/-0) scripts/software-updates-helper.py (+0/-45) server/listener/listener_test.go (+34/-22) sounds/sounds.go (+30/-6) sounds/sounds_test.go (+45/-5) tests/autopilot/push_notifications/tests/__init__.py (+51/-1) tests/autopilot/push_notifications/tests/test_broadcast_notifications.py (+61/-14) tests/autopilot/push_notifications/tests/test_unicast_notifications.py (+24/-21) tests/autopilot/run.sh (+6/-2) tests/autopilot/setup.sh (+15/-5) whoopsie/doc.go (+0/-18) whoopsie/identifier/identifier.go (+0/-78) whoopsie/identifier/identifier_test.go (+0/-58) whoopsie/identifier/testing/testing.go (+0/-70) whoopsie/identifier/testing/testing_test.go (+0/-70) |
To merge this branch: | bzr merge lp:~chipaca/ubuntu-push/merge-automatic |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Push Hackers | Pending | ||
Review via email: mp+230410@code.launchpad.net |
Commit message
Roberto's branch + fixes to tests that failed in chroot.
Description of the change
Roberto's branch + fixes to tests that failed in chroot.
To post a comment you must log in.
- 119. By Roberto Alsina
-
Roberto's branch + fixes to tests that failed in chroot.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' |
2 | --- Makefile 2014-06-19 21:31:23 +0000 |
3 | +++ Makefile 2014-08-12 02:32:01 +0000 |
4 | @@ -13,6 +13,8 @@ |
5 | GODEPS += code.google.com/p/gosqlite/sqlite3 |
6 | GODEPS += code.google.com/p/go-uuid/uuid |
7 | |
8 | +GOTEST := ./scripts/goctest |
9 | + |
10 | TOTEST = $(shell env GOPATH=$(GOPATH) go list $(PROJECT)/...|grep -v acceptance|grep -v http13client ) |
11 | TOBUILD = $(shell grep -lr '^package main') |
12 | |
13 | @@ -40,10 +42,10 @@ |
14 | $(GOPATH)/bin/godeps -t $(TOTEST) $(foreach i,$(TOBUILD),$(dir $(PROJECT)/$(i))) 2>/dev/null | cat > $@ |
15 | |
16 | check: |
17 | - go test $(TESTFLAGS) $(TOTEST) |
18 | + $(GOTEST) $(TESTFLAGS) $(TOTEST) |
19 | |
20 | check-race: |
21 | - go test $(TESTFLAGS) -race $(TOTEST) |
22 | + $(GOTEST) $(TESTFLAGS) -race $(TOTEST) |
23 | |
24 | acceptance: |
25 | cd server/acceptance; ./acceptance.sh |
26 | @@ -83,14 +85,14 @@ |
27 | bzr clean-tree --verbose --ignored --force |
28 | |
29 | coverage-summary: |
30 | - go test $(TESTFLAGS) -a -cover $(TOTEST) |
31 | + $(GOTEST) $(TESTFLAGS) -a -cover $(TOTEST) |
32 | |
33 | coverage-html: |
34 | mkdir -p coverhtml |
35 | for pkg in $(TOTEST); do \ |
36 | relname="$${pkg#$(PROJECT)/}" ; \ |
37 | mkdir -p coverhtml/$$(dirname $${relname}) ; \ |
38 | - go test $(TESTFLAGS) -a -coverprofile=coverhtml/$${relname}.out $$pkg ; \ |
39 | + $(GOTEST) $(TESTFLAGS) -a -coverprofile=coverhtml/$${relname}.out $$pkg ; \ |
40 | if [ -f coverhtml/$${relname}.out ] ; then \ |
41 | go tool cover -html=coverhtml/$${relname}.out -o coverhtml/$${relname}.html ; \ |
42 | go tool cover -func=coverhtml/$${relname}.out -o coverhtml/$${relname}.txt ; \ |
43 | |
44 | === modified file 'bus/haptic/haptic.go' |
45 | --- bus/haptic/haptic.go 2014-07-25 10:25:59 +0000 |
46 | +++ bus/haptic/haptic.go 2014-08-12 02:32:01 +0000 |
47 | @@ -34,13 +34,14 @@ |
48 | |
49 | // Haptic encapsulates info needed to call out to usensord/haptic |
50 | type Haptic struct { |
51 | - bus bus.Endpoint |
52 | - log logger.Logger |
53 | + bus bus.Endpoint |
54 | + log logger.Logger |
55 | + fallback *launch_helper.Vibration |
56 | } |
57 | |
58 | // New returns a new Haptic that'll use the provided bus.Endpoint |
59 | -func New(endp bus.Endpoint, log logger.Logger) *Haptic { |
60 | - return &Haptic{endp, log} |
61 | +func New(endp bus.Endpoint, log logger.Logger, fallback *launch_helper.Vibration) *Haptic { |
62 | + return &Haptic{endp, log, fallback} |
63 | } |
64 | |
65 | // Present presents the notification via a vibrate pattern |
66 | @@ -49,18 +50,16 @@ |
67 | panic("please check notification is not nil before calling present") |
68 | } |
69 | |
70 | - if notification.Vibrate == nil { |
71 | - haptic.log.Debugf("[%s] notification has no Vibrate: %#v", nid, notification.Vibrate) |
72 | + vib := notification.Vibration(haptic.fallback) |
73 | + if vib == nil { |
74 | + haptic.log.Debugf("[%s] notification has no Vibrate.", nid) |
75 | return false |
76 | } |
77 | - pattern := notification.Vibrate.Pattern |
78 | - repeat := notification.Vibrate.Repeat |
79 | + pattern := vib.Pattern |
80 | + repeat := vib.Repeat |
81 | if repeat == 0 { |
82 | repeat = 1 |
83 | } |
84 | - if notification.Vibrate.Duration != 0 { |
85 | - pattern = []uint32{notification.Vibrate.Duration} |
86 | - } |
87 | if len(pattern) == 0 { |
88 | haptic.log.Debugf("[%s] not enough information in the Vibrate to create a pattern", nid) |
89 | return false |
90 | |
91 | === modified file 'bus/haptic/haptic_test.go' |
92 | --- bus/haptic/haptic_test.go 2014-07-25 10:25:59 +0000 |
93 | +++ bus/haptic/haptic_test.go 2014-08-12 02:32:01 +0000 |
94 | @@ -17,6 +17,7 @@ |
95 | package haptic |
96 | |
97 | import ( |
98 | + "encoding/json" |
99 | "testing" |
100 | |
101 | . "launchpad.net/gocheck" |
102 | @@ -47,8 +48,8 @@ |
103 | func (hs *hapticSuite) TestPresentPresents(c *C) { |
104 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
105 | |
106 | - ec := New(endp, hs.log) |
107 | - notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2}} |
108 | + ec := New(endp, hs.log, nil) |
109 | + notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100], "repeat": 2}`)} |
110 | c.Check(ec.Present(hs.app, "nid", ¬if), Equals, true) |
111 | callArgs := testibus.GetCallArgs(endp) |
112 | c.Assert(callArgs, HasLen, 1) |
113 | @@ -60,9 +61,9 @@ |
114 | func (hs *hapticSuite) TestPresentDefaultsRepeatTo1(c *C) { |
115 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
116 | |
117 | - ec := New(endp, hs.log) |
118 | + ec := New(endp, hs.log, nil) |
119 | // note: no Repeat: |
120 | - notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Pattern: []uint32{200, 100}}} |
121 | + notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100]}`)} |
122 | c.Check(ec.Present(hs.app, "nid", ¬if), Equals, true) |
123 | callArgs := testibus.GetCallArgs(endp) |
124 | c.Assert(callArgs, HasLen, 1) |
125 | @@ -71,52 +72,40 @@ |
126 | c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200, 100}, uint32(1)}) |
127 | } |
128 | |
129 | -// check that Present() makes a Pattern of [Duration] if Duration is given |
130 | -func (hs *hapticSuite) TestPresentBuildsPatternWithDuration(c *C) { |
131 | - endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
132 | - |
133 | - ec := New(endp, hs.log) |
134 | - // note: no Repeat, no Pattern, just Duration: |
135 | - notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Duration: 200}} |
136 | - c.Check(ec.Present(hs.app, "nid", ¬if), Equals, true) |
137 | - callArgs := testibus.GetCallArgs(endp) |
138 | - c.Assert(callArgs, HasLen, 1) |
139 | - c.Check(callArgs[0].Member, Equals, "VibratePattern") |
140 | - // note: Pattern of [Duration], Repeat of 1: |
141 | - c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200}, uint32(1)}) |
142 | -} |
143 | - |
144 | -// check that Present() ignores Pattern and makes a Pattern of [Duration] if Duration is given |
145 | -func (hs *hapticSuite) TestPresentOverrides(c *C) { |
146 | - endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
147 | - |
148 | - ec := New(endp, hs.log) |
149 | - // note: Duration given, as well as Pattern; Repeat given as 0: |
150 | - notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Duration: 200, Pattern: []uint32{500}, Repeat: 0}} |
151 | - c.Check(ec.Present(hs.app, "nid", ¬if), Equals, true) |
152 | - callArgs := testibus.GetCallArgs(endp) |
153 | - c.Assert(callArgs, HasLen, 1) |
154 | - c.Check(callArgs[0].Member, Equals, "VibratePattern") |
155 | - // note: Pattern of [Duration], Repeat of 1: |
156 | - c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200}, uint32(1)}) |
157 | -} |
158 | - |
159 | // check that Present() doesn't call VibratePattern if things are not right |
160 | func (hs *hapticSuite) TestSkipIfMissing(c *C) { |
161 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
162 | |
163 | - ec := New(endp, hs.log) |
164 | + ec := New(endp, hs.log, nil) |
165 | // no Vibration in the notificaton |
166 | c.Check(ec.Present(hs.app, "", &launch_helper.Notification{}), Equals, false) |
167 | // empty Vibration |
168 | - c.Check(ec.Present(hs.app, "", &launch_helper.Notification{Vibrate: &launch_helper.Vibration{}}), Equals, false) |
169 | + c.Check(ec.Present(hs.app, "", &launch_helper.Notification{RawVibration: nil}), Equals, false) |
170 | + // empty empty vibration |
171 | + c.Check(ec.Present(hs.app, "", &launch_helper.Notification{RawVibration: json.RawMessage(`{}`)}), Equals, false) |
172 | } |
173 | |
174 | // check that Present() panics if the notification is nil |
175 | func (hs *hapticSuite) TestPanicsIfNil(c *C) { |
176 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
177 | |
178 | - ec := New(endp, hs.log) |
179 | + ec := New(endp, hs.log, nil) |
180 | // no notification at all |
181 | c.Check(func() { ec.Present(hs.app, "", nil) }, Panics, `please check notification is not nil before calling present`) |
182 | } |
183 | + |
184 | +// check that Present() uses the fallback if appropriate |
185 | +func (hs *hapticSuite) TestPresentPresentsFallback(c *C) { |
186 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
187 | + fallback := &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2} |
188 | + |
189 | + ec := New(endp, hs.log, fallback) |
190 | + notif := launch_helper.Notification{RawVibration: json.RawMessage(`false`)} |
191 | + c.Check(ec.Present(hs.app, "nid", ¬if), Equals, false) |
192 | + notif = launch_helper.Notification{RawVibration: json.RawMessage(`true`)} |
193 | + c.Check(ec.Present(hs.app, "nid", ¬if), Equals, true) |
194 | + callArgs := testibus.GetCallArgs(endp) |
195 | + c.Assert(callArgs, HasLen, 1) |
196 | + c.Check(callArgs[0].Member, Equals, "VibratePattern") |
197 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200, 100}, uint32(2)}) |
198 | +} |
199 | |
200 | === modified file 'click/click.go' |
201 | --- click/click.go 2014-07-29 15:36:00 +0000 |
202 | +++ click/click.go 2014-08-12 02:32:01 +0000 |
203 | @@ -51,7 +51,7 @@ |
204 | |
205 | var ( |
206 | ErrInvalidAppId = errors.New("invalid application id") |
207 | - ErrMissingAppId = errors.New("missing application id") |
208 | + ErrMissingApp = errors.New("application not installed") |
209 | ) |
210 | |
211 | func ParseAppId(id string) (*AppId, error) { |
212 | @@ -161,14 +161,14 @@ |
213 | |
214 | // ParseAndVerifyAppId parses the given app id and checks if the |
215 | // corresponding app is installed, returning the parsed id or |
216 | -// ErrInvalidAppId, or the parsed id and ErrMissingAppId respectively. |
217 | +// ErrInvalidAppId, or the parsed id and ErrMissingApp respectively. |
218 | func ParseAndVerifyAppId(id string, installedChecker InstalledChecker) (*AppId, error) { |
219 | app, err := ParseAppId(id) |
220 | if err != nil { |
221 | return nil, err |
222 | } |
223 | if installedChecker != nil && !installedChecker.Installed(app, true) { |
224 | - return app, ErrMissingAppId |
225 | + return app, ErrMissingApp |
226 | } |
227 | return app, nil |
228 | } |
229 | |
230 | === modified file 'click/click_test.go' |
231 | --- click/click_test.go 2014-07-29 15:36:00 +0000 |
232 | +++ click/click_test.go 2014-08-12 02:32:01 +0000 |
233 | @@ -178,7 +178,7 @@ |
234 | c.Check(app.Application, Equals, "baz") |
235 | |
236 | app, err = ParseAndVerifyAppId("_non-existent-app", u) |
237 | - c.Assert(err, Equals, ErrMissingAppId) |
238 | + c.Assert(err, Equals, ErrMissingApp) |
239 | c.Check(app, NotNil) |
240 | c.Check(app.Original(), Equals, "_non-existent-app") |
241 | } |
242 | |
243 | === modified file 'client/client.go' |
244 | --- client/client.go 2014-07-21 17:38:28 +0000 |
245 | +++ client/client.go 2014-08-12 02:32:01 +0000 |
246 | @@ -41,10 +41,11 @@ |
247 | "launchpad.net/ubuntu-push/client/session" |
248 | "launchpad.net/ubuntu-push/client/session/seenstate" |
249 | "launchpad.net/ubuntu-push/config" |
250 | + "launchpad.net/ubuntu-push/identifier" |
251 | + "launchpad.net/ubuntu-push/launch_helper" |
252 | "launchpad.net/ubuntu-push/logger" |
253 | "launchpad.net/ubuntu-push/protocol" |
254 | "launchpad.net/ubuntu-push/util" |
255 | - "launchpad.net/ubuntu-push/whoopsie/identifier" |
256 | ) |
257 | |
258 | // ClientConfig holds the client configuration |
259 | @@ -67,6 +68,9 @@ |
260 | RegistrationURL string `json:"registration_url"` |
261 | // The logging level (one of "debug", "info", "error") |
262 | LogLevel logger.ConfigLogLevel `json:"log_level"` |
263 | + // fallback values for simplified notification usage |
264 | + FallbackVibration *launch_helper.Vibration `json:"fallback_vibration"` |
265 | + FallbackSound string `json:"fallback_sound"` |
266 | } |
267 | |
268 | // PushService is the interface we use of service.PushService. |
269 | @@ -123,6 +127,8 @@ |
270 | return client |
271 | } |
272 | |
273 | +var newIdentifier = identifier.New |
274 | + |
275 | // configure loads its configuration, and sets it up. |
276 | func (client *PushClient) configure() error { |
277 | _, err := os.Stat(client.configPath) |
278 | @@ -152,7 +158,10 @@ |
279 | client.unregisterCh = make(chan *click.AppId, 10) |
280 | |
281 | // overridden for testing |
282 | - client.idder = identifier.New() |
283 | + client.idder, err = newIdentifier() |
284 | + if err != nil { |
285 | + return err |
286 | + } |
287 | client.connectivityEndp = bus.SystemBus.Endpoint(networkmanager.BusAddress, client.log) |
288 | client.systemImageEndp = bus.SystemBus.Endpoint(systemimage.BusAddress, client.log) |
289 | |
290 | @@ -203,6 +212,15 @@ |
291 | return setup, nil |
292 | } |
293 | |
294 | +// derivePostalServiceSetup derives the service setup from the client configuration bits. |
295 | +func (client *PushClient) derivePostalServiceSetup() *service.PostalServiceSetup { |
296 | + return &service.PostalServiceSetup{ |
297 | + InstalledChecker: client.installedChecker, |
298 | + FallbackVibration: client.config.FallbackVibration, |
299 | + FallbackSound: client.config.FallbackSound, |
300 | + } |
301 | +} |
302 | + |
303 | // getAuthorization gets the authorization blob to send to the server |
304 | func (client *PushClient) getAuthorization(url string) string { |
305 | client.log.Debugf("getting authorization for %s", url) |
306 | @@ -222,16 +240,12 @@ |
307 | } |
308 | } |
309 | |
310 | -// getDeviceId gets the whoopsie identifier for the device |
311 | +// getDeviceId gets the identifier for the device |
312 | func (client *PushClient) getDeviceId() error { |
313 | - err := client.idder.Generate() |
314 | - if err != nil { |
315 | - return err |
316 | - } |
317 | baseId := client.idder.String() |
318 | b, err := hex.DecodeString(baseId) |
319 | if err != nil { |
320 | - return fmt.Errorf("whoopsie id should be hex: %v", err) |
321 | + return fmt.Errorf("machine-id should be hex: %v", err) |
322 | } |
323 | h := sha256.Sum224(b) |
324 | client.deviceId = base64.StdEncoding.EncodeToString(h[:]) |
325 | @@ -294,7 +308,7 @@ |
326 | switch err { |
327 | default: |
328 | client.log.Debugf("notification %#v for invalid app id %#v.", notif.MsgId, notif.AppId) |
329 | - case click.ErrMissingAppId: |
330 | + case click.ErrMissingApp: |
331 | client.log.Debugf("notification %#v for missing app id %#v.", notif.MsgId, notif.AppId) |
332 | client.unregisterCh <- parsed |
333 | parsed = nil |
334 | @@ -473,7 +487,8 @@ |
335 | } |
336 | |
337 | func (client *PushClient) setupPostalService() error { |
338 | - client.postalService = service.NewPostalService(client.installedChecker, client.log) |
339 | + setup := client.derivePostalServiceSetup() |
340 | + client.postalService = service.NewPostalService(setup, client.log) |
341 | return nil |
342 | } |
343 | |
344 | |
345 | === modified file 'client/client_test.go' |
346 | --- client/client_test.go 2014-07-21 17:38:28 +0000 |
347 | +++ client/client_test.go 2014-08-12 02:32:01 +0000 |
348 | @@ -42,12 +42,13 @@ |
349 | "launchpad.net/ubuntu-push/client/session" |
350 | "launchpad.net/ubuntu-push/client/session/seenstate" |
351 | "launchpad.net/ubuntu-push/config" |
352 | + "launchpad.net/ubuntu-push/identifier" |
353 | + idtesting "launchpad.net/ubuntu-push/identifier/testing" |
354 | + "launchpad.net/ubuntu-push/launch_helper" |
355 | "launchpad.net/ubuntu-push/protocol" |
356 | helpers "launchpad.net/ubuntu-push/testing" |
357 | "launchpad.net/ubuntu-push/testing/condition" |
358 | "launchpad.net/ubuntu-push/util" |
359 | - "launchpad.net/ubuntu-push/whoopsie/identifier" |
360 | - idtesting "launchpad.net/ubuntu-push/whoopsie/identifier/testing" |
361 | ) |
362 | |
363 | func TestClient(t *testing.T) { TestingT(t) } |
364 | @@ -142,6 +143,7 @@ |
365 | |
366 | func (cs *clientSuite) SetUpSuite(c *C) { |
367 | config.IgnoreParsedFlags = true // because configure() uses <flags> |
368 | + newIdentifier = func() (identifier.Id, error) { return idtesting.Settable(), nil } |
369 | cs.timeouts = util.SwapTimeouts([]time.Duration{0}) |
370 | cs.leveldbPath = "" |
371 | } |
372 | @@ -149,11 +151,14 @@ |
373 | func (cs *clientSuite) TearDownSuite(c *C) { |
374 | util.SwapTimeouts(cs.timeouts) |
375 | cs.timeouts = nil |
376 | + newIdentifier = identifier.New |
377 | } |
378 | |
379 | func (cs *clientSuite) writeTestConfig(overrides map[string]interface{}) { |
380 | pem_file := helpers.SourceRelative("../server/acceptance/ssl/testing.cert") |
381 | cfgMap := map[string]interface{}{ |
382 | + "fallback_vibration": &launch_helper.Vibration{Pattern: []uint32{1}}, |
383 | + "fallback_sound": "sounds/ubuntu/notifications/Blip.ogg", |
384 | "connect_timeout": "7ms", |
385 | "exchange_timeout": "10ms", |
386 | "hosts_cache_expiry": "1h", |
387 | @@ -239,7 +244,7 @@ |
388 | c.Check(cli.idder, IsNil) |
389 | err := cli.configure() |
390 | c.Assert(err, IsNil) |
391 | - c.Assert(cli.idder, FitsTypeOf, identifier.New()) |
392 | + c.Assert(cli.idder, NotNil) |
393 | } |
394 | |
395 | func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) { |
396 | @@ -471,6 +476,32 @@ |
397 | } |
398 | |
399 | /***************************************************************** |
400 | + derivePostalConfig tests |
401 | +******************************************************************/ |
402 | +func (cs *clientSuite) TestDerivePostalServiceSetup(c *C) { |
403 | + cs.writeTestConfig(map[string]interface{}{}) |
404 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
405 | + err := cli.configure() |
406 | + c.Assert(err, IsNil) |
407 | + expected := &service.PostalServiceSetup{ |
408 | + InstalledChecker: cli.installedChecker, |
409 | + FallbackVibration: cli.config.FallbackVibration, |
410 | + FallbackSound: cli.config.FallbackSound, |
411 | + } |
412 | + // sanity check that we are looking at all fields |
413 | + vExpected := reflect.ValueOf(expected).Elem() |
414 | + nf := vExpected.NumField() |
415 | + for i := 0; i < nf; i++ { |
416 | + fv := vExpected.Field(i) |
417 | + // field isn't empty/zero |
418 | + c.Assert(fv.Interface(), Not(DeepEquals), reflect.Zero(fv.Type()).Interface(), Commentf("forgot about: %s", vExpected.Type().Field(i).Name)) |
419 | + } |
420 | + // finally compare |
421 | + setup := cli.derivePostalServiceSetup() |
422 | + c.Check(setup, DeepEquals, expected) |
423 | +} |
424 | + |
425 | +/***************************************************************** |
426 | startService tests |
427 | ******************************************************************/ |
428 | |
429 | @@ -536,7 +567,7 @@ |
430 | func (cs *clientSuite) TestGetDeviceIdWorks(c *C) { |
431 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
432 | cli.log = cs.log |
433 | - cli.idder = identifier.New() |
434 | + cli.idder, _ = identifier.New() |
435 | c.Check(cli.deviceId, Equals, "") |
436 | c.Check(cli.getDeviceId(), IsNil) |
437 | c.Check(cli.deviceId, HasLen, 40) |
438 | @@ -550,14 +581,14 @@ |
439 | c.Check(cli.getDeviceId(), NotNil) |
440 | } |
441 | |
442 | -func (cs *clientSuite) TestGetDeviceIdWhoopsieDoesTheUnexpected(c *C) { |
443 | +func (cs *clientSuite) TestGetDeviceIdIdentifierDoesTheUnexpected(c *C) { |
444 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
445 | cli.log = cs.log |
446 | settable := idtesting.Settable() |
447 | cli.idder = settable |
448 | settable.Set("not-hex") |
449 | c.Check(cli.deviceId, Equals, "") |
450 | - c.Check(cli.getDeviceId(), ErrorMatches, "whoopsie id should be hex: .*") |
451 | + c.Check(cli.getDeviceId(), ErrorMatches, "machine-id should be hex: .*") |
452 | } |
453 | |
454 | /***************************************************************** |
455 | @@ -1084,7 +1115,6 @@ |
456 | } |
457 | |
458 | func (cs *clientSuite) TestStart(c *C) { |
459 | - c.Skip("no dbus") |
460 | if !cs.hasDbus() { |
461 | c.Skip("no dbus") |
462 | } |
463 | @@ -1111,7 +1141,7 @@ |
464 | // and now everthing is better! We have a config, |
465 | c.Check(string(cli.config.Addr), Equals, ":0") |
466 | // and a device id, |
467 | - c.Check(cli.deviceId, HasLen, 40) |
468 | + c.Check(cli.deviceId, HasLen, 32) |
469 | // and a session, |
470 | c.Check(cli.session, NotNil) |
471 | // and a bus, |
472 | |
473 | === modified file 'client/service/common.go' |
474 | --- client/service/common.go 2014-07-15 20:53:41 +0000 |
475 | +++ client/service/common.go 2014-08-12 02:32:01 +0000 |
476 | @@ -52,7 +52,7 @@ |
477 | ErrBadArgCount = errors.New("wrong number of arguments") |
478 | ErrBadArgType = errors.New("bad argument type") |
479 | ErrBadJSON = errors.New("bad json data") |
480 | - ErrBadAppId = errors.New("package must be prefix of app id") |
481 | + ErrAppIdMismatch = errors.New("package must be prefix of app id") |
482 | ) |
483 | |
484 | // IsRunning() returns whether the service's state is StateRunning |
485 | @@ -118,10 +118,10 @@ |
486 | pkgname := string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:]))) |
487 | app, err = click.ParseAndVerifyAppId(id, svc.installedChecker) |
488 | if err != nil { |
489 | - return nil, ErrBadAppId |
490 | + return nil, err |
491 | } |
492 | if !app.InPackage(pkgname) { |
493 | - return nil, ErrBadAppId |
494 | + return nil, ErrAppIdMismatch |
495 | } |
496 | return |
497 | } |
498 | |
499 | === modified file 'client/service/common_test.go' |
500 | --- client/service/common_test.go 2014-07-08 08:32:32 +0000 |
501 | +++ client/service/common_test.go 2014-08-12 02:32:01 +0000 |
502 | @@ -18,6 +18,8 @@ |
503 | |
504 | import ( |
505 | . "launchpad.net/gocheck" |
506 | + |
507 | + "launchpad.net/ubuntu-push/click" |
508 | ) |
509 | |
510 | type commonSuite struct{} |
511 | @@ -35,10 +37,16 @@ |
512 | c.Check(app.Original(), Equals, anAppId) |
513 | } |
514 | |
515 | +type fakeInstalledChecker struct{} |
516 | + |
517 | +func (fakeInstalledChecker) Installed(app *click.AppId, setVersion bool) bool { |
518 | + return app.Original()[0] == 'c' |
519 | +} |
520 | + |
521 | func (cs *commonSuite) TestGrabDBusPackageAndAppIdFails(c *C) { |
522 | svc := new(DBusService) |
523 | + svc.installedChecker = fakeInstalledChecker{} |
524 | aDBusPath := "/com/ubuntu/Postal/com_2eexample_2etest" |
525 | - aDBusPath2 := "/com/ubuntu/Postal/com_2efoo_2ebar" |
526 | aPackage := "com.example.test" |
527 | anAppId := aPackage + "_test" |
528 | |
529 | @@ -52,8 +60,9 @@ |
530 | {aDBusPath, []interface{}{anAppId}, 1, ErrBadArgCount}, |
531 | {aDBusPath, []interface{}{anAppId, anAppId}, 0, ErrBadArgCount}, |
532 | {aDBusPath, []interface{}{1}, 0, ErrBadArgType}, |
533 | - {aDBusPath, []interface{}{aPackage}, 0, ErrBadAppId}, |
534 | - {aDBusPath2, []interface{}{anAppId}, 0, ErrBadAppId}, |
535 | + {aDBusPath, []interface{}{aPackage}, 0, click.ErrInvalidAppId}, |
536 | + {aDBusPath, []interface{}{"x" + anAppId}, 0, click.ErrMissingApp}, |
537 | + {aDBusPath, []interface{}{"c" + anAppId}, 0, ErrAppIdMismatch}, |
538 | } { |
539 | comment := Commentf("iteration #%d", i) |
540 | app, err := svc.grabDBusPackageAndAppId(s.path, s.args, s.numExtra) |
541 | |
542 | === modified file 'client/service/postal.go' |
543 | --- client/service/postal.go 2014-08-01 14:17:04 +0000 |
544 | +++ client/service/postal.go 2014-08-12 02:32:01 +0000 |
545 | @@ -56,6 +56,13 @@ |
546 | Clear(*click.AppId, ...string) int |
547 | } |
548 | |
549 | +// PostalServiceSetup is a configuration object for the service |
550 | +type PostalServiceSetup struct { |
551 | + InstalledChecker click.InstalledChecker |
552 | + FallbackVibration *launch_helper.Vibration |
553 | + FallbackSound string |
554 | +} |
555 | + |
556 | // PostalService is the dbus api |
557 | type PostalService struct { |
558 | DBusService |
559 | @@ -80,6 +87,9 @@ |
560 | // the url dispatcher, used for stuff. |
561 | urlDispatcher urldispatcher.URLDispatcher |
562 | windowStack *windowstack.WindowStack |
563 | + // fallback values for simplified notification usage |
564 | + fallbackVibration *launch_helper.Vibration |
565 | + fallbackSound string |
566 | } |
567 | |
568 | var ( |
569 | @@ -96,11 +106,13 @@ |
570 | ) |
571 | |
572 | // NewPostalService() builds a new service and returns it. |
573 | -func NewPostalService(installedChecker click.InstalledChecker, log logger.Logger) *PostalService { |
574 | +func NewPostalService(setup *PostalServiceSetup, log logger.Logger) *PostalService { |
575 | var svc = &PostalService{} |
576 | svc.Log = log |
577 | svc.Bus = bus.SessionBus.Endpoint(PostalServiceBusAddress, log) |
578 | - svc.installedChecker = installedChecker |
579 | + svc.installedChecker = setup.InstalledChecker |
580 | + svc.fallbackVibration = setup.FallbackVibration |
581 | + svc.fallbackSound = setup.FallbackSound |
582 | svc.NotificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, log) |
583 | svc.EmblemCounterEndp = bus.SessionBus.Endpoint(emblemcounter.BusAddress, log) |
584 | svc.HapticEndp = bus.SessionBus.Endpoint(haptic.BusAddress, log) |
585 | @@ -144,8 +156,8 @@ |
586 | svc.urlDispatcher = urldispatcher.New(svc.URLDispatcherEndp, svc.Log) |
587 | svc.notifications = notifications.Raw(svc.NotificationsEndp, svc.Log) |
588 | svc.emblemCounter = emblemcounter.New(svc.EmblemCounterEndp, svc.Log) |
589 | - svc.haptic = haptic.New(svc.HapticEndp, svc.Log) |
590 | - svc.sound = sounds.New(svc.Log) |
591 | + svc.haptic = haptic.New(svc.HapticEndp, svc.Log, svc.fallbackVibration) |
592 | + svc.sound = sounds.New(svc.Log, svc.fallbackSound) |
593 | svc.messagingMenu = messaging.New(svc.Log) |
594 | svc.Presenters = []Presenter{ |
595 | svc.notifications, |
596 | |
597 | === modified file 'client/service/postal_test.go' |
598 | --- client/service/postal_test.go 2014-08-01 14:17:04 +0000 |
599 | +++ client/service/postal_test.go 2014-08-12 02:32:01 +0000 |
600 | @@ -133,6 +133,7 @@ |
601 | |
602 | type postalSuite struct { |
603 | log *helpers.TestLogger |
604 | + cfg *PostalServiceSetup |
605 | bus bus.Endpoint |
606 | notifBus bus.Endpoint |
607 | counterBus bus.Endpoint |
608 | @@ -160,6 +161,7 @@ |
609 | ps.oldIsBlisted = isBlacklisted |
610 | isBlacklisted = func(*click.AppId) bool { return ps.blacklisted } |
611 | ps.log = helpers.NewTestLogger(c, "debug") |
612 | + ps.cfg = &PostalServiceSetup{} |
613 | ps.bus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
614 | ps.notifBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
615 | ps.counterBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
616 | @@ -204,7 +206,7 @@ |
617 | } |
618 | |
619 | func (ps *postalSuite) TestStart(c *C) { |
620 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
621 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
622 | c.Check(svc.IsRunning(), Equals, false) |
623 | c.Check(svc.Start(), IsNil) |
624 | c.Check(svc.IsRunning(), Equals, true) |
625 | @@ -212,30 +214,30 @@ |
626 | } |
627 | |
628 | func (ps *postalSuite) TestStartTwice(c *C) { |
629 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
630 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
631 | c.Check(svc.Start(), IsNil) |
632 | c.Check(svc.Start(), Equals, ErrAlreadyStarted) |
633 | svc.Stop() |
634 | } |
635 | |
636 | func (ps *postalSuite) TestStartNoLog(c *C) { |
637 | - svc := ps.replaceBuses(NewPostalService(nil, nil)) |
638 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, nil)) |
639 | c.Check(svc.Start(), Equals, ErrNotConfigured) |
640 | } |
641 | |
642 | func (ps *postalSuite) TestStartNoBus(c *C) { |
643 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
644 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
645 | svc.Bus = nil |
646 | c.Check(svc.Start(), Equals, ErrNotConfigured) |
647 | |
648 | - svc = ps.replaceBuses(NewPostalService(nil, ps.log)) |
649 | + svc = ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
650 | svc.NotificationsEndp = nil |
651 | c.Check(svc.Start(), Equals, ErrNotConfigured) |
652 | } |
653 | |
654 | func (ps *postalSuite) TestTakeTheBusFail(c *C) { |
655 | nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(false)) |
656 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
657 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
658 | svc.NotificationsEndp = nEndp |
659 | _, err := svc.takeTheBus() |
660 | c.Check(err, NotNil) |
661 | @@ -243,7 +245,7 @@ |
662 | |
663 | func (ps *postalSuite) TestTakeTheBusOk(c *C) { |
664 | nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(true), []interface{}{uint32(1), "hello"}) |
665 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
666 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
667 | svc.NotificationsEndp = nEndp |
668 | _, err := svc.takeTheBus() |
669 | c.Check(err, IsNil) |
670 | @@ -251,14 +253,14 @@ |
671 | |
672 | func (ps *postalSuite) TestStartFailsOnBusDialFailure(c *C) { |
673 | // XXX actually, we probably want to autoredial this |
674 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
675 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
676 | svc.Bus = testibus.NewTestingEndpoint(condition.Work(false), nil) |
677 | c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`) |
678 | svc.Stop() |
679 | } |
680 | |
681 | func (ps *postalSuite) TestStartGrabsName(c *C) { |
682 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
683 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
684 | c.Assert(svc.Start(), IsNil) |
685 | callArgs := testibus.GetCallArgs(ps.bus) |
686 | defer svc.Stop() |
687 | @@ -267,7 +269,7 @@ |
688 | } |
689 | |
690 | func (ps *postalSuite) TestStopClosesBus(c *C) { |
691 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
692 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
693 | c.Assert(svc.Start(), IsNil) |
694 | svc.Stop() |
695 | callArgs := testibus.GetCallArgs(ps.bus) |
696 | @@ -279,7 +281,7 @@ |
697 | // post() tests |
698 | |
699 | func (ps *postalSuite) TestPostHappyPath(c *C) { |
700 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
701 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
702 | svc.msgHandler = nil |
703 | ch := installTickMessageHandler(svc) |
704 | svc.launchers = map[string]launch_helper.HelperLauncher{ |
705 | @@ -322,7 +324,7 @@ |
706 | {[]interface{}{anAppId, "zoom"}, ErrBadJSON}, |
707 | {[]interface{}{1, "hello"}, ErrBadArgType}, |
708 | {[]interface{}{1, 2, 3}, ErrBadArgCount}, |
709 | - {[]interface{}{"bar", "hello"}, ErrBadAppId}, |
710 | + {[]interface{}{"bar", "hello"}, click.ErrInvalidAppId}, |
711 | } { |
712 | reg, err := new(PostalService).post(aPackageOnBus, s.args, nil) |
713 | c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
714 | @@ -333,7 +335,7 @@ |
715 | // Post() tests |
716 | |
717 | func (ps *postalSuite) TestPostWorks(c *C) { |
718 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
719 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
720 | svc.msgHandler = nil |
721 | ch := installTickMessageHandler(svc) |
722 | fakeLauncher2 := &fakeHelperLauncher{ch: make(chan []byte)} |
723 | @@ -384,7 +386,7 @@ |
724 | |
725 | func (ps *postalSuite) TestPostCallsMessageHandlerDetails(c *C) { |
726 | ch := make(chan *launch_helper.HelperOutput) |
727 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
728 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
729 | svc.launchers = map[string]launch_helper.HelperLauncher{ |
730 | "click": ps.fakeLauncher, |
731 | } |
732 | @@ -410,7 +412,7 @@ |
733 | } |
734 | |
735 | func (ps *postalSuite) TestAfterMessageHandlerSignal(c *C) { |
736 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
737 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
738 | svc.msgHandler = nil |
739 | |
740 | hInp := &launch_helper.HelperInput{ |
741 | @@ -431,7 +433,7 @@ |
742 | } |
743 | |
744 | func (ps *postalSuite) TestFailingMessageHandlerSurvived(c *C) { |
745 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
746 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
747 | svc.SetMessageHandler(func(*click.AppId, string, *launch_helper.HelperOutput) bool { |
748 | return false |
749 | }) |
750 | @@ -453,7 +455,7 @@ |
751 | // |
752 | // Notifications tests |
753 | func (ps *postalSuite) TestNotificationsWorks(c *C) { |
754 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
755 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
756 | nots, err := svc.popAll(aPackageOnBus, []interface{}{anAppId}, nil) |
757 | c.Assert(err, IsNil) |
758 | c.Assert(nots, NotNil) |
759 | @@ -487,7 +489,7 @@ |
760 | {nil, ErrBadArgCount}, |
761 | {[]interface{}{}, ErrBadArgCount}, |
762 | {[]interface{}{1}, ErrBadArgType}, |
763 | - {[]interface{}{"potato"}, ErrBadAppId}, |
764 | + {[]interface{}{"potato"}, click.ErrInvalidAppId}, |
765 | } { |
766 | reg, err := new(PostalService).popAll(aPackageOnBus, s.args, nil) |
767 | c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
768 | @@ -510,7 +512,7 @@ |
769 | |
770 | func (ps *postalSuite) TestMessageHandlerPresents(c *C) { |
771 | endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1)) |
772 | - svc := NewPostalService(nil, ps.log) |
773 | + svc := NewPostalService(ps.cfg, ps.log) |
774 | svc.Bus = endp |
775 | svc.EmblemCounterEndp = endp |
776 | svc.HapticEndp = endp |
777 | @@ -518,13 +520,14 @@ |
778 | svc.URLDispatcherEndp = ps.urlDispBus |
779 | svc.WindowStackEndp = ps.winStackBus |
780 | svc.launchers = map[string]launch_helper.HelperLauncher{} |
781 | + svc.fallbackVibration = &launch_helper.Vibration{Pattern: []uint32{1}} |
782 | c.Assert(svc.Start(), IsNil) |
783 | |
784 | // Persist is false so we just check the log |
785 | card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true, Persist: false} |
786 | - vib := &launch_helper.Vibration{Duration: 500} |
787 | + vib := json.RawMessage(`true`) |
788 | emb := &launch_helper.EmblemCounter{Count: 2, Visible: true} |
789 | - output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card, EmblemCounter: emb, Vibrate: vib}} |
790 | + output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card, EmblemCounter: emb, RawVibration: vib}} |
791 | b := svc.messageHandler(&click.AppId{}, "", output) |
792 | c.Assert(b, Equals, true) |
793 | args := testibus.GetCallArgs(endp) |
794 | @@ -547,7 +550,7 @@ |
795 | |
796 | func (ps *postalSuite) TestMessageHandlerReportsFailedNotifies(c *C) { |
797 | endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), 1) |
798 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
799 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
800 | svc.NotificationsEndp = endp |
801 | c.Assert(svc.Start(), IsNil) |
802 | card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true} |
803 | @@ -559,7 +562,7 @@ |
804 | |
805 | func (ps *postalSuite) TestMessageHandlerInhibition(c *C) { |
806 | endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{{0, "com.example.test_test-app", true, 0}}) |
807 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
808 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
809 | svc.WindowStackEndp = endp |
810 | c.Assert(svc.Start(), IsNil) |
811 | output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{}} // Doesn't matter |
812 | @@ -569,7 +572,7 @@ |
813 | } |
814 | |
815 | func (ps *postalSuite) TestMessageHandlerReportsButIgnoresUnmarshalErrors(c *C) { |
816 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
817 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
818 | c.Assert(svc.Start(), IsNil) |
819 | output := &launch_helper.HelperOutput{[]byte(`broken`), nil} |
820 | b := svc.messageHandler(nil, "", output) |
821 | @@ -579,7 +582,7 @@ |
822 | |
823 | func (ps *postalSuite) TestMessageHandlerReportsButIgnoresNilNotifies(c *C) { |
824 | endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
825 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
826 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
827 | c.Assert(svc.Start(), IsNil) |
828 | svc.NotificationsEndp = endp |
829 | output := &launch_helper.HelperOutput{[]byte(`{}`), nil} |
830 | @@ -589,7 +592,7 @@ |
831 | } |
832 | |
833 | func (ps *postalSuite) TestMessageHandlerInvalidAction(c *C) { |
834 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
835 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
836 | endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false), []string{"com.example.test_test-app"}) |
837 | svc.URLDispatcherEndp = endp |
838 | c.Assert(svc.Start(), IsNil) |
839 | @@ -601,7 +604,7 @@ |
840 | } |
841 | |
842 | func (ps *postalSuite) TestHandleActionsDispatches(c *C) { |
843 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
844 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
845 | fmm := new(fakeMM) |
846 | app, _ := click.ParseAppId("com.example.test_test-app") |
847 | c.Assert(svc.Start(), IsNil) |
848 | @@ -627,7 +630,7 @@ |
849 | } |
850 | |
851 | func (ps *postalSuite) TestHandleMMUActionsDispatches(c *C) { |
852 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
853 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
854 | c.Assert(svc.Start(), IsNil) |
855 | app, _ := click.ParseAppId("com.example.test_test-app") |
856 | aCh := make(chan *notifications.RawAction) |
857 | @@ -650,7 +653,7 @@ |
858 | } |
859 | |
860 | func (ps *postalSuite) TestValidateActions(c *C) { |
861 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
862 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
863 | endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []string{"com.example.test_test-app_0"}) |
864 | svc.URLDispatcherEndp = endp |
865 | c.Assert(svc.Start(), IsNil) |
866 | @@ -661,7 +664,7 @@ |
867 | } |
868 | |
869 | func (ps *postalSuite) TestValidateActionsNoActions(c *C) { |
870 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
871 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
872 | card := launch_helper.Card{} |
873 | notif := &launch_helper.Notification{Card: &card} |
874 | b := svc.validateActions(clickhelp.MustParseAppId("com.example.test_test-app_0"), notif) |
875 | @@ -669,7 +672,7 @@ |
876 | } |
877 | |
878 | func (ps *postalSuite) TestValidateActionsNoCard(c *C) { |
879 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
880 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
881 | notif := &launch_helper.Notification{} |
882 | b := svc.validateActions(clickhelp.MustParseAppId("com.example.test_test-app_0"), notif) |
883 | c.Check(b, Equals, true) |
884 | @@ -695,7 +698,7 @@ |
885 | } |
886 | |
887 | func (ps *postalSuite) TestListPersistent(c *C) { |
888 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
889 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
890 | fmm := new(fakeMM) |
891 | svc.messagingMenu = fmm |
892 | |
893 | @@ -709,17 +712,24 @@ |
894 | } |
895 | |
896 | func (ps *postalSuite) TestListPersistentErrors(c *C) { |
897 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
898 | - _, err := svc.listPersistent(aPackageOnBus, nil, nil) |
899 | - c.Check(err, Equals, ErrBadArgCount) |
900 | - _, err = svc.listPersistent(aPackageOnBus, []interface{}{42}, nil) |
901 | - c.Check(err, Equals, ErrBadArgType) |
902 | - _, err = svc.listPersistent(aPackageOnBus, []interface{}{"xyzzy"}, nil) |
903 | - c.Check(err, Equals, ErrBadAppId) |
904 | + for i, s := range []struct { |
905 | + args []interface{} |
906 | + errt error |
907 | + }{ |
908 | + {nil, ErrBadArgCount}, |
909 | + {[]interface{}{}, ErrBadArgCount}, |
910 | + {[]interface{}{1}, ErrBadArgType}, |
911 | + {[]interface{}{anAppId, 2}, ErrBadArgCount}, |
912 | + {[]interface{}{"bar"}, click.ErrInvalidAppId}, |
913 | + } { |
914 | + reg, err := new(PostalService).listPersistent(aPackageOnBus, s.args, nil) |
915 | + c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
916 | + c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
917 | + } |
918 | } |
919 | |
920 | func (ps *postalSuite) TestClearPersistent(c *C) { |
921 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
922 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
923 | fmm := new(fakeMM) |
924 | svc.messagingMenu = fmm |
925 | |
926 | @@ -730,24 +740,23 @@ |
927 | } |
928 | |
929 | func (ps *postalSuite) TestClearPersistentErrors(c *C) { |
930 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
931 | for i, s := range []struct { |
932 | args []interface{} |
933 | err error |
934 | }{ |
935 | {[]interface{}{}, ErrBadArgCount}, |
936 | {[]interface{}{42}, ErrBadArgType}, |
937 | - {[]interface{}{"xyzzy"}, ErrBadAppId}, |
938 | + {[]interface{}{"xyzzy"}, click.ErrInvalidAppId}, |
939 | {[]interface{}{anAppId, 42}, ErrBadArgType}, |
940 | {[]interface{}{anAppId, "", 42}, ErrBadArgType}, |
941 | } { |
942 | - _, err := svc.clearPersistent(aPackageOnBus, s.args, nil) |
943 | + _, err := new(PostalService).clearPersistent(aPackageOnBus, s.args, nil) |
944 | c.Check(err, Equals, s.err, Commentf("iter %d", i)) |
945 | } |
946 | } |
947 | |
948 | func (ps *postalSuite) TestSetCounter(c *C) { |
949 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
950 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
951 | c.Check(svc.Start(), IsNil) |
952 | |
953 | _, err := svc.setCounter(aPackageOnBus, []interface{}{anAppId, int32(42), true}, nil) |
954 | @@ -765,8 +774,9 @@ |
955 | } |
956 | |
957 | func (ps *postalSuite) TestSetCounterErrors(c *C) { |
958 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
959 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
960 | svc.Start() |
961 | + |
962 | for i, s := range []struct { |
963 | args []interface{} |
964 | err error |
965 | @@ -776,7 +786,7 @@ |
966 | {[]interface{}{anAppId}, ErrBadArgCount}, |
967 | {[]interface{}{anAppId, int32(42)}, ErrBadArgCount}, |
968 | {[]interface{}{anAppId, int32(42), true, "potato"}, ErrBadArgCount}, |
969 | - {[]interface{}{"xyzzy", int32(42), true}, ErrBadAppId}, |
970 | + {[]interface{}{"xyzzy", int32(42), true}, click.ErrInvalidAppId}, |
971 | {[]interface{}{1234567, int32(42), true}, ErrBadArgType}, |
972 | {[]interface{}{anAppId, "potatoe", true}, ErrBadArgType}, |
973 | {[]interface{}{anAppId, int32(42), "ru"}, ErrBadArgType}, |
974 | @@ -787,7 +797,7 @@ |
975 | } |
976 | |
977 | func (ps *postalSuite) TestBlacklisted(c *C) { |
978 | - svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
979 | + svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
980 | svc.Start() |
981 | ps.blacklisted = false |
982 | |
983 | |
984 | === modified file 'client/service/service_test.go' |
985 | --- client/service/service_test.go 2014-07-14 19:51:43 +0000 |
986 | +++ client/service/service_test.go 2014-08-12 02:32:01 +0000 |
987 | @@ -28,6 +28,7 @@ |
988 | |
989 | "launchpad.net/ubuntu-push/bus" |
990 | testibus "launchpad.net/ubuntu-push/bus/testing" |
991 | + "launchpad.net/ubuntu-push/click" |
992 | "launchpad.net/ubuntu-push/logger" |
993 | "launchpad.net/ubuntu-push/nih" |
994 | helpers "launchpad.net/ubuntu-push/testing" |
995 | @@ -162,7 +163,7 @@ |
996 | {nil, ErrBadArgCount}, |
997 | {[]interface{}{}, ErrBadArgCount}, |
998 | {[]interface{}{1}, ErrBadArgType}, |
999 | - {[]interface{}{"foo"}, ErrBadAppId}, |
1000 | + {[]interface{}{"foo"}, click.ErrInvalidAppId}, |
1001 | {[]interface{}{"foo", "bar"}, ErrBadArgCount}, |
1002 | } { |
1003 | reg, err := new(PushService).register("/bar", s.args, nil) |
1004 | |
1005 | === modified file 'debian/changelog' |
1006 | --- debian/changelog 2014-08-04 15:38:34 +0000 |
1007 | +++ debian/changelog 2014-08-12 02:32:01 +0000 |
1008 | @@ -1,3 +1,25 @@ |
1009 | +ubuntu-push (0.61) UNRELEASED; urgency=medium |
1010 | + |
1011 | + [ Guillermo Gonzalez ] |
1012 | + * Update autopilot tests to work with 0.50, fix setup.sh issues and add new tests for the broadcast notification changes. |
1013 | + * Replace whoopsie with /var/lib/dbus/machine-id to get the device ID. |
1014 | + |
1015 | + [ John R. Lenton] |
1016 | + * Support simpler sounds API. |
1017 | + * Support simpler vibrations API. |
1018 | + * Remove Vibration's confusing and redundant Duration attribute. |
1019 | + * Change PostalService's New() to take a setup object. |
1020 | + * goctest. |
1021 | + * Make messaging menu entries show current time instead of epoch for timestamp of 0. |
1022 | + * Tweak the upstart script, start after unity. |
1023 | + * Correctly report invalid app ids, missing apps, and package/app id mismatches as separate errors over dbus. |
1024 | + |
1025 | + [Roberto Alsina] |
1026 | + * Check that sound paths don't go up into the tree. |
1027 | + * Initial draft of QML-based doc |
1028 | + |
1029 | + -- Roberto Alsina <ralsina@yoga> Mon, 11 Aug 2014 18:23:32 -0300 |
1030 | + |
1031 | ubuntu-push (0.60+14.10.20140804-0ubuntu1) utopic; urgency=medium |
1032 | |
1033 | [ Guillermo Gonzalez ] |
1034 | |
1035 | === modified file 'debian/config.json' |
1036 | --- debian/config.json 2014-07-03 13:59:15 +0000 |
1037 | +++ debian/config.json 2014-08-12 02:32:01 +0000 |
1038 | @@ -12,5 +12,7 @@ |
1039 | "recheck_timeout": "10m", |
1040 | "connectivity_check_url": "http://start.ubuntu.com/connectivity-check.html", |
1041 | "connectivity_check_md5": "4589f42e1546aa47ca181e5d949d310b", |
1042 | - "log_level": "debug" |
1043 | + "log_level": "debug", |
1044 | + "fallback_vibration": {"pattern": [100, 100], "repeat": 2}, |
1045 | + "fallback_sound": "sounds/ubuntu/notifications/Slick.ogg" |
1046 | } |
1047 | |
1048 | === modified file 'debian/control' |
1049 | --- debian/control 2014-07-17 23:01:19 +0000 |
1050 | +++ debian/control 2014-08-12 02:32:01 +0000 |
1051 | @@ -22,7 +22,7 @@ |
1052 | libnih-dbus-dev, |
1053 | libclick-0.4-dev, |
1054 | cmake, |
1055 | - python3.4, |
1056 | + python3, |
1057 | Standards-Version: 3.9.5 |
1058 | Homepage: http://launchpad.net/ubuntu-push |
1059 | Vcs-Bzr: lp:ubuntu-push |
1060 | |
1061 | === modified file 'debian/ubuntu-push-client.conf' |
1062 | --- debian/ubuntu-push-client.conf 2014-06-02 10:01:13 +0000 |
1063 | +++ debian/ubuntu-push-client.conf 2014-08-12 02:32:01 +0000 |
1064 | @@ -1,7 +1,9 @@ |
1065 | description "ubuntu push notification client-side daemon" |
1066 | |
1067 | -start on started dbus |
1068 | -stop on stopped dbus |
1069 | +start on started unity8 |
1070 | +stop on stopping unity8 |
1071 | |
1072 | exec /usr/lib/ubuntu-push-client/ubuntu-push-client |
1073 | respawn |
1074 | + |
1075 | +post-stop exec initctl emit untrusted-helper-type-end HELPER_TYPE=push-helper |
1076 | |
1077 | === modified file 'debian/ubuntu-push-client.install' |
1078 | --- debian/ubuntu-push-client.install 2014-08-02 23:20:41 +0000 |
1079 | +++ debian/ubuntu-push-client.install 2014-08-12 02:32:01 +0000 |
1080 | @@ -6,4 +6,4 @@ |
1081 | signing-helper/signing-helper /usr/lib/ubuntu-push-client |
1082 | scripts/click-hook /usr/lib/ubuntu-push-client |
1083 | usr/bin/ubuntu-push => /usr/lib/ubuntu-push-client/ubuntu-push-client |
1084 | -scripts/software-updates-helper.py => /usr/lib/ubuntu-push-client/legacy-helpers/ubuntu-system-settings |
1085 | + |
1086 | |
1087 | === added file 'docs/highlevel.txt' |
1088 | --- docs/highlevel.txt 1970-01-01 00:00:00 +0000 |
1089 | +++ docs/highlevel.txt 2014-08-12 02:32:01 +0000 |
1090 | @@ -0,0 +1,325 @@ |
1091 | +Ubuntu Push Client Developer Guide |
1092 | +================================== |
1093 | + |
1094 | +:Version: 0.50+ |
1095 | + |
1096 | +Introduction |
1097 | +------------ |
1098 | + |
1099 | +This document describes how to use the Ubuntu Push Client service from the point of view of a developer writing |
1100 | +a QML-based application. |
1101 | + |
1102 | +--------- |
1103 | + |
1104 | +Let's describe the push system by way of an example. |
1105 | + |
1106 | +Alice has written a chat application called Chatter. Using it, Bob can send messages to Carol and viceversa. Alice has a |
1107 | +web application for it, so the way it works now is that Bob connects to the service, posts a message, and when Carol |
1108 | +connects, she gets it. If Carol leaves the browser window open, it beeps when messages arrive. |
1109 | + |
1110 | +Now Alice wants to create an Ubuntu Touch app for Chatter, so she implements the same architecture using a client that |
1111 | +does the same thing as the web browser. Sadly, since applications on Ubuntu Touch don't run continuously, messages are |
1112 | +only delivered when Carol opens the app, and the user experience suffers. |
1113 | + |
1114 | +Using the Ubuntu Push Server, this problem is alleviated: the Chatter server will deliver the messages to the Ubuntu |
1115 | +Push Server, which in turn will send it in an efficient manner to the Ubuntu Push Client running in Bob and Carol's |
1116 | +devices. The user sees a notification (all without starting the app) and then can launch it if he's interested in |
1117 | +reading messages at that point. |
1118 | + |
1119 | +Since the app is not started and messages are delivered oportunistically, this is both battery and bandwidth-efficient. |
1120 | + |
1121 | +.. figure:: push.svg |
1122 | + |
1123 | +The Ubuntu Push system provides: |
1124 | + |
1125 | +* A push server which receives **push messages** from the app servers, queues them and delivers them efficiently |
1126 | + to the devices. |
1127 | +* A push client which receives those messages, queues messages to the app and displays notifications to the user |
1128 | + |
1129 | +The full lifecycle of a push message is: |
1130 | + |
1131 | +* Created in a application-specific server |
1132 | +* Sent to the Ubuntu Push server, targeted at a user or user+device pair |
1133 | +* Delivered to one or more Ubuntu devices |
1134 | +* Passed through the application helper for processing |
1135 | +* Notification displayed to the user (via different mechanisms) |
1136 | +* Application Message queued for the app's use |
1137 | + |
1138 | +If the user interacts with the notification, the application is launched and should check its queue for messages |
1139 | +it has to process. |
1140 | + |
1141 | +For the app developer, there are several components needed: |
1142 | + |
1143 | +* A server that sends the **push messages** to the Ubuntu Push server |
1144 | +* Support in the client app for registering with the Ubuntu Push client |
1145 | +* Support in the client app to react to **notifications** displayed to the user and process **application messages** |
1146 | +* A helper program with application-specific knowledge that transforms **push messages** as needed. |
1147 | + |
1148 | +In the following sections, we'll see how to implement all the client side parts. For the application server, see the |
1149 | +`Ubuntu Push Server API section <#ubuntu-push-server-api>`__ |
1150 | + |
1151 | +The PushClient Component |
1152 | +------------------------ |
1153 | + |
1154 | +Example:: |
1155 | + |
1156 | + import Ubuntu.PushNotifications 0.1 |
1157 | + |
1158 | + PushClient { |
1159 | + id: pushClient |
1160 | + Component.onCompleted: { |
1161 | + newNotifications.connect(messageList.handle_notifications) |
1162 | + error.connect(messageList.handle_error) |
1163 | + } |
1164 | + appId: "com.ubuntu.developer.push.hello_hello" |
1165 | + } |
1166 | + |
1167 | +Registration: the appId and token properties |
1168 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1169 | + |
1170 | +To register with the push system and start receiving notifications, set the ``appId`` property to your application's APP_ID, |
1171 | +with or without version number. For this to succeed the user **must** have an Ubuntu One account configured in the device. |
1172 | + |
1173 | +The APP_ID is as described in the `ApplicationId documentation <https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId>`__ |
1174 | +except that the version is treated as optional. Therefore both ``com.ubuntu.music_music`` and ``com.ubuntu.music_music_1.3.496`` |
1175 | +are valid. Keep in mind that while both versioned and unversioned APP_IDs are valid, they are still different and will affect |
1176 | +which notifications are delivered to the application. Unversioned IDs mean the token will be the same after updates and the application |
1177 | +will receive old notifications, while versioned IDs mean the app needs to explicitly ask to get older messages delivered. |
1178 | + |
1179 | +Setting the same appId more than once has no effect. |
1180 | + |
1181 | +After you are registered, if no error occurs, the PushClient will have a value set in its ``token`` property |
1182 | +which uniquely identifies the user+device combination. |
1183 | + |
1184 | +Receiving Notifications |
1185 | +~~~~~~~~~~~~~~~~~~~~~~~ |
1186 | + |
1187 | +When a notification is received by the Push Client, it will be delivered to your application's push helper, and then |
1188 | +placed in your application's mailbox. At that point, the PushClient will emit the ``newNotifications(QStringList)`` signal |
1189 | +containing your messages. You should probably connect to that signal and handle those messages. |
1190 | + |
1191 | +Because of the application's lifecycle, there is no guarantee that it will be running when the signal is emitted. For that |
1192 | +reason, apps should check for pending notifications whenever they are activated or started. To do that, use the |
1193 | +``getNotifications()`` slot. Triggering that slot will fetch notifications and trigger the |
1194 | +``newNotifications(QStringList)`` signal. |
1195 | + |
1196 | +Error Handling |
1197 | +~~~~~~~~~~~~~~ |
1198 | + |
1199 | +Whenever PushClient suffers an error, it will emit the ``error(QString)`` signal with the error message. |
1200 | + |
1201 | +Persistent Notification Management |
1202 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1203 | + |
1204 | +Some notifications are persistent, meaning they don't disappear automatically. For those notifications, there is an API that |
1205 | +allows the app to manage them without having to know the underlying details of the platform. |
1206 | + |
1207 | +On each notification there's an optional ``tag`` field, used for this purpose. |
1208 | + |
1209 | +The ``persistent`` property of PushClient contains the list of the tags of notifications with the "persist" element set |
1210 | +to true that are visible to the user right now. |
1211 | + |
1212 | +The ``void clearPersistent(QStringList tags)`` method clears persistent notifications for that app marked by ``tags``. |
1213 | +If no tag is given, match all. |
1214 | + |
1215 | + |
1216 | +The ``count`` property sets the counter in the application's icon to the given value. |
1217 | + |
1218 | + |
1219 | +Application Helpers |
1220 | +------------------- |
1221 | + |
1222 | +The payload delivered to push-client will be passed onto a helper program that can modify it as needed before passing it onto |
1223 | +the postal service (see `Helper Output Format <#helper-output-format>`__). |
1224 | + |
1225 | +The helper receives two arguments ``infile`` and ``outfile``. The message is delivered via ``infile`` and the transformed |
1226 | +version is placed in ``outfile``. |
1227 | + |
1228 | +This is the simplest possible useful helper, which simply passes the message through unchanged:: |
1229 | + |
1230 | + #!/usr/bin/python3 |
1231 | + |
1232 | + import sys |
1233 | + f1, f2 = sys.argv[1:3] |
1234 | + open(f2, "w").write(open(f1).read()) |
1235 | + |
1236 | +Helpers need to be added to the click package manifest:: |
1237 | + |
1238 | + { |
1239 | + "name": "com.ubuntu.developer.ralsina.hello", |
1240 | + "description": "description of hello", |
1241 | + "framework": "ubuntu-sdk-14.10-qml-dev2", |
1242 | + "architecture": "all", |
1243 | + "title": "hello", |
1244 | + "hooks": { |
1245 | + "hello": { |
1246 | + "apparmor": "hello.json", |
1247 | + "desktop": "hello.desktop" |
1248 | + }, |
1249 | + "helloHelper": { |
1250 | + "apparmor": "helloHelper-apparmor.json", |
1251 | + "push-helper": "helloHelper.json" |
1252 | + } |
1253 | + }, |
1254 | + "version": "0.2", |
1255 | + "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>" |
1256 | + } |
1257 | + |
1258 | +Here, we created a helloHelper entry in hooks that has an apparmor profile and an additional JSON file for the push-helper hook. |
1259 | + |
1260 | +helloHelper-apparmor.json must contain **only** the push-notification-client policy group:: |
1261 | + |
1262 | + { |
1263 | + "policy_groups": [ |
1264 | + "push-notification-client" |
1265 | + ], |
1266 | + "policy_version": 1.2 |
1267 | + } |
1268 | + |
1269 | +And helloHelper.json must have at least a exec key with the path to the helper executable relative to the json, and optionally |
1270 | +an app_id key containing the short id of one of the apps in the package (in the format packagename_appname without a version). |
1271 | +If the app_id is not specified, the helper will be used for all apps in the package:: |
1272 | + |
1273 | + { |
1274 | + "exec": "helloHelper", |
1275 | + "app_id": "com.ubuntu.developer.ralsina.hello_hello" |
1276 | + } |
1277 | + |
1278 | +.. note:: For deb packages, helpers should be installed into /usr/lib/ubuntu-push-client/legacy-helpers/ as part of the package. |
1279 | + |
1280 | +Helper Output Format |
1281 | +-------------------- |
1282 | + |
1283 | +Helpers output has two parts, the postal message (in the "message" key) and a notification to be presented to the user (in the "notification" key). |
1284 | + |
1285 | +Here's a simple example:: |
1286 | + |
1287 | + { |
1288 | + "message": "foobar", |
1289 | + "notification": { |
1290 | + "tag": "foo", |
1291 | + "card": { |
1292 | + "summary": "yes", |
1293 | + "body": "hello", |
1294 | + "popup": true, |
1295 | + "persist": true, |
1296 | + "timestamp": 1407160197 |
1297 | + } |
1298 | + "sound": "buzz.mp3", |
1299 | + "vibrate": { |
1300 | + "pattern": [200, 100], |
1301 | + "repeat": 2 |
1302 | + } |
1303 | + "emblem-counter": { |
1304 | + "count": 12, |
1305 | + "visible": true |
1306 | + } |
1307 | + } |
1308 | + } |
1309 | + |
1310 | +The notification can contain a **tag** field, which can later be used by the `persistent notification management API. <#persistent-notification-management>`__ |
1311 | + |
1312 | +:message: (optional) A JSON object that is passed as-is to the application via PopAll. |
1313 | +:notification: (optional) Describes the user-facing notifications triggered by this push message. |
1314 | + |
1315 | +The notification can contain a **card**. A card describes a specific notification to be given to the user, |
1316 | +and has the following fields: |
1317 | + |
1318 | +:summary: (required) a title. The card will not be presented if this is missing. |
1319 | +:body: longer text, defaults to empty. |
1320 | +:actions: If empty (the default), a bubble notification is non-clickable. |
1321 | + If you add a URL, then bubble notifications are clickable and launch that URL. One use for this is using a URL like |
1322 | + ``appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version`` which will switch to the app or launch |
1323 | + it if it's not running. See `URLDispatcher <https://wiki.ubuntu.com/URLDispatcher>`__ for more information. |
1324 | + |
1325 | +:icon: An icon relating to the event being notified. Defaults to empty (no icon); |
1326 | + a secondary icon relating to the application will be shown as well, regardless of this field. |
1327 | +:timestamp: Seconds since the unix epoch, only used for persist (for now). If zero or unset, defaults to current timestamp. |
1328 | +:persist: Whether to show in notification centre; defaults to false |
1329 | +:popup: Whether to show in a bubble. Users can disable this, and can easily miss them, so don't rely on it exclusively. Defaults to false. |
1330 | + |
1331 | +.. note:: Keep in mind that the precise way in which each field is presented to the user depends on factors such as |
1332 | + whether it's shown as a bubble or in the notification centre, or even the version of Ubuntu Touch the user |
1333 | + has on their device. |
1334 | + |
1335 | +The notification can contain a **sound** field. This is either a boolean (play a predetermined sound) or the path to a sound file. The user can disable it, so don't rely on it exclusively. |
1336 | +Defaults to empty (no sound). The path is relative, and will be looked up in (a) the application's .local/share/<pkgname>, and (b) |
1337 | +standard xdg dirs. |
1338 | + |
1339 | +The notification can contain a **vibrate** field, causing haptic feedback, which can be either a boolean (if true, vibrate a predetermined way) or an object that has the following content: |
1340 | + |
1341 | +:pattern: a list of integers describing a vibration pattern (duration of alternating vibration/no vibration times, in milliseconds). |
1342 | +:repeat: number of times the pattern has to be repeated (defaults to 1, 0 is the same as 1). |
1343 | + |
1344 | +The notification can contain a **emblem-counter** field, with the following content: |
1345 | + |
1346 | +:count: a number to be displayed over the application's icon in the launcher. |
1347 | +:visible: set to true to show the counter, or false to hide it. |
1348 | + |
1349 | +.. note:: Unlike other notifications, emblem-counter needs to be cleaned by the app itself. |
1350 | + Please see `the persistent notification management section. <#persistent-notification-management>`__ |
1351 | + |
1352 | +.. FIXME crosslink to hello example app on each method |
1353 | + |
1354 | +Security |
1355 | +~~~~~~~~ |
1356 | + |
1357 | +To use the push API, applications need to request permission in their security profile, using something like this:: |
1358 | + |
1359 | + { |
1360 | + "policy_groups": [ |
1361 | + "networking", |
1362 | + "push-notification-client" |
1363 | + ], |
1364 | + "policy_version": 1.2 |
1365 | + } |
1366 | + |
1367 | + |
1368 | +Ubuntu Push Server API |
1369 | +---------------------- |
1370 | + |
1371 | +The Ubuntu Push server is located at https://push.ubuntu.com and has a single endpoint: ``/notify``. |
1372 | +To notify a user, your application has to do a POST with ``Content-type: application/json``. |
1373 | + |
1374 | +Here is an example of the POST body using all the fields:: |
1375 | + |
1376 | + { |
1377 | + "appid": "com.ubuntu.music_music", |
1378 | + "expire_on": "2014-10-08T14:48:00.000Z", |
1379 | + "token": "LeA4tRQG9hhEkuhngdouoA==", |
1380 | + "clear_pending": true, |
1381 | + "replace_tag": "tagname", |
1382 | + "data": { |
1383 | + "message": "foobar", |
1384 | + "notification": { |
1385 | + "card": { |
1386 | + "summary": "yes", |
1387 | + "body": "hello", |
1388 | + "popup": true, |
1389 | + "persist": true, |
1390 | + "timestamp": 1407160197 |
1391 | + } |
1392 | + "sound": "buzz.mp3", |
1393 | + "tag": "foo", |
1394 | + "vibrate": { |
1395 | + "pattern": [200, 100], |
1396 | + "repeat": 2 |
1397 | + } |
1398 | + "emblem-counter": { |
1399 | + "count": 12, |
1400 | + "visible": true |
1401 | + } |
1402 | + } |
1403 | + } |
1404 | + } |
1405 | + |
1406 | + |
1407 | +:appid: ID of the application that will receive the notification, as described in the client side documentation. |
1408 | +:expire_on: Expiration date/time for this message, in `ISO8601 Extendend format <http://en.wikipedia.org/wiki/ISO_8601>`__ |
1409 | +:token: The token identifying the user+device to which the message is directed, as described in the client side documentation. |
1410 | +:clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error. |
1411 | +:replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one. |
1412 | +:data: A JSON object. |
1413 | + |
1414 | +In this example, data is `what a helper would output <#helper-output-format>`__ but that's not necessarily the case. |
1415 | +The content of the data field will be passed to the helper application which **has** to produce output in that format. |
1416 | |
1417 | === modified file 'docs/lowlevel.txt' |
1418 | --- docs/lowlevel.txt 2014-07-29 17:14:22 +0000 |
1419 | +++ docs/lowlevel.txt 2014-08-12 02:32:01 +0000 |
1420 | @@ -73,7 +73,7 @@ |
1421 | Each Ubuntu Touch package has to use a separate object path for security reasons, that's why the object path includes QUOTED_PKGNAME. |
1422 | For example, in the case of the music application, the package name is ``com.ubuntu.music`` and QUOTED_PKGNAME is com_2eubuntu_2emusic. |
1423 | Everything that is not a letter or digit has to be quoted as _XX where XX are the hex digits of the character. In practice, |
1424 | -this means replacing "." with "_2e" and "-" with "_2f" |
1425 | +this means replacing "." with "_2e" and "-" with "_2d" |
1426 | |
1427 | .. note:: For applications that are not installed as part of click packages, the QUOTED_PKGNAME is "_" and the APP_ID when required is |
1428 | _PACKAGENAME. |
1429 | @@ -296,12 +296,12 @@ |
1430 | "summary": "yes", |
1431 | "body": "hello", |
1432 | "popup": true, |
1433 | - "persist": true |
1434 | + "persist": true, |
1435 | + "timestamp": 1407160197 |
1436 | } |
1437 | "sound": "buzz.mp3", |
1438 | "vibrate": { |
1439 | "pattern": [200, 100], |
1440 | - "duration": 200, |
1441 | "repeat": 2 |
1442 | } |
1443 | "emblem-counter": { |
1444 | @@ -328,7 +328,7 @@ |
1445 | |
1446 | :icon: An icon relating to the event being notified. Defaults to empty (no icon); |
1447 | a secondary icon relating to the application will be shown as well, regardless of this field. |
1448 | -:timestamp: Seconds since the unix epoch, only used for persist (for now) |
1449 | +:timestamp: Seconds since the unix epoch, only used for persist (for now). If zero or unset, defaults to current timestamp. |
1450 | :persist: Whether to show in notification centre; defaults to false |
1451 | :popup: Whether to show in a bubble. Users can disable this, and can easily miss them, so don't rely on it exclusively. Defaults to false. |
1452 | |
1453 | @@ -336,14 +336,13 @@ |
1454 | whether it's shown as a bubble or in the notification centre, or even the version of Ubuntu Touch the user |
1455 | has on their device. |
1456 | |
1457 | -The notification can contain a **sound** field. This is the path to a sound file. The user can disable it, so don't rely on it exclusively. |
1458 | -Defaults to empty (no sound). This is a relative path, and will be looked up in (a) the application's .local/share/<pkgname>, and (b) |
1459 | +The notification can contain a **sound** field. This is either a boolean (play a predetermined sound) or the path to a sound file. The user can disable it, so don't rely on it exclusively. |
1460 | +Defaults to empty (no sound). The path is relative, and will be looked up in (a) the application's .local/share/<pkgname>, and (b) |
1461 | standard xdg dirs. |
1462 | |
1463 | -The notification can contain a **vibrate** field, causing haptic feedback, that has the following content: |
1464 | +The notification can contain a **vibrate** field, causing haptic feedback, which can be either a boolean (if true, vibrate a predetermined way) or an object that has the following content: |
1465 | |
1466 | -:pattern: a list of integers describing a vibration pattern. |
1467 | -:duration: duration in milliseconds. Is equivalent to setting pattern to [duration], and overrides pattern. |
1468 | +:pattern: a list of integers describing a vibration pattern (duration of alternating vibration/no vibration times, in milliseconds). |
1469 | :repeat: number of times the pattern has to be repeated (defaults to 1, 0 is the same as 1). |
1470 | |
1471 | The notification can contain a **emblem-counter** field, with the following content: |
1472 | @@ -391,13 +390,13 @@ |
1473 | "summary": "yes", |
1474 | "body": "hello", |
1475 | "popup": true, |
1476 | - "persist": true |
1477 | + "persist": true, |
1478 | + "timestamp": 1407160197 |
1479 | } |
1480 | "sound": "buzz.mp3", |
1481 | "tag": "foo", |
1482 | "vibrate": { |
1483 | - "duration": 200, |
1484 | - "pattern": (200, 100), |
1485 | + "pattern": [200, 100], |
1486 | "repeat": 2 |
1487 | } |
1488 | "emblem-counter": { |
1489 | |
1490 | === added directory 'identifier' |
1491 | === added file 'identifier/identifier.go' |
1492 | --- identifier/identifier.go 1970-01-01 00:00:00 +0000 |
1493 | +++ identifier/identifier.go 2014-08-12 02:32:01 +0000 |
1494 | @@ -0,0 +1,59 @@ |
1495 | +/* |
1496 | + Copyright 2013-2014 Canonical Ltd. |
1497 | + |
1498 | + This program is free software: you can redistribute it and/or modify it |
1499 | + under the terms of the GNU General Public License version 3, as published |
1500 | + by the Free Software Foundation. |
1501 | + |
1502 | + This program is distributed in the hope that it will be useful, but |
1503 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
1504 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1505 | + PURPOSE. See the GNU General Public License for more details. |
1506 | + |
1507 | + You should have received a copy of the GNU General Public License along |
1508 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
1509 | +*/ |
1510 | + |
1511 | +// Package identifier is the source of an anonymous and stable |
1512 | +// system id (from /var/lib/dbus/machine-id) used by the Ubuntu |
1513 | +// push notifications service. |
1514 | +package identifier |
1515 | + |
1516 | +import ( |
1517 | + "fmt" |
1518 | + "io/ioutil" |
1519 | +) |
1520 | + |
1521 | +var machineIdPath = "/var/lib/dbus/machine-id" |
1522 | + |
1523 | +// an Id knows how to generate itself, and how to stringify itself. |
1524 | +type Id interface { |
1525 | + String() string |
1526 | +} |
1527 | + |
1528 | +// Identifier is the default Id implementation. |
1529 | +type Identifier struct { |
1530 | + value string |
1531 | +} |
1532 | + |
1533 | +func readMachineId() (string, error) { |
1534 | + value, err := ioutil.ReadFile(machineIdPath) |
1535 | + if err != nil { |
1536 | + return "", err |
1537 | + } |
1538 | + return string(value)[:len(value)-1], nil |
1539 | +} |
1540 | + |
1541 | +// New creates an Identifier |
1542 | +func New() (Id, error) { |
1543 | + value, err := readMachineId() |
1544 | + if err != nil { |
1545 | + return &Identifier{value: ""}, fmt.Errorf("Failed to read the machine id: %s", err) |
1546 | + } |
1547 | + return &Identifier{value: value}, nil |
1548 | +} |
1549 | + |
1550 | +// String returns the system identifier as a string. |
1551 | +func (id *Identifier) String() string { |
1552 | + return id.value |
1553 | +} |
1554 | |
1555 | === added file 'identifier/identifier_test.go' |
1556 | --- identifier/identifier_test.go 1970-01-01 00:00:00 +0000 |
1557 | +++ identifier/identifier_test.go 2014-08-12 02:32:01 +0000 |
1558 | @@ -0,0 +1,59 @@ |
1559 | +/* |
1560 | + Copyright 2013-2014 Canonical Ltd. |
1561 | + |
1562 | + This program is free software: you can redistribute it and/or modify it |
1563 | + under the terms of the GNU General Public License version 3, as published |
1564 | + by the Free Software Foundation. |
1565 | + |
1566 | + This program is distributed in the hope that it will be useful, but |
1567 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
1568 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1569 | + PURPOSE. See the GNU General Public License for more details. |
1570 | + |
1571 | + You should have received a copy of the GNU General Public License along |
1572 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
1573 | +*/ |
1574 | + |
1575 | +package identifier |
1576 | + |
1577 | +import ( |
1578 | + . "launchpad.net/gocheck" |
1579 | + "os" |
1580 | + "testing" |
1581 | +) |
1582 | + |
1583 | +// hook up gocheck |
1584 | +func Test(t *testing.T) { TestingT(t) } |
1585 | + |
1586 | +type IdentifierSuite struct{} |
1587 | + |
1588 | +var _ = Suite(&IdentifierSuite{}) |
1589 | + |
1590 | +// TestNew checks that New does not fail, and returns a |
1591 | +// 32-byte string. |
1592 | +func (s *IdentifierSuite) TestNew(c *C) { |
1593 | + _, err := os.Stat(machineIdPath) |
1594 | + if os.IsNotExist(err) { |
1595 | + c.Skip("no dbus machine id") |
1596 | + } |
1597 | + id, err := New() |
1598 | + c.Check(err, IsNil) |
1599 | + c.Check(id.String(), HasLen, 32) |
1600 | +} |
1601 | + |
1602 | +// TestNewFail checks that when we can't read the machine-id |
1603 | +// file the error is propagated |
1604 | +func (s *IdentifierSuite) TestNewFail(c *C) { |
1605 | + // replace the machine-id file path |
1606 | + machineIdPath = "/var/lib/dbus/no-such-file" |
1607 | + id, err := New() |
1608 | + c.Check(err, NotNil) |
1609 | + c.Check(err.Error(), Equals, "Failed to read the machine id: open /var/lib/dbus/no-such-file: no such file or directory") |
1610 | + c.Check(id.String(), HasLen, 0) |
1611 | +} |
1612 | + |
1613 | +// TestIdentifierInterface checks that Identifier implements Id. |
1614 | +func (s *IdentifierSuite) TestIdentifierInterface(c *C) { |
1615 | + id, _ := New() |
1616 | + _ = []Id{id} |
1617 | +} |
1618 | |
1619 | === added directory 'identifier/testing' |
1620 | === added file 'identifier/testing/testing.go' |
1621 | --- identifier/testing/testing.go 1970-01-01 00:00:00 +0000 |
1622 | +++ identifier/testing/testing.go 2014-08-12 02:32:01 +0000 |
1623 | @@ -0,0 +1,58 @@ |
1624 | +/* |
1625 | + Copyright 2013-2014 Canonical Ltd. |
1626 | + |
1627 | + This program is free software: you can redistribute it and/or modify it |
1628 | + under the terms of the GNU General Public License version 3, as published |
1629 | + by the Free Software Foundation. |
1630 | + |
1631 | + This program is distributed in the hope that it will be useful, but |
1632 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
1633 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1634 | + PURPOSE. See the GNU General Public License for more details. |
1635 | + |
1636 | + You should have received a copy of the GNU General Public License along |
1637 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
1638 | +*/ |
1639 | + |
1640 | +// Package testing implements a couple of Ids that are useful |
1641 | +// for testing things that use identifier. |
1642 | +package testing |
1643 | + |
1644 | +// SettableIdentifier is an Id that lets you set the value of the identifier. |
1645 | +// |
1646 | +// By default the identifier's value is "<Settable>", so it's visible |
1647 | +// if you're misusing it. |
1648 | +type SettableIdentifier struct { |
1649 | + value string |
1650 | +} |
1651 | + |
1652 | +// Settable is the constructor for SettableIdentifier. |
1653 | +func Settable() *SettableIdentifier { |
1654 | + return &SettableIdentifier{"<Settable>"} |
1655 | +} |
1656 | + |
1657 | +// Set is the method you use to set the identifier. |
1658 | +func (sid *SettableIdentifier) Set(value string) { |
1659 | + sid.value = value |
1660 | +} |
1661 | + |
1662 | +// String returns the string you set. |
1663 | +func (sid *SettableIdentifier) String() string { |
1664 | + return sid.value |
1665 | +} |
1666 | + |
1667 | +// FailingIdentifier is an Id that always fails to generate. |
1668 | +type FailingIdentifier struct{} |
1669 | + |
1670 | +// Failing is the constructor for FailingIdentifier. |
1671 | +func Failing() *FailingIdentifier { |
1672 | + return &FailingIdentifier{} |
1673 | +} |
1674 | + |
1675 | +// String returns "<Failing>". |
1676 | +// |
1677 | +// The purpose of this is to make it easy to spot if you're using it |
1678 | +// by accident. |
1679 | +func (*FailingIdentifier) String() string { |
1680 | + return "<Failing>" |
1681 | +} |
1682 | |
1683 | === added file 'identifier/testing/testing_test.go' |
1684 | --- identifier/testing/testing_test.go 1970-01-01 00:00:00 +0000 |
1685 | +++ identifier/testing/testing_test.go 2014-08-12 02:32:01 +0000 |
1686 | @@ -0,0 +1,57 @@ |
1687 | +/* |
1688 | + Copyright 2013-2014 Canonical Ltd. |
1689 | + |
1690 | + This program is free software: you can redistribute it and/or modify it |
1691 | + under the terms of the GNU General Public License version 3, as published |
1692 | + by the Free Software Foundation. |
1693 | + |
1694 | + This program is distributed in the hope that it will be useful, but |
1695 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
1696 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1697 | + PURPOSE. See the GNU General Public License for more details. |
1698 | + |
1699 | + You should have received a copy of the GNU General Public License along |
1700 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
1701 | +*/ |
1702 | + |
1703 | +package testing |
1704 | + |
1705 | +import ( |
1706 | + identifier ".." |
1707 | + . "launchpad.net/gocheck" |
1708 | + "testing" |
1709 | +) |
1710 | + |
1711 | +// hook up gocheck |
1712 | +func Test(t *testing.T) { TestingT(t) } |
1713 | + |
1714 | +type IdentifierSuite struct{} |
1715 | + |
1716 | +var _ = Suite(&IdentifierSuite{}) |
1717 | + |
1718 | +// TestSettableDefaultValueVisible tests that SettableIdentifier's default |
1719 | +// value is notable. |
1720 | +func (s *IdentifierSuite) TestSettableDefaultValueVisible(c *C) { |
1721 | + id := Settable() |
1722 | + c.Check(id.String(), Equals, "<Settable>") |
1723 | +} |
1724 | + |
1725 | +// TestSettableSets tests that SettableIdentifier is settable. |
1726 | +func (s *IdentifierSuite) TestSettableSets(c *C) { |
1727 | + id := Settable() |
1728 | + id.Set("hello") |
1729 | + c.Check(id.String(), Equals, "hello") |
1730 | +} |
1731 | + |
1732 | +// TestFailingStringNotEmpty tests that FailingIdentifier still has a |
1733 | +// non-empty string. |
1734 | +func (s *IdentifierSuite) TestFailingStringNotEmpty(c *C) { |
1735 | + id := Failing() |
1736 | + c.Check(id.String(), Equals, "<Failing>") |
1737 | +} |
1738 | + |
1739 | +// TestIdentifierInterface tests that FailingIdentifier and |
1740 | +// SettableIdentifier implement Id. |
1741 | +func (s *IdentifierSuite) TestIdentifierInterface(c *C) { |
1742 | + _ = []identifier.Id{Failing(), Settable()} |
1743 | +} |
1744 | |
1745 | === modified file 'launch_helper/helper_output.go' |
1746 | --- launch_helper/helper_output.go 2014-07-22 14:08:25 +0000 |
1747 | +++ launch_helper/helper_output.go 2014-08-12 02:32:01 +0000 |
1748 | @@ -18,6 +18,7 @@ |
1749 | |
1750 | import ( |
1751 | "encoding/json" |
1752 | + "time" |
1753 | |
1754 | "launchpad.net/ubuntu-push/click" |
1755 | ) |
1756 | @@ -25,13 +26,13 @@ |
1757 | // a Card is the usual “visual” presentation of a notification, used |
1758 | // for bubbles and the notification centre (neé messaging menu) |
1759 | type Card struct { |
1760 | - Summary string `json:"summary"` // required for the card to be presented |
1761 | - Body string `json:"body"` // defaults to empty |
1762 | - Actions []string `json:"actions"` // if empty (default), bubble is non-clickable. More entries change it to be clickable and (for bubbles) snap-decisions. |
1763 | - Icon string `json:"icon"` // an icon relating to the event being notified. Defaults to empty (no icon); a secondary icon relating to the application will be shown as well, irrespectively. |
1764 | - Timestamp int `json:"timestamp"` // seconds since epoch, only used for persist (for now) |
1765 | - Persist bool `json:"persist"` // whether to show in notification centre; defaults to false |
1766 | - Popup bool `json:"popup"` // whether to show in a bubble. Users can disable this, and can easily miss them, so don't rely on it exclusively. Defaults to false. |
1767 | + Summary string `json:"summary"` // required for the card to be presented |
1768 | + Body string `json:"body"` // defaults to empty |
1769 | + Actions []string `json:"actions"` // if empty (default), bubble is non-clickable. More entries change it to be clickable and (for bubbles) snap-decisions. |
1770 | + Icon string `json:"icon"` // an icon relating to the event being notified. Defaults to empty (no icon); a secondary icon relating to the application will be shown as well, irrespectively. |
1771 | + RawTimestamp int `json:"timestamp"` // seconds since epoch, only used for persist (for now). Timestamp() returns this if non-zero, current timestamp otherwise. |
1772 | + Persist bool `json:"persist"` // whether to show in notification centre; defaults to false |
1773 | + Popup bool `json:"popup"` // whether to show in a bubble. Users can disable this, and can easily miss them, so don't rely on it exclusively. Defaults to false. |
1774 | } |
1775 | |
1776 | // an EmblemCounter puts a number on an emblem on an app's icon in the launcher |
1777 | @@ -43,18 +44,17 @@ |
1778 | // a Vibration generates a vibration in the form of a Pattern set in |
1779 | // duration a pattern of on off states, repeated a number of times |
1780 | type Vibration struct { |
1781 | - Duration uint32 `json:"duration"` // if Duration is present and not 0, it's like a Pattern of [Duration]; otherwise, Pattern is used. |
1782 | - Pattern []uint32 `json:"pattern"` |
1783 | - Repeat uint32 `json:"repeat"` // defaults to 1. A value of zero is ignored (so it's like 1). |
1784 | + Pattern []uint32 `json:"pattern"` |
1785 | + Repeat uint32 `json:"repeat"` // defaults to 1. A value of zero is ignored (so it's like 1). |
1786 | } |
1787 | |
1788 | // a Notification can be any of the above |
1789 | type Notification struct { |
1790 | - Card *Card `json:"card"` // defaults to nil (no card) |
1791 | - Sound string `json:"sound"` // a sound file. Users can disable this, so don't rely on it exclusively. Defaults to empty (no sound). |
1792 | - Vibrate *Vibration `json:"vibrate"` // users can disable this, blah blah. Defaults to null (no vibration) |
1793 | - EmblemCounter *EmblemCounter `json:"emblem-counter"` // puts a counter on an emblem in the launcher. Defaults to nil (no change to emblem counter). |
1794 | - Tag string `json:"tag,omitempty"` // tag used for Clear/ListPersistent. |
1795 | + Card *Card `json:"card"` // defaults to nil (no card) |
1796 | + RawSound json.RawMessage `json:"sound"` // a boolean, or the relative path to a sound file. Users can disable this, so don't rely on it exclusively. Defaults to empty (no sound). |
1797 | + RawVibration json.RawMessage `json:"vibrate"` // users can disable this, blah blah. Can be Vibration, or boolean. Defaults to null (no vibration) |
1798 | + EmblemCounter *EmblemCounter `json:"emblem-counter"` // puts a counter on an emblem in the launcher. Defaults to nil (no change to emblem counter). |
1799 | + Tag string `json:"tag,omitempty"` // tag used for Clear/ListPersistent. |
1800 | } |
1801 | |
1802 | // HelperOutput is the expected output of a helper |
1803 | @@ -76,3 +76,58 @@ |
1804 | NotificationId string |
1805 | Payload json.RawMessage |
1806 | } |
1807 | + |
1808 | +// Timestamp() returns RawTimestamp if non-zero. If it's zero, returns |
1809 | +// the current time as second since epoch. |
1810 | +func (card *Card) Timestamp() int64 { |
1811 | + if card.RawTimestamp == 0 { |
1812 | + return time.Now().Unix() |
1813 | + } else { |
1814 | + return int64(card.RawTimestamp) |
1815 | + } |
1816 | +} |
1817 | + |
1818 | +func (notification *Notification) Vibration(fallback *Vibration) *Vibration { |
1819 | + var b bool |
1820 | + var vib *Vibration |
1821 | + |
1822 | + if notification.RawVibration == nil { |
1823 | + return nil |
1824 | + } |
1825 | + if json.Unmarshal(notification.RawVibration, &b) == nil { |
1826 | + if !b { |
1827 | + return nil |
1828 | + } else { |
1829 | + return fallback |
1830 | + } |
1831 | + } |
1832 | + if json.Unmarshal(notification.RawVibration, &vib) != nil { |
1833 | + return nil |
1834 | + } |
1835 | + if len(vib.Pattern) == 0 { |
1836 | + return nil |
1837 | + } |
1838 | + |
1839 | + return vib |
1840 | +} |
1841 | + |
1842 | +func (notification *Notification) Sound(fallback string) string { |
1843 | + var b bool |
1844 | + var s string |
1845 | + |
1846 | + if notification.RawSound == nil { |
1847 | + return "" |
1848 | + } |
1849 | + if json.Unmarshal(notification.RawSound, &b) == nil { |
1850 | + if !b { |
1851 | + return "" |
1852 | + } else { |
1853 | + return fallback |
1854 | + } |
1855 | + } |
1856 | + if json.Unmarshal(notification.RawSound, &s) != nil { |
1857 | + return "" |
1858 | + } |
1859 | + |
1860 | + return s |
1861 | +} |
1862 | |
1863 | === added file 'launch_helper/helper_output_test.go' |
1864 | --- launch_helper/helper_output_test.go 1970-01-01 00:00:00 +0000 |
1865 | +++ launch_helper/helper_output_test.go 2014-08-12 02:32:01 +0000 |
1866 | @@ -0,0 +1,96 @@ |
1867 | +/* |
1868 | + Copyright 2014 Canonical Ltd. |
1869 | + |
1870 | + This program is free software: you can redistribute it and/or modify it |
1871 | + under the terms of the GNU General Public License version 3, as published |
1872 | + by the Free Software Foundation. |
1873 | + |
1874 | + This program is distributed in the hope that it will be useful, but |
1875 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
1876 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1877 | + PURPOSE. See the GNU General Public License for more details. |
1878 | + |
1879 | + You should have received a copy of the GNU General Public License along |
1880 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
1881 | +*/ |
1882 | + |
1883 | +package launch_helper |
1884 | + |
1885 | +import ( |
1886 | + "encoding/json" |
1887 | + "time" |
1888 | + |
1889 | + . "launchpad.net/gocheck" |
1890 | +) |
1891 | + |
1892 | +type outSuite struct{} |
1893 | + |
1894 | +var _ = Suite(&outSuite{}) |
1895 | + |
1896 | +func (*outSuite) TestCardGetTimestamp(c *C) { |
1897 | + t := time.Now().Add(-2 * time.Second) |
1898 | + var card Card |
1899 | + err := json.Unmarshal([]byte(`{"timestamp": 12}`), &card) |
1900 | + c.Assert(err, IsNil) |
1901 | + c.Check(card, DeepEquals, Card{RawTimestamp: 12}) |
1902 | + c.Check(time.Unix((&Card{}).Timestamp(), 0).After(t), Equals, true) |
1903 | + c.Check((&Card{RawTimestamp: 42}).Timestamp(), Equals, int64(42)) |
1904 | +} |
1905 | + |
1906 | +func (*outSuite) TestBadVibeBegetsNilVibe(c *C) { |
1907 | + fbck := &Vibration{Repeat: 2} |
1908 | + for _, s := range []string{ |
1909 | + `{}`, |
1910 | + `{"vibrate": "foo"}`, |
1911 | + `{"vibrate": {}}`, |
1912 | + `{"vibrate": false}`, // not bad, but rather pointless |
1913 | + `{"vibrate": {"repeat": 2}}`, // no pattern |
1914 | + `{"vibrate": {"repeat": "foo"}}`, |
1915 | + `{"vibrate": {"pattern": "foo"}}`, |
1916 | + `{"vibrate": {"pattern": ["foo"]}}`, |
1917 | + `{"vibrate": {"pattern": null}}`, |
1918 | + `{"vibrate": {"pattern": [-1]}}`, |
1919 | + `{"vibrate": {"pattern": [1], "repeat": -1}}`, |
1920 | + } { |
1921 | + var notif *Notification |
1922 | + err := json.Unmarshal([]byte(s), ¬if) |
1923 | + c.Assert(err, IsNil) |
1924 | + c.Assert(notif, NotNil) |
1925 | + c.Check(notif.Vibration(fbck), IsNil, Commentf("not nil Vibration() for: %s", s)) |
1926 | + c.Check(notif.Vibration(fbck), IsNil, Commentf("not nil second call to Vibration() for: %s", s)) |
1927 | + } |
1928 | +} |
1929 | + |
1930 | +func (*outSuite) TestGoodVibe(c *C) { |
1931 | + var notif *Notification |
1932 | + err := json.Unmarshal([]byte(`{"vibrate": {"pattern": [1,2,3], "repeat": 2}}`), ¬if) |
1933 | + c.Assert(err, IsNil) |
1934 | + c.Assert(notif, NotNil) |
1935 | + c.Check(notif.Vibration(nil), DeepEquals, &Vibration{Pattern: []uint32{1, 2, 3}, Repeat: 2}) |
1936 | +} |
1937 | + |
1938 | +func (*outSuite) TestGoodSimpleVibe(c *C) { |
1939 | + var notif *Notification |
1940 | + fallback := &Vibration{Pattern: []uint32{100, 100}, Repeat: 3} |
1941 | + err := json.Unmarshal([]byte(`{"vibrate": true}`), ¬if) |
1942 | + c.Assert(err, IsNil) |
1943 | + c.Assert(notif, NotNil) |
1944 | + c.Check(notif.Vibration(fallback), Equals, fallback) |
1945 | +} |
1946 | + |
1947 | +func (*outSuite) TestBadSoundBegetsNoSound(c *C) { |
1948 | + c.Check((&Notification{RawSound: json.RawMessage("foo")}).Sound("x"), Equals, "") |
1949 | +} |
1950 | + |
1951 | +func (*outSuite) TestNilSoundBegetsNoSound(c *C) { |
1952 | + c.Check((&Notification{RawSound: nil}).Sound("x"), Equals, "") |
1953 | +} |
1954 | + |
1955 | +func (*outSuite) TestGoodSound(c *C) { |
1956 | + c.Check((&Notification{RawSound: json.RawMessage(`"foo"`)}).Sound("x"), Equals, "foo") |
1957 | +} |
1958 | + |
1959 | +func (*outSuite) TestGoodSimpleSound(c *C) { |
1960 | + c.Check((&Notification{RawSound: json.RawMessage(`true`)}).Sound("x"), Equals, "x") |
1961 | + c.Check((&Notification{RawSound: json.RawMessage(`false`)}).Sound("x"), Equals, "") |
1962 | +} |
1963 | |
1964 | === modified file 'launch_helper/helper_test.go' |
1965 | --- launch_helper/helper_test.go 2014-07-22 14:08:25 +0000 |
1966 | +++ launch_helper/helper_test.go 2014-08-12 02:32:01 +0000 |
1967 | @@ -43,7 +43,7 @@ |
1968 | } |
1969 | |
1970 | func (s *runnerSuite) TestTrivialPoolWorks(c *C) { |
1971 | - notif := &Notification{Sound: "42", Tag: "foo"} |
1972 | + notif := &Notification{RawSound: json.RawMessage(`"42"`), Tag: "foo"} |
1973 | |
1974 | triv := NewTrivialHelperPool(s.testlog) |
1975 | ch := triv.Start() |
1976 | |
1977 | === modified file 'launch_helper/kindpool_test.go' |
1978 | --- launch_helper/kindpool_test.go 2014-07-30 09:26:25 +0000 |
1979 | +++ launch_helper/kindpool_test.go 2014-08-12 02:32:01 +0000 |
1980 | @@ -17,6 +17,7 @@ |
1981 | package launch_helper |
1982 | |
1983 | import ( |
1984 | + "encoding/json" |
1985 | "fmt" |
1986 | "io/ioutil" |
1987 | "os" |
1988 | @@ -285,7 +286,7 @@ |
1989 | |
1990 | res := takeNext(ch, c) |
1991 | |
1992 | - expected := HelperOutput{Notification: &Notification{Sound: "hello", Tag: "a-tag"}} |
1993 | + expected := HelperOutput{Notification: &Notification{RawSound: json.RawMessage(`"hello"`), Tag: "a-tag"}} |
1994 | c.Check(res.HelperOutput, DeepEquals, expected) |
1995 | c.Check(pool.hmap, HasLen, 0) |
1996 | } |
1997 | |
1998 | === modified file 'messaging/cmessaging/cmessaging.go' |
1999 | --- messaging/cmessaging/cmessaging.go 2014-07-24 16:25:55 +0000 |
2000 | +++ messaging/cmessaging/cmessaging.go 2014-08-12 02:32:01 +0000 |
2001 | @@ -84,7 +84,7 @@ |
2002 | body := gchar(card.Body) |
2003 | defer gfree(body) |
2004 | |
2005 | - timestamp := (C.gint64)(int64(card.Timestamp) * 1000000) |
2006 | + timestamp := (C.gint64)(card.Timestamp() * 1000000) |
2007 | |
2008 | C.add_notification(desktop_id, notification_id, icon_path, summary, body, timestamp, nil, (C.gpointer)(payload)) |
2009 | } |
2010 | |
2011 | === added file 'scripts/connect-many.py' |
2012 | --- scripts/connect-many.py 1970-01-01 00:00:00 +0000 |
2013 | +++ scripts/connect-many.py 2014-08-12 02:32:01 +0000 |
2014 | @@ -0,0 +1,29 @@ |
2015 | +#!/usr/bin/python3 |
2016 | +import sys |
2017 | +import resource |
2018 | +import socket |
2019 | +import ssl |
2020 | +import time |
2021 | + |
2022 | +host, port = sys.argv[1].split(":") |
2023 | +addr = (host, int(port)) |
2024 | +soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) |
2025 | +# reset soft == hard |
2026 | +resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard)) |
2027 | + |
2028 | +conns = [] |
2029 | +t0 = time.time() |
2030 | +try: |
2031 | + for i in range(soft+100): |
2032 | + s=socket.socket() |
2033 | + w = ssl.wrap_socket(s) |
2034 | + w.settimeout(1) |
2035 | + w.connect(addr) |
2036 | + conns.append(w) |
2037 | + w.send(b"x") |
2038 | +except Exception as e: |
2039 | + print("%s|%d|%s" % (e, len(conns), time.time()-t0)) |
2040 | + sys.exit(0) |
2041 | + |
2042 | +print("UNTROUBLED|%d" % len(conns)) |
2043 | +sys.exit(1) |
2044 | |
2045 | === added file 'scripts/goctest' |
2046 | --- scripts/goctest 1970-01-01 00:00:00 +0000 |
2047 | +++ scripts/goctest 2014-08-12 02:32:01 +0000 |
2048 | @@ -0,0 +1,46 @@ |
2049 | +#!/usr/bin/python3 |
2050 | +# -*- python -*- |
2051 | +# (c) 2014 John Lenton |
2052 | +# MIT licensed. |
2053 | +# from https://github.com/chipaca/goctest |
2054 | + |
2055 | +import re |
2056 | +import signal |
2057 | +import subprocess |
2058 | +import sys |
2059 | + |
2060 | +ok_rx = re.compile(rb'^(PASS:?|ok\s+)') |
2061 | +fail_rx = re.compile(rb'^(FAIL:?|OOPS:?)') |
2062 | +panic_rx = re.compile(rb'^(PANIC:?|panic:?|\.\.\. Panic:?)') |
2063 | +log_rx = re.compile(rb'^\[LOG\]|^\?\s+') |
2064 | + |
2065 | +class bcolors: |
2066 | + OK = b'\033[38;5;34m' |
2067 | + FAIL = b'\033[38;5;196m' |
2068 | + PANIC = b'\033[38;5;226m\033[48;5;88m' |
2069 | + OTHER = b'\033[38;5;241m' |
2070 | + WARNING = b'\033[38;5;226m' |
2071 | + ENDC = b'\033[0m' |
2072 | + |
2073 | +signal.signal(signal.SIGINT, lambda *_: None) |
2074 | + |
2075 | +if sys.stdout.isatty(): |
2076 | + with subprocess.Popen(["go", "test"] + sys.argv[1:], |
2077 | + bufsize=0, |
2078 | + stderr=subprocess.STDOUT, |
2079 | + stdout=subprocess.PIPE) as proc: |
2080 | + for line in proc.stdout: |
2081 | + if panic_rx.search(line) is not None: |
2082 | + line = panic_rx.sub(bcolors.PANIC + rb'\1' + bcolors.ENDC, line) |
2083 | + elif fail_rx.search(line) is not None: |
2084 | + line = fail_rx.sub(bcolors.FAIL + rb'\1' + bcolors.ENDC, line) |
2085 | + elif ok_rx.search(line) is not None: |
2086 | + line = ok_rx.sub(bcolors.OK + rb'\1' + bcolors.ENDC, line) |
2087 | + elif log_rx.search(line) is not None: |
2088 | + line = bcolors.OTHER + line + bcolors.ENDC |
2089 | + |
2090 | + sys.stdout.write(line.decode("utf-8")) |
2091 | + sys.stdout.flush() |
2092 | + sys.exit(proc.wait()) |
2093 | +else: |
2094 | + sys.exit(subprocess.call(["go", "test"] + sys.argv[1:])) |
2095 | |
2096 | === removed file 'scripts/software-updates-helper.py' |
2097 | --- scripts/software-updates-helper.py 2014-07-19 09:22:50 +0000 |
2098 | +++ scripts/software-updates-helper.py 1970-01-01 00:00:00 +0000 |
2099 | @@ -1,45 +0,0 @@ |
2100 | -#!/usr/bin/python3 |
2101 | -# Software Updates Push Notifications helper. |
2102 | -# |
2103 | -# This helper is called with one of two things: |
2104 | -# * regular push messages about updated click packages |
2105 | -# * broadcast messages about system updates |
2106 | -# |
2107 | -# the latter is unique to this helper. Figuring out which of those two |
2108 | -# it is is also this helper's job. |
2109 | - |
2110 | -import json |
2111 | -import sys |
2112 | -import time |
2113 | - |
2114 | -if len(sys.argv) != 3: |
2115 | - print("File in and out expected via argv", file=sys.stderr) |
2116 | - sys.exit(1) |
2117 | - |
2118 | -f1, f2 = sys.argv[1:3] |
2119 | - |
2120 | -# XXX assuming it's an actionable broadcast. Smarts go here. |
2121 | - |
2122 | -obj = { |
2123 | - "notification": { |
2124 | - "emblem-counter": { |
2125 | - "count": 1, |
2126 | - "visible": True, |
2127 | - }, |
2128 | - "vibrate": { |
2129 | - "pattern": [50,150], |
2130 | - "repeat": 3, |
2131 | - }, |
2132 | - "card": { |
2133 | - "summary": "There's an updated system image.", |
2134 | - "body": "Tap to open the system updater.", |
2135 | - "actions": ["settings:///system/system-update"], |
2136 | - "icon": "/usr/share/ubuntu/settings/system/icons/settings-system-update.svg", |
2137 | - "timestamp": int(time.time()), |
2138 | - "persist": True, |
2139 | - "popup": True, |
2140 | - }, |
2141 | - }, |
2142 | -} |
2143 | - |
2144 | -json.dump(obj, open(f2,"w")) |
2145 | |
2146 | === modified file 'server/listener/listener_test.go' |
2147 | --- server/listener/listener_test.go 2014-03-06 19:21:44 +0000 |
2148 | +++ server/listener/listener_test.go 2014-08-12 02:32:01 +0000 |
2149 | @@ -20,9 +20,12 @@ |
2150 | "crypto/tls" |
2151 | "crypto/x509" |
2152 | "net" |
2153 | + "os/exec" |
2154 | + "regexp" |
2155 | "syscall" |
2156 | "testing" |
2157 | "time" |
2158 | + "unicode" |
2159 | |
2160 | . "launchpad.net/gocheck" |
2161 | |
2162 | @@ -37,7 +40,7 @@ |
2163 | |
2164 | var _ = Suite(&listenerSuite{}) |
2165 | |
2166 | -const NofileMax = 500 |
2167 | +const NofileMax = 20 |
2168 | |
2169 | func (s *listenerSuite) SetUpSuite(*C) { |
2170 | // make it easier to get a too many open files error |
2171 | @@ -111,13 +114,19 @@ |
2172 | |
2173 | func testSession(conn net.Conn) error { |
2174 | defer conn.Close() |
2175 | - conn.SetDeadline(time.Now().Add(2 * time.Second)) |
2176 | + conn.SetDeadline(time.Now().Add(10 * time.Second)) |
2177 | var buf [1]byte |
2178 | - _, err := conn.Read(buf[:]) |
2179 | - if err != nil { |
2180 | - return err |
2181 | + for { |
2182 | + _, err := conn.Read(buf[:]) |
2183 | + if err != nil { |
2184 | + return err |
2185 | + } |
2186 | + // 1|2... send digit back |
2187 | + if unicode.IsDigit(rune(buf[0])) { |
2188 | + break |
2189 | + } |
2190 | } |
2191 | - _, err = conn.Write(buf[:]) |
2192 | + _, err := conn.Write(buf[:]) |
2193 | return err |
2194 | } |
2195 | |
2196 | @@ -165,6 +174,18 @@ |
2197 | c.Check(s.testlog.Captured(), Equals, "") |
2198 | } |
2199 | |
2200 | +// waitForLogs waits for the logs captured in s.testlog to match reStr. |
2201 | +func (s *listenerSuite) waitForLogs(c *C, reStr string) { |
2202 | + rx := regexp.MustCompile("^" + reStr + "$") |
2203 | + for i := 0; i < 100; i++ { |
2204 | + if rx.MatchString(s.testlog.Captured()) { |
2205 | + break |
2206 | + } |
2207 | + time.Sleep(20 * time.Millisecond) |
2208 | + } |
2209 | + c.Check(s.testlog.Captured(), Matches, reStr) |
2210 | +} |
2211 | + |
2212 | func (s *listenerSuite) TestDeviceAcceptLoopTemporaryError(c *C) { |
2213 | // ENFILE is not the temp network error we want to handle this way |
2214 | // but is relatively easy to generate in a controlled way |
2215 | @@ -177,20 +198,11 @@ |
2216 | errCh <- lst.AcceptLoop(testSession, s.testlog) |
2217 | }() |
2218 | listenerAddr := lst.Addr().String() |
2219 | - conns := make([]net.Conn, 0, NofileMax) |
2220 | - for i := 0; i < NofileMax; i++ { |
2221 | - var conn1 net.Conn |
2222 | - conn1, err = net.Dial("tcp", listenerAddr) |
2223 | - if err != nil { |
2224 | - break |
2225 | - } |
2226 | - defer conn1.Close() |
2227 | - conns = append(conns, conn1) |
2228 | - } |
2229 | - c.Assert(err, ErrorMatches, "*.too many open.*") |
2230 | - for _, conn := range conns { |
2231 | - conn.Close() |
2232 | - } |
2233 | + connectMany := helpers.ScriptAbsPath("connect-many.py") |
2234 | + cmd := exec.Command(connectMany, listenerAddr) |
2235 | + res, err := cmd.Output() |
2236 | + c.Assert(err, IsNil) |
2237 | + c.Assert(string(res), Matches, "(?s).*timed out.*") |
2238 | conn2, err := testTlsDial(c, listenerAddr) |
2239 | c.Assert(err, IsNil) |
2240 | defer conn2.Close() |
2241 | @@ -198,7 +210,7 @@ |
2242 | testReadByte(c, conn2, '2') |
2243 | lst.Close() |
2244 | c.Check(<-errCh, ErrorMatches, ".*use of closed.*") |
2245 | - c.Check(s.testlog.Captured(), Matches, ".*device listener:.*accept.*too many open.*-- retrying\n") |
2246 | + s.waitForLogs(c, "(?ms).*device listener:.*accept.*too many open.*-- retrying") |
2247 | } |
2248 | |
2249 | func (s *listenerSuite) TestDeviceAcceptLoopPanic(c *C) { |
2250 | @@ -217,7 +229,7 @@ |
2251 | c.Assert(err, Not(IsNil)) |
2252 | lst.Close() |
2253 | c.Check(<-errCh, ErrorMatches, ".*use of closed.*") |
2254 | - c.Check(s.testlog.Captured(), Matches, "(?s)ERROR\\(PANIC\\) terminating device connection on: session crash:.*AcceptLoop.*") |
2255 | + s.waitForLogs(c, "(?s)ERROR\\(PANIC\\) terminating device connection on: session crash:.*AcceptLoop.*") |
2256 | } |
2257 | |
2258 | func (s *listenerSuite) TestForeignListener(c *C) { |
2259 | |
2260 | === modified file 'sounds/sounds.go' |
2261 | --- sounds/sounds.go 2014-07-25 10:25:59 +0000 |
2262 | +++ sounds/sounds.go 2014-08-12 02:32:01 +0000 |
2263 | @@ -17,9 +17,11 @@ |
2264 | package sounds |
2265 | |
2266 | import ( |
2267 | + "errors" |
2268 | "os" |
2269 | "os/exec" |
2270 | "path/filepath" |
2271 | + "strings" |
2272 | |
2273 | "launchpad.net/go-xdg/v0" |
2274 | |
2275 | @@ -31,12 +33,19 @@ |
2276 | type Sound struct { |
2277 | player string |
2278 | log logger.Logger |
2279 | + fallback string |
2280 | dataDirs func() []string |
2281 | dataFind func(string) (string, error) |
2282 | } |
2283 | |
2284 | -func New(log logger.Logger) *Sound { |
2285 | - return &Sound{player: "paplay", log: log, dataDirs: xdg.Data.Dirs, dataFind: xdg.Data.Find} |
2286 | +func New(log logger.Logger, fallback string) *Sound { |
2287 | + return &Sound{ |
2288 | + player: "paplay", |
2289 | + log: log, |
2290 | + fallback: fallback, |
2291 | + dataDirs: xdg.Data.Dirs, |
2292 | + dataFind: xdg.Data.Find, |
2293 | + } |
2294 | } |
2295 | |
2296 | func (snd *Sound) Present(app *click.AppId, nid string, notification *launch_helper.Notification) bool { |
2297 | @@ -44,13 +53,14 @@ |
2298 | panic("please check notification is not nil before calling present") |
2299 | } |
2300 | |
2301 | - if notification.Sound == "" { |
2302 | - snd.log.Debugf("[%s] notification has no Sound: %#v", nid, notification.Sound) |
2303 | + sound := notification.Sound(snd.fallback) |
2304 | + if sound == "" { |
2305 | + snd.log.Debugf("[%s] notification has no Sound: %#v", nid, sound) |
2306 | return false |
2307 | } |
2308 | - absPath := snd.findSoundFile(app, nid, notification.Sound) |
2309 | + absPath := snd.findSoundFile(app, nid, sound) |
2310 | if absPath == "" { |
2311 | - snd.log.Debugf("[%s] unable to find sound %s", nid, notification.Sound) |
2312 | + snd.log.Debugf("[%s] unable to find sound %s", nid, sound) |
2313 | return false |
2314 | } |
2315 | snd.log.Debugf("[%s] playing sound %s using %s", nid, absPath, snd.player) |
2316 | @@ -69,9 +79,23 @@ |
2317 | return true |
2318 | } |
2319 | |
2320 | +// Removes all cruft from path, ensures it's a "forward" path. |
2321 | +func (snd *Sound) cleanPath(path string) (string, error) { |
2322 | + cleaned := filepath.Clean(path) |
2323 | + if strings.Contains(cleaned, "../") { |
2324 | + return "", errors.New("Path escaping xdg attempt") |
2325 | + } |
2326 | + return cleaned, nil |
2327 | +} |
2328 | + |
2329 | func (snd *Sound) findSoundFile(app *click.AppId, nid string, sound string) string { |
2330 | // XXX also support legacy appIds? |
2331 | // first, check package-specific |
2332 | + sound, err := snd.cleanPath(sound) |
2333 | + if err != nil { |
2334 | + // bad boy |
2335 | + return "" |
2336 | + } |
2337 | absPath, err := snd.dataFind(filepath.Join(app.Package, sound)) |
2338 | if err == nil { |
2339 | // ffffound |
2340 | |
2341 | === modified file 'sounds/sounds_test.go' |
2342 | --- sounds/sounds_test.go 2014-07-25 10:25:59 +0000 |
2343 | +++ sounds/sounds_test.go 2014-08-12 02:32:01 +0000 |
2344 | @@ -17,6 +17,7 @@ |
2345 | package sounds |
2346 | |
2347 | import ( |
2348 | + "encoding/json" |
2349 | "errors" |
2350 | "os" |
2351 | "path" |
2352 | @@ -45,9 +46,10 @@ |
2353 | } |
2354 | |
2355 | func (ss *soundsSuite) TestNew(c *C) { |
2356 | - s := New(ss.log) |
2357 | + s := New(ss.log, "foo") |
2358 | c.Check(s.log, Equals, ss.log) |
2359 | c.Check(s.player, Equals, "paplay") |
2360 | + c.Check(s.fallback, Equals, "foo") |
2361 | } |
2362 | |
2363 | func (ss *soundsSuite) TestPresent(c *C) { |
2364 | @@ -57,10 +59,22 @@ |
2365 | } |
2366 | |
2367 | c.Check(s.Present(ss.app, "", |
2368 | - &launch_helper.Notification{Sound: "hello"}), Equals, true) |
2369 | + &launch_helper.Notification{RawSound: json.RawMessage(`"hello"`)}), Equals, true) |
2370 | c.Check(ss.log.Captured(), Matches, `(?sm).* playing sound com.example.test/hello using echo`) |
2371 | } |
2372 | |
2373 | +func (ss *soundsSuite) TestPresentSimple(c *C) { |
2374 | + s := &Sound{ |
2375 | + player: "echo", log: ss.log, |
2376 | + dataFind: func(s string) (string, error) { return s, nil }, |
2377 | + fallback: "fallback", |
2378 | + } |
2379 | + |
2380 | + c.Check(s.Present(ss.app, "", |
2381 | + &launch_helper.Notification{RawSound: json.RawMessage(`true`)}), Equals, true) |
2382 | + c.Check(ss.log.Captured(), Matches, `(?sm).* playing sound com.example.test/fallback using echo`) |
2383 | +} |
2384 | + |
2385 | func (ss *soundsSuite) TestPresentFails(c *C) { |
2386 | s := &Sound{ |
2387 | player: "/", |
2388 | @@ -74,10 +88,10 @@ |
2389 | // no Sound |
2390 | c.Check(s.Present(ss.app, "", &launch_helper.Notification{}), Equals, false) |
2391 | // bad player |
2392 | - c.Check(s.Present(ss.app, "", &launch_helper.Notification{Sound: "hello"}), Equals, false) |
2393 | + c.Check(s.Present(ss.app, "", &launch_helper.Notification{RawSound: json.RawMessage(`"hello"`)}), Equals, false) |
2394 | s.player = "echo" |
2395 | // no file found |
2396 | - c.Check(s.Present(ss.app, "", &launch_helper.Notification{Sound: "hello"}), Equals, false) |
2397 | + c.Check(s.Present(ss.app, "", &launch_helper.Notification{RawSound: json.RawMessage(`"hello"`)}), Equals, false) |
2398 | |
2399 | // and now, just to prove it would've worked, |
2400 | |
2401 | @@ -86,5 +100,31 @@ |
2402 | c.Assert(err, IsNil) |
2403 | f.Close() |
2404 | s.dataDirs = func() []string { return []string{"", d} } |
2405 | - c.Check(s.Present(ss.app, "", &launch_helper.Notification{Sound: "hello"}), Equals, true) |
2406 | + c.Check(s.Present(ss.app, "", &launch_helper.Notification{RawSound: json.RawMessage(`"hello"`)}), Equals, true) |
2407 | +} |
2408 | + |
2409 | +func (ss *soundsSuite) TestBadPathFails(c *C) { |
2410 | + s := &Sound{ |
2411 | + player: "/", |
2412 | + log: ss.log, |
2413 | + dataFind: func(string) (string, error) { return "", errors.New("nope") }, |
2414 | + dataDirs: func() []string { return []string{""} }, |
2415 | + } |
2416 | + |
2417 | + sound, err := s.cleanPath("../../foo") |
2418 | + c.Check(err, NotNil) |
2419 | + c.Check(sound, Equals, "") |
2420 | +} |
2421 | + |
2422 | +func (ss *soundsSuite) TestGoodPathSucceeds(c *C) { |
2423 | + s := &Sound{ |
2424 | + player: "/", |
2425 | + log: ss.log, |
2426 | + dataFind: func(string) (string, error) { return "", errors.New("nope") }, |
2427 | + dataDirs: func() []string { return []string{""} }, |
2428 | + } |
2429 | + |
2430 | + sound, err := s.cleanPath("foo/../bar") |
2431 | + c.Check(err, IsNil) |
2432 | + c.Check(sound, Equals, "bar") |
2433 | } |
2434 | |
2435 | === modified file 'tests/autopilot/push_notifications/tests/__init__.py' |
2436 | --- tests/autopilot/push_notifications/tests/__init__.py 2014-07-09 23:26:45 +0000 |
2437 | +++ tests/autopilot/push_notifications/tests/__init__.py 2014-08-12 02:32:01 +0000 |
2438 | @@ -26,6 +26,7 @@ |
2439 | import evdev |
2440 | |
2441 | from autopilot.introspection import dbus |
2442 | +from autopilot.exceptions import StateNotFoundError |
2443 | from push_notifications import config as push_config |
2444 | import push_notifications.helpers.push_notifications_helper as push_helper |
2445 | from testtools.matchers import Equals |
2446 | @@ -238,6 +239,22 @@ |
2447 | """ |
2448 | self.validate_and_dismiss_notification_dialog(message, secondary_icon=False) |
2449 | |
2450 | + def validate_and_tap_notification_dialog(self, message, |
2451 | + secondary_icon=True): |
2452 | + """ |
2453 | + Validate a notification dialog is displayed and dismiss it |
2454 | + :param message: expected message displayed in summary |
2455 | + """ |
2456 | + # get the dialog |
2457 | + dialog, props = self.get_notification_dialog() |
2458 | + # validate dialog |
2459 | + self.assert_notification_dialog( |
2460 | + props, summary=message, secondary_icon=secondary_icon) |
2461 | + # tap the dialog |
2462 | + self.touch.tap_object(dialog) |
2463 | + # check the dialog is no longer displayed |
2464 | + self.validate_notification_not_displayed(wait=False) |
2465 | + |
2466 | def wait_until_dialog_dismissed(self, dialog): |
2467 | """Wait for the dialog to dismiss automatically""" |
2468 | dialog_disappeared = False |
2469 | @@ -270,7 +287,7 @@ |
2470 | self.wait_until_dialog_dismissed(dialog) |
2471 | |
2472 | # unicast messages |
2473 | - def send_unicast_notification(self, icon="messages-app", |
2474 | + def send_unicast_notification(self, icon="", |
2475 | body="A unicast message", summary="Look!", |
2476 | persist=False, popup=True, actions=[], emblem_counter={}): |
2477 | """Build and send a push unicast message. |
2478 | @@ -309,3 +326,36 @@ |
2479 | indicator_page = self.main_window.open_indicator_page( |
2480 | "indicator-messages") |
2481 | return indicator_page |
2482 | + |
2483 | + def validate_mmu_notification(self, body_text, title_text): |
2484 | + # get the mmu notification and check the body and title. |
2485 | + # swipe down and show the incomming page |
2486 | + messaging = self.get_messaging_menu() |
2487 | + # get the notification and check the body and title. |
2488 | + menuItem0 = messaging.select_single('QQuickLoader', |
2489 | + objectName='menuItem0') |
2490 | + hmh = menuItem0.select_single('HeroMessageHeader') |
2491 | + body = hmh.select_single("Label", objectName='body') |
2492 | + self.assertEqual(body.text, body_text) |
2493 | + title = hmh.select_single("Label", objectName='title') |
2494 | + self.assertEqual(title.text, title_text) |
2495 | + self.clear_mmu(ignore_missing=False) |
2496 | + |
2497 | + def clear_mmu(self, ignore_missing=True): |
2498 | + # get the mmu notification and check the body and title. |
2499 | + messaging = self.get_messaging_menu() |
2500 | + # clear all notifications |
2501 | + try: |
2502 | + clear_all = messaging.select_single( |
2503 | + 'ButtonMenu', objectName='indicator.remove-all') |
2504 | + except StateNotFoundError: |
2505 | + if not ignore_missing: |
2506 | + raise |
2507 | + return |
2508 | + emptyLabel = messaging.select_single('Label', |
2509 | + objectName='emptyLabel') |
2510 | + self.assertFalse(emptyLabel.visible) |
2511 | + self.touch.tap_object(clear_all) |
2512 | + emptyLabel = messaging.select_single('Label', |
2513 | + objectName='emptyLabel') |
2514 | + self.assertTrue(emptyLabel.visible) |
2515 | |
2516 | === modified file 'tests/autopilot/push_notifications/tests/test_broadcast_notifications.py' |
2517 | --- tests/autopilot/push_notifications/tests/test_broadcast_notifications.py 2014-07-09 18:37:25 +0000 |
2518 | +++ tests/autopilot/push_notifications/tests/test_broadcast_notifications.py 2014-08-12 02:32:01 +0000 |
2519 | @@ -21,15 +21,19 @@ |
2520 | |
2521 | import time |
2522 | |
2523 | +from autopilot import platform |
2524 | +from testtools import skipIf |
2525 | + |
2526 | from push_notifications.tests import PushNotificationTestBase |
2527 | |
2528 | |
2529 | +DEFAULT_DISPLAY_MESSAGE = "There's an updated system image." |
2530 | +MMU_BODY = "Tap to open the system updater." |
2531 | +MMU_TITLE = DEFAULT_DISPLAY_MESSAGE |
2532 | + |
2533 | + |
2534 | class TestPushClientBroadcast(PushNotificationTestBase): |
2535 | - """ |
2536 | - Test cases for broadcast push notifications |
2537 | - """ |
2538 | - |
2539 | - DEFAULT_DISPLAY_MESSAGE = 'There\'s an updated system image.' |
2540 | + """Test cases for broadcast push notifications""" |
2541 | |
2542 | def test_broadcast_push_notification_screen_off(self): |
2543 | """ |
2544 | @@ -45,8 +49,12 @@ |
2545 | time.sleep(2) |
2546 | # Turn display on |
2547 | self.press_power_button() |
2548 | - self.validate_and_dismiss_broadcast_notification_dialog( |
2549 | - self.DEFAULT_DISPLAY_MESSAGE) |
2550 | + # Assumes greeter starts in locked state |
2551 | + self.unlock_greeter() |
2552 | + # check the bubble is there |
2553 | + self.validate_and_dismiss_notification_dialog(DEFAULT_DISPLAY_MESSAGE) |
2554 | + # clear the mmu |
2555 | + self.clear_mmu() |
2556 | |
2557 | def test_broadcast_push_notification_locked_greeter(self): |
2558 | """ |
2559 | @@ -54,10 +62,13 @@ |
2560 | to the client and validate that a notification message is displayed |
2561 | whist the greeter screen is displayed |
2562 | """ |
2563 | + self.send_push_broadcast_message() |
2564 | + # check the bubble is there |
2565 | + self.validate_and_dismiss_notification_dialog(DEFAULT_DISPLAY_MESSAGE) |
2566 | # Assumes greeter starts in locked state |
2567 | - self.send_push_broadcast_message() |
2568 | - self.validate_and_dismiss_broadcast_notification_dialog( |
2569 | - self.DEFAULT_DISPLAY_MESSAGE) |
2570 | + self.unlock_greeter() |
2571 | + # clear the mmu |
2572 | + self.clear_mmu() |
2573 | |
2574 | def test_broadcast_push_notification(self): |
2575 | """ |
2576 | @@ -67,8 +78,42 @@ |
2577 | # Assumes greeter starts in locked state |
2578 | self.unlock_greeter() |
2579 | self.send_push_broadcast_message() |
2580 | - self.validate_and_dismiss_broadcast_notification_dialog( |
2581 | - self.DEFAULT_DISPLAY_MESSAGE) |
2582 | + # check the bubble is there |
2583 | + self.validate_and_dismiss_notification_dialog(DEFAULT_DISPLAY_MESSAGE) |
2584 | + # clear the mmu |
2585 | + self.clear_mmu() |
2586 | + |
2587 | + @skipIf(platform.model() == 'X86 Emulator', |
2588 | + "Test not working in the emulator") |
2589 | + def test_broadcast_push_notification_is_persistent(self): |
2590 | + """ |
2591 | + Positive test case to send a valid broadcast push notification |
2592 | + to the client and validate that a notification message is displayed |
2593 | + and includes a persitent mmu notification. |
2594 | + """ |
2595 | + # Assumes greeter starts in locked state |
2596 | + self.unlock_greeter() |
2597 | + self.send_push_broadcast_message() |
2598 | + # check the bubble is there |
2599 | + self.validate_and_dismiss_notification_dialog(DEFAULT_DISPLAY_MESSAGE) |
2600 | + # check the mmu notification is there |
2601 | + self.validate_mmu_notification(MMU_BODY, MMU_TITLE) |
2602 | + |
2603 | + def test_broadcast_push_notification_click_bubble_clears_mmu(self): |
2604 | + """ |
2605 | + Positive test case to send a valid broadcast push notification |
2606 | + to the client and validate that a notification message is displayed |
2607 | + """ |
2608 | + # Assumes greeter starts in locked state |
2609 | + self.unlock_greeter() |
2610 | + self.send_push_broadcast_message() |
2611 | + # check the bubble is there |
2612 | + self.validate_and_tap_notification_dialog(DEFAULT_DISPLAY_MESSAGE) |
2613 | + # swipe down and show the incomming page |
2614 | + messaging = self.get_messaging_menu() |
2615 | + emptyLabel = messaging.select_single('Label', |
2616 | + objectName='emptyLabel') |
2617 | + self.assertTrue(emptyLabel.visible) |
2618 | |
2619 | def test_broadcast_push_notification_on_connect(self): |
2620 | """ |
2621 | @@ -81,8 +126,10 @@ |
2622 | self.push_client_controller.stop_push_client() |
2623 | self.send_push_broadcast_message() |
2624 | self.push_client_controller.start_push_client() |
2625 | - self.validate_and_dismiss_broadcast_notification_dialog( |
2626 | - self.DEFAULT_DISPLAY_MESSAGE) |
2627 | + # check the bubble is there |
2628 | + self.validate_and_dismiss_notification_dialog(DEFAULT_DISPLAY_MESSAGE) |
2629 | + # clear the mmu |
2630 | + self.clear_mmu() |
2631 | |
2632 | def test_expired_broadcast_push_notification(self): |
2633 | """ |
2634 | |
2635 | === modified file 'tests/autopilot/push_notifications/tests/test_unicast_notifications.py' |
2636 | --- tests/autopilot/push_notifications/tests/test_unicast_notifications.py 2014-07-10 01:21:45 +0000 |
2637 | +++ tests/autopilot/push_notifications/tests/test_unicast_notifications.py 2014-08-12 02:32:01 +0000 |
2638 | @@ -21,6 +21,9 @@ |
2639 | |
2640 | import os |
2641 | |
2642 | +from autopilot import platform |
2643 | +from testtools import skipIf |
2644 | + |
2645 | from push_notifications.tests import PushNotificationTestBase |
2646 | |
2647 | |
2648 | @@ -30,21 +33,24 @@ |
2649 | DEFAULT_DISPLAY_MESSAGE = 'Look!' |
2650 | |
2651 | scenarios = [('click_app_with_version', |
2652 | - dict(app_name="com.ubuntu.calculator_calculator", |
2653 | + dict(app_name="com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter", |
2654 | appid=None, path=None, |
2655 | desktop_dir="~/.local/share/applications/", |
2656 | - launcher_idx=4)), |
2657 | + icon="twitter", |
2658 | + launcher_idx=5)), |
2659 | ('click_app', |
2660 | - dict(app_name="com.ubuntu.calculator_calculator", |
2661 | - appid="com.ubuntu.calculator_calculator", |
2662 | - path="com_2eubuntu_2ecalculator", |
2663 | + dict(app_name="com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter", |
2664 | + appid="com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter", |
2665 | + path="com_2eubuntu_2edeveloper_2ewebapps_2ewebapp_2dtwitter", |
2666 | desktop_dir="~/.local/share/applications/", |
2667 | - launcher_idx=4)), |
2668 | + icon="twitter", |
2669 | + launcher_idx=5)), |
2670 | ('legacy_app', |
2671 | dict(app_name="messaging-app", |
2672 | appid="_messaging-app", |
2673 | path="_", |
2674 | desktop_dir="/usr/share/applications/", |
2675 | + icon="messages-app", |
2676 | launcher_idx=1))] |
2677 | |
2678 | def setUp(self): |
2679 | @@ -63,10 +69,13 @@ |
2680 | fname.endswith(".desktop"): |
2681 | # remove .desktop extension, only need the name. |
2682 | appid = os.path.splitext(fname)[0] |
2683 | - path = appid.split("_")[0].replace(".", "_2e") |
2684 | + path = appid.split("_")[0] |
2685 | + path = path.replace(".", "_2e").replace("-", "_2d") |
2686 | return appid, path |
2687 | return self.appid, self.path |
2688 | |
2689 | + @skipIf(platform.model() == 'X86 Emulator', |
2690 | + "Test not working in the emulator") |
2691 | def test_unicast_push_notification_persistent(self): |
2692 | """Send a persistent unicast push notification. |
2693 | |
2694 | @@ -76,17 +85,9 @@ |
2695 | # Assumes greeter starts in locked state |
2696 | self.unlock_greeter() |
2697 | # send message |
2698 | - self.send_unicast_notification(persist=True) |
2699 | - # swipe down and show the incomming page |
2700 | - messaging = self.get_messaging_menu() |
2701 | - # get the notification and check the body and title. |
2702 | - menuItem0 = messaging.select_single('QQuickLoader', |
2703 | - objectName='menuItem0') |
2704 | - hmh = menuItem0.select_single('HeroMessageHeader') |
2705 | - body = hmh.select_single("Label", objectName='body') |
2706 | - self.assertEqual(body.text, 'A unicast message') |
2707 | - title = hmh.select_single("Label", objectName='title') |
2708 | - self.assertEqual(title.text, 'Look!') |
2709 | + self.send_unicast_notification(persist=True, popup=False, |
2710 | + icon=self.icon) |
2711 | + self.validate_mmu_notification("A unicast message", "Look!") |
2712 | |
2713 | def get_running_app_launcher_icon(self): |
2714 | launcher = self.main_window.get_launcher() |
2715 | @@ -118,6 +119,7 @@ |
2716 | # send message, only showing emblem counter |
2717 | emblem_counter = {'count': 42, 'visible': True} |
2718 | self.send_unicast_notification(persist=False, popup=False, |
2719 | + icon=self.icon, |
2720 | emblem_counter=emblem_counter) |
2721 | # show the dash and check the emblem count. |
2722 | self.main_window.show_dash_swiping() |
2723 | @@ -131,14 +133,15 @@ |
2724 | The notification should be displayed on top of the greeter. |
2725 | """ |
2726 | # Assumes greeter starts in locked state |
2727 | - self.send_unicast_notification(summary="Locked greeter") |
2728 | + self.send_unicast_notification(summary="Locked greeter", |
2729 | + icon=self.icon) |
2730 | self.validate_and_dismiss_notification_dialog("Locked greeter") |
2731 | |
2732 | def test_unicast_push_notification(self): |
2733 | """Send a push notificationn and validate it's displayed.""" |
2734 | # Assumes greeter starts in locked state |
2735 | self.unlock_greeter() |
2736 | - self.send_unicast_notification() |
2737 | + self.send_unicast_notification(icon=self.icon) |
2738 | self.validate_and_dismiss_notification_dialog( |
2739 | self.DEFAULT_DISPLAY_MESSAGE) |
2740 | |
2741 | @@ -151,7 +154,7 @@ |
2742 | # Assumes greeter starts in locked state |
2743 | self.unlock_greeter() |
2744 | self.push_client_controller.stop_push_client() |
2745 | - self.send_unicast_notification() |
2746 | + self.send_unicast_notification(icon=self.icon) |
2747 | self.push_client_controller.start_push_client() |
2748 | self.validate_and_dismiss_notification_dialog( |
2749 | self.DEFAULT_DISPLAY_MESSAGE) |
2750 | |
2751 | === modified file 'tests/autopilot/run.sh' |
2752 | --- tests/autopilot/run.sh 2014-07-10 13:25:57 +0000 |
2753 | +++ tests/autopilot/run.sh 2014-08-12 02:32:01 +0000 |
2754 | @@ -8,15 +8,17 @@ |
2755 | usage: $0 options |
2756 | |
2757 | OPTIONS: |
2758 | + -t fqn of the tests to run (module, class or method) |
2759 | -d adb device_id |
2760 | -v run tests in verbose mode |
2761 | EOF |
2762 | } |
2763 | |
2764 | -while getopts "d:v" opt; do |
2765 | +while getopts "t:d:v" opt; do |
2766 | case $opt in |
2767 | d) DEVICE_ID=$OPTARG;; |
2768 | v) VERBOSITY="-v";; |
2769 | + t) TESTS=$OPTARG;; |
2770 | *) usage |
2771 | exit 1 |
2772 | ;; |
2773 | @@ -24,7 +26,9 @@ |
2774 | done |
2775 | |
2776 | |
2777 | +VERBOSITY=${VERBOSITY:-""} |
2778 | +TESTS=${TESTS:-"push_notifications"} |
2779 | DEVICE_ID=${DEVICE_ID:-"emulator-5554"} |
2780 | BASE_DIR="ubuntu-push/src/launchpad.net" |
2781 | BRANCH_DIR="$BASE_DIR/ubuntu-push" |
2782 | -adb -s ${DEVICE_ID} shell "su - phablet bash -c 'cd ${BRANCH_DIR}/tests/autopilot/; /sbin/initctl stop unity8; autopilot3 run ${VERBOSITY} push_notifications'" |
2783 | +adb -s ${DEVICE_ID} shell "su - phablet bash -c 'cd ${BRANCH_DIR}/tests/autopilot/; /sbin/initctl stop unity8; autopilot3 run ${VERBOSITY} ${TESTS}'" |
2784 | |
2785 | === modified file 'tests/autopilot/setup.sh' |
2786 | --- tests/autopilot/setup.sh 2014-07-10 20:54:12 +0000 |
2787 | +++ tests/autopilot/setup.sh 2014-08-12 02:32:01 +0000 |
2788 | @@ -15,11 +15,11 @@ |
2789 | EOF |
2790 | } |
2791 | |
2792 | -while getopts "H:b:c:u" opt; do |
2793 | +while getopts "H:b:d:u" opt; do |
2794 | case $opt in |
2795 | H) PUSH_SERVER=$OPTARG;; |
2796 | b) BRANCH_URL=$OPTARG;; |
2797 | - c) DEVICE_ID=$OPTARG;; |
2798 | + d) DEVICE_ID=$OPTARG;; |
2799 | u) APT_UPDATE="1";; |
2800 | *) usage |
2801 | exit 1 |
2802 | @@ -54,18 +54,28 @@ |
2803 | adb -s ${DEVICE_ID} shell "touch autopilot-deps.ok" |
2804 | fi |
2805 | # fetch the code |
2806 | -BASE_DIR="ubuntu-push/src/launchpad.net" |
2807 | +BASE_DIR="/home/phablet/ubuntu-push/src/launchpad.net" |
2808 | BRANCH_DIR="$BASE_DIR/ubuntu-push" |
2809 | BRANCH_OK=$(adb -s ${DEVICE_ID} shell "su - phablet bash -c '[ ! -d "${BRANCH_DIR}/tests/autopilot" ] && echo 1 || echo 0'") |
2810 | -if [[ "${BRANCH_OK:0:1}" == 1 ]] |
2811 | +if [[ "${BRANCH_OK:0:1}" == 1 ]] || [[ "${BRANCH_URL}" != "lp:ubuntu-push/automatic" ]] |
2812 | then |
2813 | + adb -s ${DEVICE_ID} shell "su - phablet bash -c 'rm -Rf ${BRANCH_DIR}'" |
2814 | echo "fetching code." |
2815 | adb -s ${DEVICE_ID} shell "su - phablet bash -c 'mkdir -p ${BASE_DIR}'" |
2816 | adb -s ${DEVICE_ID} shell "su - phablet bash -c 'bzr branch ${BRANCH_URL} ${BRANCH_DIR}'" |
2817 | fi |
2818 | -adb -s ${DEVICE_ID} shell "su - phablet bash -c 'sed -i 's/192.168.1.3/${PUSH_SERVER}/' ${BRANCH_DIR}/tests/autopilot/push_notifications/config/push.conf'" |
2819 | +adb -s ${DEVICE_ID} shell "su - phablet bash -c \"sed -i 's/addr =.*/addr = ${PUSH_SERVER}/' ${BRANCH_DIR}/tests/autopilot/push_notifications/config/push.conf\"" |
2820 | + |
2821 | +# copy the trivial-helper.sh as the heper for the messaging-app (used in the tests) |
2822 | +HELPER_OK=$(adb -s ${DEVICE_ID} shell "[ ! -f /usr/lib/ubuntu-push-client/legacy-helpers/messaging-app ] && echo 1 || echo 0") |
2823 | +if [[ "${HELPER_OK:0:1}" == 1 ]] |
2824 | +then |
2825 | + adb -s ${DEVICE_ID} shell "cp ${BRANCH_DIR}/scripts/trivial-helper.sh /usr/lib/ubuntu-push-client/legacy-helpers/messaging-app" |
2826 | +fi |
2827 | |
2828 | # change the local/dev server config, listen in all interfaces |
2829 | sed -i 's/127.0.0.1/0.0.0.0/g' ${ROOT_DIR}/sampleconfigs/dev.json |
2830 | # and start it |
2831 | cd ${ROOT_DIR}; make run-server-dev |
2832 | +# remove the trivial helper for the messaging-app |
2833 | +adb -s ${DEVICE_ID} shell "rm /usr/lib/ubuntu-push-client/legacy-helpers/messaging-app" |
2834 | |
2835 | === removed directory 'whoopsie' |
2836 | === removed file 'whoopsie/doc.go' |
2837 | --- whoopsie/doc.go 2014-02-21 16:17:28 +0000 |
2838 | +++ whoopsie/doc.go 1970-01-01 00:00:00 +0000 |
2839 | @@ -1,18 +0,0 @@ |
2840 | -/* |
2841 | - Copyright 2013-2014 Canonical Ltd. |
2842 | - |
2843 | - This program is free software: you can redistribute it and/or modify it |
2844 | - under the terms of the GNU General Public License version 3, as published |
2845 | - by the Free Software Foundation. |
2846 | - |
2847 | - This program is distributed in the hope that it will be useful, but |
2848 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
2849 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2850 | - PURPOSE. See the GNU General Public License for more details. |
2851 | - |
2852 | - You should have received a copy of the GNU General Public License along |
2853 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
2854 | -*/ |
2855 | - |
2856 | -// Package whoopsie wraps libwhoopsie. |
2857 | -package whoopsie |
2858 | |
2859 | === removed directory 'whoopsie/identifier' |
2860 | === removed file 'whoopsie/identifier/identifier.go' |
2861 | --- whoopsie/identifier/identifier.go 2014-07-03 10:51:36 +0000 |
2862 | +++ whoopsie/identifier/identifier.go 1970-01-01 00:00:00 +0000 |
2863 | @@ -1,78 +0,0 @@ |
2864 | -/* |
2865 | - Copyright 2013-2014 Canonical Ltd. |
2866 | - |
2867 | - This program is free software: you can redistribute it and/or modify it |
2868 | - under the terms of the GNU General Public License version 3, as published |
2869 | - by the Free Software Foundation. |
2870 | - |
2871 | - This program is distributed in the hope that it will be useful, but |
2872 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
2873 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2874 | - PURPOSE. See the GNU General Public License for more details. |
2875 | - |
2876 | - You should have received a copy of the GNU General Public License along |
2877 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
2878 | -*/ |
2879 | - |
2880 | -// Package identifier wraps libwhoopsie, and is thus the |
2881 | -// source of an anonymous and stable system id used by the Ubuntu |
2882 | -// error tracker and the Ubuntu push notifications service. |
2883 | -package identifier |
2884 | - |
2885 | -/* |
2886 | -#cgo pkg-config: libwhoopsie |
2887 | -#include <glib.h> |
2888 | -#include <libwhoopsie/identifier.h> |
2889 | -*/ |
2890 | -import "C" |
2891 | -import "unsafe" |
2892 | -import "errors" |
2893 | -import "time" |
2894 | - |
2895 | -// an Id knows how to generate itself, and how to stringify itself. |
2896 | -type Id interface { |
2897 | - Generate() error |
2898 | - String() string |
2899 | -} |
2900 | - |
2901 | -// Identifier is the default Id implementation. |
2902 | -type Identifier struct { |
2903 | - value string |
2904 | - generator func(**C.char, **C.GError) |
2905 | -} |
2906 | - |
2907 | -func generator(csp **C.char, errp **C.GError) { |
2908 | - C.whoopsie_identifier_generate(csp, errp) |
2909 | -} |
2910 | - |
2911 | -// New creates an Identifier, but does not call Generate() on it. |
2912 | -func New() Id { |
2913 | - return &Identifier{generator: generator} |
2914 | -} |
2915 | - |
2916 | -// Generate makes the Identifier create the identifier itself. |
2917 | -func (id *Identifier) Generate() error { |
2918 | - var gerr *C.GError |
2919 | - var cs *C.char |
2920 | - defer C.g_free((C.gpointer)(unsafe.Pointer(cs))) |
2921 | - |
2922 | - for i := 0; i < 200; i++ { |
2923 | - id.generator(&cs, &gerr) |
2924 | - |
2925 | - if gerr == nil && cs != nil { |
2926 | - goto Success |
2927 | - } |
2928 | - C.g_clear_error(&gerr) |
2929 | - time.Sleep(600 * time.Millisecond) |
2930 | - } |
2931 | - return errors.New("whoopsie_identifier_generate still bad after 2m; giving up") |
2932 | - |
2933 | -Success: |
2934 | - id.value = C.GoString(cs) |
2935 | - return nil |
2936 | -} |
2937 | - |
2938 | -// String returns the system identifier as a string. |
2939 | -func (id *Identifier) String() string { |
2940 | - return id.value |
2941 | -} |
2942 | |
2943 | === removed file 'whoopsie/identifier/identifier_test.go' |
2944 | --- whoopsie/identifier/identifier_test.go 2014-05-02 10:42:16 +0000 |
2945 | +++ whoopsie/identifier/identifier_test.go 1970-01-01 00:00:00 +0000 |
2946 | @@ -1,58 +0,0 @@ |
2947 | -/* |
2948 | - Copyright 2013-2014 Canonical Ltd. |
2949 | - |
2950 | - This program is free software: you can redistribute it and/or modify it |
2951 | - under the terms of the GNU General Public License version 3, as published |
2952 | - by the Free Software Foundation. |
2953 | - |
2954 | - This program is distributed in the hope that it will be useful, but |
2955 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
2956 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2957 | - PURPOSE. See the GNU General Public License for more details. |
2958 | - |
2959 | - You should have received a copy of the GNU General Public License along |
2960 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
2961 | -*/ |
2962 | - |
2963 | -package identifier |
2964 | - |
2965 | -import ( |
2966 | - . "launchpad.net/gocheck" |
2967 | - "testing" |
2968 | -) |
2969 | - |
2970 | -// hook up gocheck |
2971 | -func Test(t *testing.T) { TestingT(t) } |
2972 | - |
2973 | -type IdentifierSuite struct{} |
2974 | - |
2975 | -var _ = Suite(&IdentifierSuite{}) |
2976 | - |
2977 | -// TestGenerate checks that Generate() does not fail, and returns a |
2978 | -// 128-byte string. |
2979 | -func (s *IdentifierSuite) TestGenerate(c *C) { |
2980 | - id := New() |
2981 | - |
2982 | - c.Check(id.Generate(), Equals, nil) |
2983 | - c.Check(id.String(), HasLen, 128) |
2984 | -} |
2985 | - |
2986 | -// TestIdentifierInterface checks that Identifier implements Id. |
2987 | -func (s *IdentifierSuite) TestIdentifierInterface(c *C) { |
2988 | - _ = []Id{New()} |
2989 | -} |
2990 | - |
2991 | -// TestFailure checks that Identifier survives whoopsie shenanigans |
2992 | -func (s *IdentifierSuite) TestIdentifierSurvivesShenanigans(c *C) { |
2993 | - count := 0 |
2994 | - // using _Ctype* as a workaround for gocheck also having a C |
2995 | - gen := func(csp **_Ctype_char, errp **_Ctype_GError) { |
2996 | - count++ |
2997 | - if count > 3 { |
2998 | - generator(csp, errp) |
2999 | - } |
3000 | - } |
3001 | - id := &Identifier{generator: gen} |
3002 | - id.Generate() |
3003 | - c.Check(id.String(), HasLen, 128) |
3004 | -} |
3005 | |
3006 | === removed directory 'whoopsie/identifier/testing' |
3007 | === removed file 'whoopsie/identifier/testing/testing.go' |
3008 | --- whoopsie/identifier/testing/testing.go 2014-02-21 16:17:28 +0000 |
3009 | +++ whoopsie/identifier/testing/testing.go 1970-01-01 00:00:00 +0000 |
3010 | @@ -1,70 +0,0 @@ |
3011 | -/* |
3012 | - Copyright 2013-2014 Canonical Ltd. |
3013 | - |
3014 | - This program is free software: you can redistribute it and/or modify it |
3015 | - under the terms of the GNU General Public License version 3, as published |
3016 | - by the Free Software Foundation. |
3017 | - |
3018 | - This program is distributed in the hope that it will be useful, but |
3019 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
3020 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3021 | - PURPOSE. See the GNU General Public License for more details. |
3022 | - |
3023 | - You should have received a copy of the GNU General Public License along |
3024 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
3025 | -*/ |
3026 | - |
3027 | -// Package testing implements a couple of Ids that are useful |
3028 | -// for testing things that use whoopsie/identifier. |
3029 | -package testing |
3030 | - |
3031 | -import "errors" |
3032 | - |
3033 | -// SettableIdentifier is an Id that lets you set the value of the identifier. |
3034 | -// |
3035 | -// By default the identifier's value is "<Settable>", so it's visible |
3036 | -// if you're misusing it. |
3037 | -type SettableIdentifier struct { |
3038 | - value string |
3039 | -} |
3040 | - |
3041 | -// Settable is the constructor for SettableIdentifier. |
3042 | -func Settable() *SettableIdentifier { |
3043 | - return &SettableIdentifier{"<Settable>"} |
3044 | -} |
3045 | - |
3046 | -// Set is the method you use to set the identifier. |
3047 | -func (sid *SettableIdentifier) Set(value string) { |
3048 | - sid.value = value |
3049 | -} |
3050 | - |
3051 | -// Generate does nothing. |
3052 | -func (sid *SettableIdentifier) Generate() error { |
3053 | - return nil |
3054 | -} |
3055 | - |
3056 | -// String returns the string you set. |
3057 | -func (sid *SettableIdentifier) String() string { |
3058 | - return sid.value |
3059 | -} |
3060 | - |
3061 | -// FailingIdentifier is an Id that always fails to generate. |
3062 | -type FailingIdentifier struct{} |
3063 | - |
3064 | -// Failing is the constructor for FailingIdentifier. |
3065 | -func Failing() *FailingIdentifier { |
3066 | - return &FailingIdentifier{} |
3067 | -} |
3068 | - |
3069 | -// Generate fails with an ubiquitous error. |
3070 | -func (*FailingIdentifier) Generate() error { |
3071 | - return errors.New("lp0 on fire") |
3072 | -} |
3073 | - |
3074 | -// String returns "<Failing>". |
3075 | -// |
3076 | -// The purpose of this is to make it easy to spot if you're using it |
3077 | -// by accident. |
3078 | -func (*FailingIdentifier) String() string { |
3079 | - return "<Failing>" |
3080 | -} |
3081 | |
3082 | === removed file 'whoopsie/identifier/testing/testing_test.go' |
3083 | --- whoopsie/identifier/testing/testing_test.go 2014-02-21 16:17:28 +0000 |
3084 | +++ whoopsie/identifier/testing/testing_test.go 1970-01-01 00:00:00 +0000 |
3085 | @@ -1,70 +0,0 @@ |
3086 | -/* |
3087 | - Copyright 2013-2014 Canonical Ltd. |
3088 | - |
3089 | - This program is free software: you can redistribute it and/or modify it |
3090 | - under the terms of the GNU General Public License version 3, as published |
3091 | - by the Free Software Foundation. |
3092 | - |
3093 | - This program is distributed in the hope that it will be useful, but |
3094 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
3095 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3096 | - PURPOSE. See the GNU General Public License for more details. |
3097 | - |
3098 | - You should have received a copy of the GNU General Public License along |
3099 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
3100 | -*/ |
3101 | - |
3102 | -package testing |
3103 | - |
3104 | -import ( |
3105 | - identifier ".." |
3106 | - . "launchpad.net/gocheck" |
3107 | - "testing" |
3108 | -) |
3109 | - |
3110 | -// hook up gocheck |
3111 | -func Test(t *testing.T) { TestingT(t) } |
3112 | - |
3113 | -type IdentifierSuite struct{} |
3114 | - |
3115 | -var _ = Suite(&IdentifierSuite{}) |
3116 | - |
3117 | -// TestSettableDefaultValueVisible tests that SettableIdentifier's default |
3118 | -// value is notable. |
3119 | -func (s *IdentifierSuite) TestSettableDefaultValueVisible(c *C) { |
3120 | - id := Settable() |
3121 | - c.Check(id.String(), Equals, "<Settable>") |
3122 | -} |
3123 | - |
3124 | -// TestSettableSets tests that SettableIdentifier is settable. |
3125 | -func (s *IdentifierSuite) TestSettableSets(c *C) { |
3126 | - id := Settable() |
3127 | - id.Set("hello") |
3128 | - c.Check(id.String(), Equals, "hello") |
3129 | -} |
3130 | - |
3131 | -// TestSettableGenerateDoesNotFail tests that SettableIdentifier's Generate |
3132 | -// does not fail. |
3133 | -func (s *IdentifierSuite) TestSettableGenerateDoesNotFail(c *C) { |
3134 | - id := Settable() |
3135 | - c.Check(id.Generate(), Equals, nil) |
3136 | -} |
3137 | - |
3138 | -// TestFailingFails tests that FailingIdentifier fails. |
3139 | -func (s *IdentifierSuite) TestFailingFails(c *C) { |
3140 | - id := Failing() |
3141 | - c.Check(id.Generate(), Not(Equals), nil) |
3142 | -} |
3143 | - |
3144 | -// TestFailingStringNotEmpty tests that FailingIdentifier still has a |
3145 | -// non-empty string. |
3146 | -func (s *IdentifierSuite) TestFailingStringNotEmpty(c *C) { |
3147 | - id := Failing() |
3148 | - c.Check(id.String(), Equals, "<Failing>") |
3149 | -} |
3150 | - |
3151 | -// TestIdentifierInterface tests that FailingIdentifier and |
3152 | -// SettableIdentifier implement Id. |
3153 | -func (s *IdentifierSuite) TestIdentifierInterface(c *C) { |
3154 | - _ = []identifier.Id{Failing(), Settable()} |
3155 | -} |