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