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