Merge lp:~chipaca/ubuntu-push/the-push-automatic into lp:ubuntu-push
- the-push-automatic
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | John Lenton |
Approved revision: | no longer in the source branch. |
Merged at revision: | 109 |
Proposed branch: | lp:~chipaca/ubuntu-push/the-push-automatic |
Merge into: | lp:ubuntu-push |
Diff against target: |
9594 lines (+5991/-1010) 77 files modified
bus/emblemcounter/emblemcounter.go (+4/-9) bus/emblemcounter/emblemcounter_test.go (+15/-8) bus/haptic/haptic.go (+3/-2) bus/haptic/haptic_test.go (+11/-7) bus/notifications/raw.go (+60/-22) bus/notifications/raw_test.go (+143/-14) bus/windowstack/windowstack.go (+69/-0) bus/windowstack/windowstack_test.go (+93/-0) click/cappinfo/cappinfo.go (+8/-7) click/cclick/cclick.go (+0/-6) click/click.go (+177/-20) click/click_test.go (+206/-35) click/testing/helpers.go (+31/-0) client/client.go (+96/-115) client/client_test.go (+233/-243) client/service/common.go (+18/-12) client/service/common_test.go (+9/-6) client/service/mbox.go (+80/-0) client/service/mbox_test.go (+132/-0) client/service/postal.go (+199/-72) client/service/postal_test.go (+492/-153) client/service/service.go (+15/-11) client/service/service_test.go (+37/-20) client/session/session.go (+14/-5) client/session/session_test.go (+54/-20) debian/changelog (+52/-0) debian/control (+21/-0) debian/exec-tool (+2/-0) debian/push-helper.hook (+3/-0) debian/rules (+2/-2) debian/ubuntu-push-autopilot.install (+1/-0) debian/ubuntu-push-client.install (+3/-0) debian/ubuntu-push-dev-server.install (+2/-0) launch_helper/cual/cual.go (+97/-0) launch_helper/cual/cual_c.go (+57/-0) launch_helper/helper.go (+36/-23) launch_helper/helper_output.go (+22/-2) launch_helper/helper_test.go (+47/-13) launch_helper/iface.go (+25/-0) launch_helper/kindpool.go (+283/-0) launch_helper/kindpool_test.go (+440/-0) launch_helper/legacy/legacy.go (+83/-0) launch_helper/legacy/legacy_test.go (+112/-0) messaging/cmessaging/cmessaging.go (+24/-9) messaging/cmessaging/cmessaging_c.go (+8/-6) messaging/messaging.go (+43/-9) messaging/messaging_test.go (+47/-8) sampleconfigs/dev.json (+1/-0) scripts/register (+13/-3) scripts/slow-helper.sh (+2/-0) scripts/software-updates-helper.py (+45/-0) scripts/trivial-helper.sh (+3/-0) server/acceptance/cmd/acceptanceclient.go (+29/-7) server/acceptance/suites/helpers.go (+6/-3) server/acceptance/suites/unicast.go (+83/-2) server/api/handlers.go (+125/-15) server/api/handlers_test.go (+318/-34) server/broker/testsuite/suite.go (+2/-2) server/dev/server.go (+19/-3) server/store/inmemory.go (+81/-28) server/store/inmemory_test.go (+248/-32) server/store/store.go (+66/-2) sounds/sounds.go (+4/-9) sounds/sounds_test.go (+16/-11) tests/autopilot/push_notifications/README (+74/-0) tests/autopilot/push_notifications/__init__.py (+20/-0) tests/autopilot/push_notifications/config/__init__.py (+39/-0) tests/autopilot/push_notifications/config/push.conf (+6/-0) tests/autopilot/push_notifications/config/testing.cert (+10/-0) tests/autopilot/push_notifications/data.py (+98/-0) tests/autopilot/push_notifications/helpers/__init__.py (+18/-0) tests/autopilot/push_notifications/helpers/push_notifications_helper.py (+317/-0) tests/autopilot/push_notifications/tests/__init__.py (+311/-0) tests/autopilot/push_notifications/tests/test_broadcast_notifications.py (+141/-0) tests/autopilot/push_notifications/tests/test_unicast_notifications.py (+186/-0) tests/autopilot/run.sh (+30/-0) tests/autopilot/setup.sh (+71/-0) |
To merge this branch: | bzr merge lp:~chipaca/ubuntu-push/the-push-automatic |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Push Hackers | Pending | ||
Review via email: mp+227420@code.launchpad.net |
Commit message
[Samuele Pedroni]
* Check in the api whether an app has pushed too many notifications.
* Return payload of most recent notification in too many pending
notifications API error.
* Introduce clear_pending flag to clean everything pending for an app.
* Refactor and cleanup.
* Introduce replace_tag support in store and api, with acceptance test.
* Teach a couple of trick to cmd/acceptancec
wait for event matching given regexp pattern.
* Limit unicast data payload to 2K.
* Payload should be json (fixes message needing to be base64-encoded in
helper reply)
* Implement limited mboxes
* Refactor and cleanup of things done in haste by Chipaca.
[Richard Huddie]
* autopilot test framework and basic coverage of broadcast notifications.
[Guillermo Gonzalez]
* Add scripts to simplify setup/run of the autopilot tests in the
device/emulator and include basic unicast tests.
* Add autopilot test for notification using the emblem counter.
* Adds scenarios to the autopilot tests for legacy and click (without
version) applications.
* Broadcast via the helpers route.
* Basic support for actions (only default action) in the persistent
notifications.
* Change PostBroadcast to send the broadcast message to the software
updates helper.
[John R. Lenton]
* Detangle client and postal.
* Introduce PostalService interface, and change the client tests to use
that as much as reasonable.
* Async invocation of helpers.
* Give click.Click knowledge of helpers.
* Write ual-based helper launcher.
* Switch to the ual-based helper launcher unless the environment
variable UBUNTU_
* Threw together an implementation of helpers for legacy applications.
* Hacked up an initial software updates helper, to be handed off to the
appropriate team shortly.
[Roberto Alsina]
* Wrap the (dbus) WindowStack API and add endpoint to the Postal service
to support inhibition of notifications for focused apps.
* Inhibit notifications for focused apps
Description of the change
Merged from automatic.
- 109. By Richard Huddie
-
[Samuele Pedroni]
* Check in the api whether an app has pushed too many notifications.
* Return payload of most recent notification in too many pending
notifications API error.
* Introduce clear_pending flag to clean everything pending for an app.
* Refactor and cleanup.
* Introduce replace_tag support in store and api, with acceptance test.
* Teach a couple of trick to cmd/acceptanceclient: exit on run timeout,
wait for event matching given regexp pattern.
* Limit unicast data payload to 2K.
* Payload should be json (fixes message needing to be base64-encoded in
helper reply)
* Implement limited mboxes
* Refactor and cleanup of things done in haste by Chipaca.[Richard Huddie]
* autopilot test framework and basic coverage of broadcast notifications.[Guillermo Gonzalez]
* Add scripts to simplify setup/run of the autopilot tests in the
device/emulator and include basic unicast tests.
* Add autopilot test for notification using the emblem counter.
* Adds scenarios to the autopilot tests for legacy and click (without
version) applications.
* Broadcast via the helpers route.
* Basic support for actions (only default action) in the persistent
notifications.
* Change PostBroadcast to send the broadcast message to the software
updates helper.[John R. Lenton]
* Detangle client and postal.
* Introduce PostalService interface, and change the client tests to use
that as much as reasonable.
* Async invocation of helpers.
* Give click.Click knowledge of helpers.
* Write ual-based helper launcher.
* Switch to the ual-based helper launcher unless the environment
variable UBUNTU_PUSH_USE_ TRIVIAL_ HELPER is set.
* Threw together an implementation of helpers for legacy applications.
* Hacked up an initial software updates helper, to be handed off to the
appropriate team shortly.[Roberto Alsina]
* Wrap the (dbus) WindowStack API and add endpoint to the Postal service
to support inhibition of notifications for focused apps.
* Inhibit notifications for focused apps
Preview Diff
1 | === modified file 'bus/emblemcounter/emblemcounter.go' |
2 | --- bus/emblemcounter/emblemcounter.go 2014-07-06 19:57:47 +0000 |
3 | +++ bus/emblemcounter/emblemcounter.go 2014-07-21 14:14:04 +0000 |
4 | @@ -49,20 +49,15 @@ |
5 | |
6 | // Look for an EmblemCounter section in a Notification and, if |
7 | // present, presents it to the user. |
8 | -func (ctr *EmblemCounter) Present(appId string, notificationId string, notification *launch_helper.Notification) { |
9 | +func (ctr *EmblemCounter) Present(app *click.AppId, nid string, notification *launch_helper.Notification) { |
10 | if notification == nil || notification.EmblemCounter == nil { |
11 | - ctr.log.Debugf("no notification or no EmblemCounter in the notification; doing nothing: %#v", notification) |
12 | - return |
13 | - } |
14 | - parsed, err := click.ParseAppId(appId) |
15 | - if err != nil { |
16 | - ctr.log.Debugf("no appId in %#v", appId) |
17 | + ctr.log.Debugf("[%s] no notification or no EmblemCounter in the notification; doing nothing: %#v", nid, notification) |
18 | return |
19 | } |
20 | ec := notification.EmblemCounter |
21 | - ctr.log.Debugf("setting emblem counter for %s to %d (visible: %t)", appId, ec.Count, ec.Visible) |
22 | + ctr.log.Debugf("[%s] setting emblem counter for %s to %d (visible: %t)", nid, app.Base(), ec.Count, ec.Visible) |
23 | |
24 | - quoted := string(nih.Quote([]byte(parsed.Application))) |
25 | + quoted := string(nih.Quote([]byte(app.Base()))) |
26 | |
27 | ctr.bus.SetProperty("count", "/"+quoted, dbus.Variant{ec.Count}) |
28 | ctr.bus.SetProperty("countVisible", "/"+quoted, dbus.Variant{ec.Visible}) |
29 | |
30 | === modified file 'bus/emblemcounter/emblemcounter_test.go' |
31 | --- bus/emblemcounter/emblemcounter_test.go 2014-07-06 19:57:47 +0000 |
32 | +++ bus/emblemcounter/emblemcounter_test.go 2014-07-21 14:14:04 +0000 |
33 | @@ -23,7 +23,10 @@ |
34 | . "launchpad.net/gocheck" |
35 | |
36 | testibus "launchpad.net/ubuntu-push/bus/testing" |
37 | + "launchpad.net/ubuntu-push/click" |
38 | + clickhelp "launchpad.net/ubuntu-push/click/testing" |
39 | "launchpad.net/ubuntu-push/launch_helper" |
40 | + "launchpad.net/ubuntu-push/nih" |
41 | helpers "launchpad.net/ubuntu-push/testing" |
42 | "launchpad.net/ubuntu-push/testing/condition" |
43 | ) |
44 | @@ -32,48 +35,52 @@ |
45 | |
46 | type ecSuite struct { |
47 | log *helpers.TestLogger |
48 | + app *click.AppId |
49 | } |
50 | |
51 | var _ = Suite(&ecSuite{}) |
52 | |
53 | func (ecs *ecSuite) SetUpTest(c *C) { |
54 | ecs.log = helpers.NewTestLogger(c, "debug") |
55 | + ecs.app = clickhelp.MustParseAppId("com.example.test_test-app_0") |
56 | } |
57 | |
58 | // checks that Present() actually calls SetProperty on the launcher |
59 | func (ecs *ecSuite) TestPresentPresents(c *C) { |
60 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
61 | + quoted := string(nih.Quote([]byte(ecs.app.Base()))) |
62 | |
63 | ec := New(endp, ecs.log) |
64 | notif := launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{Count: 42, Visible: true}} |
65 | - ec.Present("com.example.test_test-app", "nid", ¬if) |
66 | + ec.Present(ecs.app, "nid", ¬if) |
67 | callArgs := testibus.GetCallArgs(endp) |
68 | c.Assert(callArgs, HasLen, 2) |
69 | c.Check(callArgs[0].Member, Equals, "::SetProperty") |
70 | c.Check(callArgs[1].Member, Equals, "::SetProperty") |
71 | - c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", "/test_2dapp", dbus.Variant{Value: int32(42)}}) |
72 | - c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", "/test_2dapp", dbus.Variant{Value: true}}) |
73 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", "/" + quoted, dbus.Variant{Value: int32(42)}}) |
74 | + c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", "/" + quoted, dbus.Variant{Value: true}}) |
75 | } |
76 | |
77 | // check that Present() doesn't call SetProperty if no EmblemCounter in the Notification |
78 | func (ecs *ecSuite) TestSkipIfMissing(c *C) { |
79 | + quoted := string(nih.Quote([]byte(ecs.app.Base()))) |
80 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
81 | ec := New(endp, ecs.log) |
82 | |
83 | // nothing happens if nil Notification |
84 | - ec.Present("com.example.test_test-app", "nid", nil) |
85 | + ec.Present(ecs.app, "nid", nil) |
86 | c.Assert(testibus.GetCallArgs(endp), HasLen, 0) |
87 | |
88 | // nothing happens if no EmblemCounter in Notification |
89 | - ec.Present("com.example.test_test-app", "nid", &launch_helper.Notification{}) |
90 | + ec.Present(ecs.app, "nid", &launch_helper.Notification{}) |
91 | c.Assert(testibus.GetCallArgs(endp), HasLen, 0) |
92 | |
93 | // but an empty EmblemCounter is acted on |
94 | - ec.Present("com.example.test_test-app", "nid", &launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{}}) |
95 | + ec.Present(ecs.app, "nid", &launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{}}) |
96 | callArgs := testibus.GetCallArgs(endp) |
97 | c.Assert(callArgs, HasLen, 2) |
98 | c.Check(callArgs[0].Member, Equals, "::SetProperty") |
99 | c.Check(callArgs[1].Member, Equals, "::SetProperty") |
100 | - c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", "/test_2dapp", dbus.Variant{Value: int32(0)}}) |
101 | - c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", "/test_2dapp", dbus.Variant{Value: false}}) |
102 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", "/" + quoted, dbus.Variant{Value: int32(0)}}) |
103 | + c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", "/" + quoted, dbus.Variant{Value: false}}) |
104 | } |
105 | |
106 | === modified file 'bus/haptic/haptic.go' |
107 | --- bus/haptic/haptic.go 2014-07-06 20:11:07 +0000 |
108 | +++ bus/haptic/haptic.go 2014-07-21 14:14:04 +0000 |
109 | @@ -20,6 +20,7 @@ |
110 | |
111 | import ( |
112 | "launchpad.net/ubuntu-push/bus" |
113 | + "launchpad.net/ubuntu-push/click" |
114 | "launchpad.net/ubuntu-push/launch_helper" |
115 | "launchpad.net/ubuntu-push/logger" |
116 | ) |
117 | @@ -43,7 +44,7 @@ |
118 | } |
119 | |
120 | // Present presents the notification via a vibrate pattern |
121 | -func (haptic *Haptic) Present(_, _ string, notification *launch_helper.Notification) bool { |
122 | +func (haptic *Haptic) Present(_ *click.AppId, _ string, notification *launch_helper.Notification) bool { |
123 | if notification == nil || notification.Vibrate == nil { |
124 | haptic.log.Debugf("no notification or no Vibrate in the notification; doing nothing: %#v", notification) |
125 | return false |
126 | @@ -63,7 +64,7 @@ |
127 | haptic.log.Debugf("vibrating %d times to the tune of %v", repeat, pattern) |
128 | err := haptic.bus.Call("VibratePattern", bus.Args(pattern, repeat)) |
129 | if err != nil { |
130 | - haptic.log.Debugf("VibratePattern call returned %v", err) |
131 | + haptic.log.Errorf("VibratePattern call returned %v", err) |
132 | return false |
133 | } |
134 | return true |
135 | |
136 | === modified file 'bus/haptic/haptic_test.go' |
137 | --- bus/haptic/haptic_test.go 2014-07-06 13:53:42 +0000 |
138 | +++ bus/haptic/haptic_test.go 2014-07-21 14:14:04 +0000 |
139 | @@ -22,6 +22,8 @@ |
140 | . "launchpad.net/gocheck" |
141 | |
142 | testibus "launchpad.net/ubuntu-push/bus/testing" |
143 | + "launchpad.net/ubuntu-push/click" |
144 | + clickhelp "launchpad.net/ubuntu-push/click/testing" |
145 | "launchpad.net/ubuntu-push/launch_helper" |
146 | helpers "launchpad.net/ubuntu-push/testing" |
147 | "launchpad.net/ubuntu-push/testing/condition" |
148 | @@ -31,12 +33,14 @@ |
149 | |
150 | type hapticSuite struct { |
151 | log *helpers.TestLogger |
152 | + app *click.AppId |
153 | } |
154 | |
155 | var _ = Suite(&hapticSuite{}) |
156 | |
157 | func (hs *hapticSuite) SetUpTest(c *C) { |
158 | hs.log = helpers.NewTestLogger(c, "debug") |
159 | + hs.app = clickhelp.MustParseAppId("com.example.test_test-app_0") |
160 | } |
161 | |
162 | // checks that Present() actually calls VibratePattern |
163 | @@ -45,7 +49,7 @@ |
164 | |
165 | ec := New(endp, hs.log) |
166 | notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2}} |
167 | - c.Check(ec.Present("com.example.test_test-app", "nid", ¬if), Equals, true) |
168 | + c.Check(ec.Present(hs.app, "nid", ¬if), Equals, true) |
169 | callArgs := testibus.GetCallArgs(endp) |
170 | c.Assert(callArgs, HasLen, 1) |
171 | c.Check(callArgs[0].Member, Equals, "VibratePattern") |
172 | @@ -59,7 +63,7 @@ |
173 | ec := New(endp, hs.log) |
174 | // note: no Repeat: |
175 | notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Pattern: []uint32{200, 100}}} |
176 | - c.Check(ec.Present("com.example.test_test-app", "nid", ¬if), Equals, true) |
177 | + c.Check(ec.Present(hs.app, "nid", ¬if), Equals, true) |
178 | callArgs := testibus.GetCallArgs(endp) |
179 | c.Assert(callArgs, HasLen, 1) |
180 | c.Check(callArgs[0].Member, Equals, "VibratePattern") |
181 | @@ -74,7 +78,7 @@ |
182 | ec := New(endp, hs.log) |
183 | // note: no Repeat, no Pattern, just Duration: |
184 | notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Duration: 200}} |
185 | - c.Check(ec.Present("com.example.test_test-app", "nid", ¬if), Equals, true) |
186 | + c.Check(ec.Present(hs.app, "nid", ¬if), Equals, true) |
187 | callArgs := testibus.GetCallArgs(endp) |
188 | c.Assert(callArgs, HasLen, 1) |
189 | c.Check(callArgs[0].Member, Equals, "VibratePattern") |
190 | @@ -89,7 +93,7 @@ |
191 | ec := New(endp, hs.log) |
192 | // note: Duration given, as well as Pattern; Repeat given as 0: |
193 | notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Duration: 200, Pattern: []uint32{500}, Repeat: 0}} |
194 | - c.Check(ec.Present("com.example.test_test-app", "nid", ¬if), Equals, true) |
195 | + c.Check(ec.Present(hs.app, "nid", ¬if), Equals, true) |
196 | callArgs := testibus.GetCallArgs(endp) |
197 | c.Assert(callArgs, HasLen, 1) |
198 | c.Check(callArgs[0].Member, Equals, "VibratePattern") |
199 | @@ -103,9 +107,9 @@ |
200 | |
201 | ec := New(endp, hs.log) |
202 | // no notification at all |
203 | - c.Check(ec.Present("", "", nil), Equals, false) |
204 | + c.Check(ec.Present(hs.app, "", nil), Equals, false) |
205 | // no Vibration in the notificaton |
206 | - c.Check(ec.Present("", "", &launch_helper.Notification{}), Equals, false) |
207 | + c.Check(ec.Present(hs.app, "", &launch_helper.Notification{}), Equals, false) |
208 | // empty Vibration |
209 | - c.Check(ec.Present("", "", &launch_helper.Notification{Vibrate: &launch_helper.Vibration{}}), Equals, false) |
210 | + c.Check(ec.Present(hs.app, "", &launch_helper.Notification{Vibrate: &launch_helper.Vibration{}}), Equals, false) |
211 | } |
212 | |
213 | === modified file 'bus/notifications/raw.go' |
214 | --- bus/notifications/raw.go 2014-07-01 11:55:30 +0000 |
215 | +++ bus/notifications/raw.go 2014-07-21 14:14:04 +0000 |
216 | @@ -22,12 +22,13 @@ |
217 | // this is the lower-level api |
218 | |
219 | import ( |
220 | + "encoding/json" |
221 | "errors" |
222 | - "fmt" |
223 | |
224 | "launchpad.net/go-dbus/v1" |
225 | + |
226 | "launchpad.net/ubuntu-push/bus" |
227 | - c_helper "launchpad.net/ubuntu-push/bus/notifications/app_helper" |
228 | + "launchpad.net/ubuntu-push/click" |
229 | "launchpad.net/ubuntu-push/launch_helper" |
230 | "launchpad.net/ubuntu-push/logger" |
231 | ) |
232 | @@ -44,9 +45,12 @@ |
233 | */ |
234 | |
235 | // convenience type for the (uint32, string) ActionInvoked signal data |
236 | -type RawActionReply struct { |
237 | - NotificationId uint32 |
238 | - ActionId string |
239 | +type RawAction struct { |
240 | + App *click.AppId `json:"app,omitempty"` |
241 | + Action string `json:"act,omitempty"` |
242 | + ActionId int `json:"aid,omitempty"` |
243 | + Nid string `json:"nid,omitempty"` |
244 | + RawId uint32 `json:"-"` |
245 | } |
246 | |
247 | // a raw notification provides a low-level interface to the f.d.o. dbus |
248 | @@ -87,11 +91,32 @@ |
249 | |
250 | // WatchActions listens for ActionInvoked signals from the notification daemon |
251 | // and sends them over the channel provided |
252 | -func (raw *RawNotifications) WatchActions() (<-chan RawActionReply, error) { |
253 | - ch := make(chan RawActionReply) |
254 | +func (raw *RawNotifications) WatchActions() (<-chan *RawAction, error) { |
255 | + ch := make(chan *RawAction) |
256 | err := raw.bus.WatchSignal("ActionInvoked", |
257 | func(ns ...interface{}) { |
258 | - ch <- RawActionReply{ns[0].(uint32), ns[1].(string)} |
259 | + if len(ns) != 2 { |
260 | + raw.log.Debugf("ActionInvoked delivered %d things instead of 2", len(ns)) |
261 | + return |
262 | + } |
263 | + rawId, ok := ns[0].(uint32) |
264 | + if !ok { |
265 | + raw.log.Debugf("ActionInvoked's 1st param not a uint32") |
266 | + return |
267 | + } |
268 | + encodedAction, ok := ns[1].(string) |
269 | + if !ok { |
270 | + raw.log.Debugf("ActionInvoked's 2nd param not a string") |
271 | + return |
272 | + } |
273 | + var action *RawAction |
274 | + err := json.Unmarshal([]byte(encodedAction), &action) |
275 | + if err != nil { |
276 | + raw.log.Debugf("ActionInvoked's 2nd param not a json-encoded RawAction") |
277 | + return |
278 | + } |
279 | + action.RawId = rawId |
280 | + ch <- action |
281 | }, func() { close(ch) }) |
282 | if err != nil { |
283 | raw.log.Debugf("Failed to set up the watch: %s", err) |
284 | @@ -100,35 +125,48 @@ |
285 | return ch, nil |
286 | } |
287 | |
288 | -// ShowCard displays a given card. |
289 | +// Present displays a given card. |
290 | // |
291 | +// If card.Actions is empty it's a plain, noninteractive bubble notification. |
292 | // If card.Actions has 1 action, it's an interactive notification. |
293 | -// If card.Actions has 2 or more actions, it will show as a snap decision. |
294 | -// |
295 | -// WatchActions will receive something like this in the ActionId field: |
296 | -// appId::notificationId::action.Id |
297 | -func (raw *RawNotifications) Present(appId string, notificationId string, notification *launch_helper.Notification) (uint32, error) { |
298 | +// If card.Actions has 2 actions, it will show as a snap decision. |
299 | +// If it has more actions, who knows (good luck). |
300 | +func (raw *RawNotifications) Present(app *click.AppId, nid string, notification *launch_helper.Notification) (uint32, error) { |
301 | if notification == nil || notification.Card == nil || !notification.Card.Popup || notification.Card.Summary == "" { |
302 | - raw.log.Debugf("skipping notification: nil, or nil card, or not popup, or no summary: %#v", notification) |
303 | + raw.log.Debugf("[%s] skipping notification: nil, or nil card, or not popup, or no summary: %#v", nid, notification) |
304 | return 0, nil |
305 | } |
306 | |
307 | card := notification.Card |
308 | |
309 | - app_icon := c_helper.AppIconFromId(appId) |
310 | hints := make(map[string]*dbus.Variant) |
311 | - hints["x-canonical-secondary-icon"] = &dbus.Variant{app_icon} |
312 | + hints["x-canonical-secondary-icon"] = &dbus.Variant{app.Icon()} |
313 | |
314 | + appId := app.Original() |
315 | actions := make([]string, 2*len(card.Actions)) |
316 | for i, action := range card.Actions { |
317 | - actions[2*i] = fmt.Sprintf("%s::%s::%d", appId, notificationId, i) |
318 | + act, err := json.Marshal(&RawAction{ |
319 | + App: app, |
320 | + Nid: nid, |
321 | + ActionId: i, |
322 | + Action: action, |
323 | + }) |
324 | + if err != nil { |
325 | + return 0, err |
326 | + } |
327 | + actions[2*i] = string(act) |
328 | actions[2*i+1] = action |
329 | } |
330 | - switch len(actions) { |
331 | + switch len(card.Actions) { |
332 | + case 0: |
333 | + // nothing |
334 | + case 1: |
335 | + hints["x-canonical-switch-to-application"] = &dbus.Variant{"true"} |
336 | case 2: |
337 | - hints["x-canonical-switch-to-application"] = &dbus.Variant{true} |
338 | - case 4: |
339 | - hints["x-canonical-snap-decisions"] = &dbus.Variant{true} |
340 | + hints["x-canonical-snap-decisions"] = &dbus.Variant{"true"} |
341 | + hints["x-canonical-private-button-tint"] = &dbus.Variant{"true"} |
342 | + default: |
343 | + raw.log.Debugf("[%s] don't know what to do with %d actions; no hints set", nid, len(card.Actions)) |
344 | } |
345 | return raw.Notify(appId, 0, card.Icon, card.Summary, card.Body, actions, hints, 30*1000) |
346 | } |
347 | |
348 | === modified file 'bus/notifications/raw_test.go' |
349 | --- bus/notifications/raw_test.go 2014-07-01 00:51:59 +0000 |
350 | +++ bus/notifications/raw_test.go 2014-07-21 14:14:04 +0000 |
351 | @@ -20,25 +20,33 @@ |
352 | package notifications |
353 | |
354 | import ( |
355 | + "encoding/json" |
356 | + "testing" |
357 | + "time" |
358 | + |
359 | + "launchpad.net/go-dbus/v1" |
360 | . "launchpad.net/gocheck" |
361 | + |
362 | + "launchpad.net/ubuntu-push/bus" |
363 | testibus "launchpad.net/ubuntu-push/bus/testing" |
364 | + "launchpad.net/ubuntu-push/click" |
365 | + clickhelp "launchpad.net/ubuntu-push/click/testing" |
366 | "launchpad.net/ubuntu-push/launch_helper" |
367 | - "launchpad.net/ubuntu-push/logger" |
368 | helpers "launchpad.net/ubuntu-push/testing" |
369 | "launchpad.net/ubuntu-push/testing/condition" |
370 | - "testing" |
371 | - "time" |
372 | ) |
373 | |
374 | // hook up gocheck |
375 | func TestRaw(t *testing.T) { TestingT(t) } |
376 | |
377 | type RawSuite struct { |
378 | - log logger.Logger |
379 | + log *helpers.TestLogger |
380 | + app *click.AppId |
381 | } |
382 | |
383 | func (s *RawSuite) SetUpTest(c *C) { |
384 | s.log = helpers.NewTestLogger(c, "debug") |
385 | + s.app = clickhelp.MustParseAppId("com.example.test_test-app_0") |
386 | } |
387 | |
388 | var _ = Suite(&RawSuite{}) |
389 | @@ -72,16 +80,25 @@ |
390 | } |
391 | |
392 | func (s *RawSuite) TestWatchActions(c *C) { |
393 | + act := &RawAction{ |
394 | + App: clickhelp.MustParseAppId("_foo"), |
395 | + Nid: "notif-id", |
396 | + ActionId: 1, |
397 | + Action: "hello", |
398 | + RawId: 0, |
399 | + } |
400 | + jAct, err := json.Marshal(act) |
401 | + c.Assert(err, IsNil) |
402 | endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), |
403 | - []interface{}{uint32(1), "hello"}) |
404 | + []interface{}{uint32(1), string(jAct)}) |
405 | raw := Raw(endp, s.log) |
406 | ch, err := raw.WatchActions() |
407 | c.Assert(err, IsNil) |
408 | // check we get the right action reply |
409 | + act.RawId = 1 // checking the RawId is overwritten with the one in the ActionInvoked |
410 | select { |
411 | case p := <-ch: |
412 | - c.Check(p.NotificationId, Equals, uint32(1)) |
413 | - c.Check(p.ActionId, Equals, "hello") |
414 | + c.Check(p, DeepEquals, act) |
415 | case <-time.NewTimer(time.Second / 10).C: |
416 | c.Error("timed out?") |
417 | } |
418 | @@ -90,6 +107,49 @@ |
419 | c.Check(ok, Equals, false) |
420 | } |
421 | |
422 | +type tst struct { |
423 | + errstr string |
424 | + endp bus.Endpoint |
425 | + works bool |
426 | +} |
427 | + |
428 | +func (s *RawSuite) TestWatchActionsToleratesDBusWeirdness(c *C) { |
429 | + X := func(errstr string, args ...interface{}) tst { |
430 | + endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), args) |
431 | + // stop the endpoint from closing the channel: |
432 | + testibus.SetWatchTicker(endp, make(chan bool)) |
433 | + return tst{errstr, endp, errstr == ""} |
434 | + } |
435 | + |
436 | + ts := []tst{ |
437 | + X("delivered 0 things instead of 2"), |
438 | + X("delivered 1 things instead of 2", 2), |
439 | + X("1st param not a uint32", 1, "foo"), |
440 | + X("2nd param not a string", uint32(1), nil), |
441 | + X("2nd param not a json-encoded RawAction", uint32(1), ``), |
442 | + X("", uint32(1), `{}`), |
443 | + } |
444 | + |
445 | + for i, t := range ts { |
446 | + raw := Raw(t.endp, s.log) |
447 | + ch, err := raw.WatchActions() |
448 | + c.Assert(err, IsNil) |
449 | + select { |
450 | + case p := <-ch: |
451 | + if !t.works { |
452 | + c.Errorf("got something on the channel! %#v (iter: %d)", p, i) |
453 | + } |
454 | + case <-time.After(time.Second / 10): |
455 | + if t.works { |
456 | + c.Errorf("failed to get something on the channel (iter: %d)", i) |
457 | + } |
458 | + } |
459 | + c.Check(s.log.Captured(), Matches, `(?ms).*`+t.errstr+`.*`) |
460 | + s.log.ResetCapture() |
461 | + } |
462 | + |
463 | +} |
464 | + |
465 | func (s *RawSuite) TestWatchActionsFails(c *C) { |
466 | endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) |
467 | raw := Raw(endp, s.log) |
468 | @@ -100,15 +160,86 @@ |
469 | func (s *RawSuite) TestPresentNotifies(c *C) { |
470 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
471 | raw := Raw(endp, s.log) |
472 | - nid, err := raw.Present("firefox.desktop", "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true}}) |
473 | + nid, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true}}) |
474 | c.Check(err, IsNil) |
475 | c.Check(nid, Equals, uint32(1)) |
476 | } |
477 | |
478 | +func (s *RawSuite) TestPresentOneAction(c *C) { |
479 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
480 | + raw := Raw(endp, s.log) |
481 | + _, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true, Actions: []string{"Yes"}}}) |
482 | + c.Check(err, IsNil) |
483 | + callArgs := testibus.GetCallArgs(endp) |
484 | + c.Assert(callArgs, HasLen, 1) |
485 | + c.Assert(callArgs[0].Member, Equals, "Notify") |
486 | + c.Assert(len(callArgs[0].Args), Equals, 8) |
487 | + actions, ok := callArgs[0].Args[5].([]string) |
488 | + c.Assert(ok, Equals, true) |
489 | + c.Assert(actions, HasLen, 2) |
490 | + c.Check(actions[0], Equals, `{"app":"com.example.test_test-app_0","act":"Yes","nid":"notifId"}`) |
491 | + c.Check(actions[1], Equals, "Yes") |
492 | + hints, ok := callArgs[0].Args[6].(map[string]*dbus.Variant) |
493 | + c.Assert(ok, Equals, true) |
494 | + // with one action, there should be 2 hints set: |
495 | + c.Assert(hints, HasLen, 2) |
496 | + c.Check(hints["x-canonical-switch-to-application"], NotNil) |
497 | + c.Check(hints["x-canonical-secondary-icon"], NotNil) |
498 | + c.Check(hints["x-canonical-snap-decisions"], IsNil) |
499 | + c.Check(hints["x-canonical-private-button-tint"], IsNil) |
500 | +} |
501 | + |
502 | +func (s *RawSuite) TestPresentTwoActions(c *C) { |
503 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
504 | + raw := Raw(endp, s.log) |
505 | + _, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true, Actions: []string{"Yes", "No"}}}) |
506 | + c.Check(err, IsNil) |
507 | + callArgs := testibus.GetCallArgs(endp) |
508 | + c.Assert(callArgs, HasLen, 1) |
509 | + c.Assert(callArgs[0].Member, Equals, "Notify") |
510 | + c.Assert(len(callArgs[0].Args), Equals, 8) |
511 | + actions, ok := callArgs[0].Args[5].([]string) |
512 | + c.Assert(ok, Equals, true) |
513 | + c.Assert(actions, HasLen, 4) |
514 | + c.Check(actions[0], Equals, `{"app":"com.example.test_test-app_0","act":"Yes","nid":"notifId"}`) |
515 | + c.Check(actions[1], Equals, "Yes") |
516 | + c.Check(actions[2], Equals, `{"app":"com.example.test_test-app_0","act":"No","aid":1,"nid":"notifId"}`) |
517 | + c.Check(actions[3], Equals, "No") |
518 | + hints, ok := callArgs[0].Args[6].(map[string]*dbus.Variant) |
519 | + c.Assert(ok, Equals, true) |
520 | + // with two actions, there should be 3 hints set: |
521 | + c.Assert(hints, HasLen, 3) |
522 | + c.Check(hints["x-canonical-switch-to-application"], IsNil) |
523 | + c.Check(hints["x-canonical-secondary-icon"], NotNil) |
524 | + c.Check(hints["x-canonical-snap-decisions"], NotNil) |
525 | + c.Check(hints["x-canonical-private-button-tint"], NotNil) |
526 | + c.Check(hints["x-canonical-non-shaped-icon"], IsNil) // checking just in case |
527 | +} |
528 | + |
529 | +func (s *RawSuite) TestPresentThreeActions(c *C) { |
530 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
531 | + raw := Raw(endp, s.log) |
532 | + _, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true, Actions: []string{"Yes", "No", "What"}}}) |
533 | + c.Check(err, IsNil) |
534 | + callArgs := testibus.GetCallArgs(endp) |
535 | + c.Assert(callArgs, HasLen, 1) |
536 | + c.Assert(callArgs[0].Member, Equals, "Notify") |
537 | + c.Assert(len(callArgs[0].Args), Equals, 8) |
538 | + actions, ok := callArgs[0].Args[5].([]string) |
539 | + c.Assert(ok, Equals, true) |
540 | + c.Assert(actions, HasLen, 6) |
541 | + hints, ok := callArgs[0].Args[6].(map[string]*dbus.Variant) |
542 | + c.Assert(ok, Equals, true) |
543 | + // with two actions, there should be 3 hints set: |
544 | + c.Check(hints, HasLen, 1) |
545 | + c.Check(hints["x-canonical-secondary-icon"], NotNil) |
546 | + c.Check(s.log.Captured(), Matches, `(?ms).* no hints set$`) |
547 | +} |
548 | + |
549 | func (s *RawSuite) TestPresentNoNotificationDoesNotNotify(c *C) { |
550 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
551 | raw := Raw(endp, s.log) |
552 | - nid, err := raw.Present("firefox.desktop", "notifId", nil) |
553 | + nid, err := raw.Present(s.app, "notifId", nil) |
554 | c.Check(err, IsNil) |
555 | c.Check(nid, Equals, uint32(0)) |
556 | } |
557 | @@ -116,7 +247,7 @@ |
558 | func (s *RawSuite) TestPresentNoCardDoesNotNotify(c *C) { |
559 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
560 | raw := Raw(endp, s.log) |
561 | - nid, err := raw.Present("firefox.desktop", "notifId", &launch_helper.Notification{}) |
562 | + nid, err := raw.Present(s.app, "notifId", &launch_helper.Notification{}) |
563 | c.Check(err, IsNil) |
564 | c.Check(nid, Equals, uint32(0)) |
565 | } |
566 | @@ -124,7 +255,7 @@ |
567 | func (s *RawSuite) TestPresentNoSummaryDoesNotNotify(c *C) { |
568 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
569 | raw := Raw(endp, s.log) |
570 | - nid, err := raw.Present("firefox.desktop", "notifId", &launch_helper.Notification{Card: &launch_helper.Card{}}) |
571 | + nid, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{}}) |
572 | c.Check(err, IsNil) |
573 | c.Check(nid, Equals, uint32(0)) |
574 | } |
575 | @@ -132,9 +263,7 @@ |
576 | func (s *RawSuite) TestPresentNoPopupNoNotify(c *C) { |
577 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
578 | raw := Raw(endp, s.log) |
579 | - nid, err := raw.Present("firefox.desktop", "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary"}}) |
580 | + nid, err := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary"}}) |
581 | c.Check(err, IsNil) |
582 | c.Check(nid, Equals, uint32(0)) |
583 | } |
584 | - |
585 | -// XXX Missing test about ShowCard manipulating Actions and hints correctly. |
586 | |
587 | === added directory 'bus/windowstack' |
588 | === added file 'bus/windowstack/windowstack.go' |
589 | --- bus/windowstack/windowstack.go 1970-01-01 00:00:00 +0000 |
590 | +++ bus/windowstack/windowstack.go 2014-07-21 14:14:04 +0000 |
591 | @@ -0,0 +1,69 @@ |
592 | +/* |
593 | + Copyright 2014 Canonical Ltd. |
594 | + |
595 | + This program is free software: you can redistribute it and/or modify it |
596 | + under the terms of the GNU General Public License version 3, as published |
597 | + by the Free Software Foundation. |
598 | + |
599 | + This program is distributed in the hope that it will be useful, but |
600 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
601 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
602 | + PURPOSE. See the GNU General Public License for more details. |
603 | + |
604 | + You should have received a copy of the GNU General Public License along |
605 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
606 | +*/ |
607 | + |
608 | +// Package windowstack retrieves information about the windowstack |
609 | +// using Unity's dbus interface |
610 | +package windowstack |
611 | + |
612 | +import ( |
613 | + "launchpad.net/ubuntu-push/bus" |
614 | + "launchpad.net/ubuntu-push/click" |
615 | + "launchpad.net/ubuntu-push/logger" |
616 | +) |
617 | + |
618 | +// Well known address for the WindowStack API |
619 | +var BusAddress bus.Address = bus.Address{ |
620 | + Interface: "com.canonical.Unity.WindowStack", |
621 | + Path: "/com/canonical/Unity/WindowStack", |
622 | + Name: "com.canonical.Unity.WindowStack", |
623 | +} |
624 | + |
625 | +type WindowsInfo struct { |
626 | + WindowId uint32 |
627 | + AppId string // in the form "com.ubuntu.calendar_calendar" or "webbrowser-app" |
628 | + Focused bool |
629 | + Stage uint32 |
630 | +} |
631 | + |
632 | +// WindowStack encapsulates info needed to call out to the WindowStack API |
633 | +type WindowStack struct { |
634 | + bus bus.Endpoint |
635 | + log logger.Logger |
636 | +} |
637 | + |
638 | +// New returns a new WindowStack that'll use the provided bus.Endpoint |
639 | +func New(endp bus.Endpoint, log logger.Logger) *WindowStack { |
640 | + return &WindowStack{endp, log} |
641 | +} |
642 | + |
643 | +// GetWindowStack returns the window stack state |
644 | +func (stack *WindowStack) GetWindowStack() []WindowsInfo { |
645 | + var wstack []WindowsInfo |
646 | + err := stack.bus.Call("GetWindowStack", bus.Args(), &wstack) |
647 | + if err != nil { |
648 | + stack.log.Errorf("GetWindowStack call returned %v", err) |
649 | + } |
650 | + return wstack |
651 | +} |
652 | + |
653 | +func (stack *WindowStack) IsAppFocused(AppId *click.AppId) bool { |
654 | + for _, winfo := range stack.GetWindowStack() { |
655 | + if winfo.Focused && winfo.AppId == AppId.Base() { |
656 | + return true |
657 | + } |
658 | + } |
659 | + return false |
660 | +} |
661 | |
662 | === added file 'bus/windowstack/windowstack_test.go' |
663 | --- bus/windowstack/windowstack_test.go 1970-01-01 00:00:00 +0000 |
664 | +++ bus/windowstack/windowstack_test.go 2014-07-21 14:14:04 +0000 |
665 | @@ -0,0 +1,93 @@ |
666 | +/* |
667 | + Copyright 2014 Canonical Ltd. |
668 | + |
669 | + This program is free software: you can redistribute it and/or modify it |
670 | + under the terms of the GNU General Public License version 3, as published |
671 | + by the Free Software Foundation. |
672 | + |
673 | + This program is distributed in the hope that it will be useful, but |
674 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
675 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
676 | + PURPOSE. See the GNU General Public License for more details. |
677 | + |
678 | + You should have received a copy of the GNU General Public License along |
679 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
680 | +*/ |
681 | + |
682 | +package windowstack |
683 | + |
684 | +import ( |
685 | + "testing" |
686 | + |
687 | + . "launchpad.net/gocheck" |
688 | + |
689 | + testibus "launchpad.net/ubuntu-push/bus/testing" |
690 | + "launchpad.net/ubuntu-push/click" |
691 | + clickhelp "launchpad.net/ubuntu-push/click/testing" |
692 | + helpers "launchpad.net/ubuntu-push/testing" |
693 | + "launchpad.net/ubuntu-push/testing/condition" |
694 | +) |
695 | + |
696 | +func TestWindowStack(t *testing.T) { TestingT(t) } |
697 | + |
698 | +type stackSuite struct { |
699 | + log *helpers.TestLogger |
700 | + app *click.AppId |
701 | +} |
702 | + |
703 | +var _ = Suite(&stackSuite{}) |
704 | + |
705 | +func (hs *stackSuite) SetUpTest(c *C) { |
706 | + hs.log = helpers.NewTestLogger(c, "debug") |
707 | + hs.app = clickhelp.MustParseAppId("com.example.test_test-app_0") |
708 | +} |
709 | + |
710 | +// Checks that GetWindowStack() actually calls GetWindowStack |
711 | +func (ss *stackSuite) TestGetsWindowStack(c *C) { |
712 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true), []WindowsInfo{}) |
713 | + ec := New(endp, ss.log) |
714 | + c.Check(ec.GetWindowStack(), DeepEquals, []WindowsInfo{}) |
715 | + callArgs := testibus.GetCallArgs(endp) |
716 | + c.Assert(callArgs, HasLen, 1) |
717 | + c.Check(callArgs[0].Member, Equals, "GetWindowStack") |
718 | + c.Check(callArgs[0].Args, DeepEquals, []interface{}(nil)) |
719 | +} |
720 | + |
721 | +var isFocusedTests = []struct { |
722 | + expected bool // expected result |
723 | + wstack []WindowsInfo // window stack data |
724 | +}{ |
725 | + { |
726 | + false, |
727 | + []WindowsInfo{}, // No windows |
728 | + }, |
729 | + { |
730 | + true, |
731 | + []WindowsInfo{{0, "com.example.test_test-app", true, 0}}, // Just one window, matching app |
732 | + }, |
733 | + { |
734 | + false, |
735 | + []WindowsInfo{{0, "com.example.test_notest-app", true, 0}}, // Just one window, not matching app |
736 | + }, |
737 | + { |
738 | + true, |
739 | + []WindowsInfo{{0, "com.example.test_notest-app", false, 0}, {0, "com.example.test_test-app", true, 0}}, // Two windows, app focused |
740 | + }, |
741 | + { |
742 | + false, |
743 | + []WindowsInfo{{0, "com.example.test_notest-app", true, 0}, {0, "com.example.test_test-app", false, 0}}, // Two windows, app unfocused |
744 | + }, |
745 | + { |
746 | + true, |
747 | + []WindowsInfo{{0, "com.example.test_notest-app", true, 0}, {0, "com.example.test_test-app", true, 0}}, // Two windows, both focused |
748 | + }, |
749 | +} |
750 | + |
751 | +// Check that if the app is focused, IsAppFocused returns true |
752 | +func (ss *stackSuite) TestIsAppFocused(c *C) { |
753 | + for _, t := range isFocusedTests { |
754 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true), t.wstack) |
755 | + ec := New(endp, ss.log) |
756 | + c.Check(ec.IsAppFocused(ss.app), Equals, t.expected) |
757 | + } |
758 | +} |
759 | |
760 | === renamed directory 'bus/notifications/app_helper' => 'click/cappinfo' |
761 | === renamed file 'bus/notifications/app_helper/app_helper_c.go' => 'click/cappinfo/cappinfo.go' |
762 | --- bus/notifications/app_helper/app_helper_c.go 2014-07-03 15:05:48 +0000 |
763 | +++ click/cappinfo/cappinfo.go 2014-07-21 14:14:04 +0000 |
764 | @@ -14,17 +14,18 @@ |
765 | with this program. If not, see <http://www.gnu.org/licenses/>. |
766 | */ |
767 | |
768 | -// Package app_helper wraps C functions to access app information |
769 | -package app_helper |
770 | +// Package cappinfo wraps C functions to access app information |
771 | + |
772 | +package cappinfo |
773 | |
774 | /* |
775 | #cgo pkg-config: gio-unix-2.0 |
776 | #include <glib.h> |
777 | #include <gio/gdesktopappinfo.h> |
778 | |
779 | -gchar* app_icon_filename_from_id (gchar* app_id) { |
780 | +gchar* app_icon_filename_from_desktop_id (gchar* desktop_id) { |
781 | gchar* filename = NULL; |
782 | - GAppInfo* app_info = (GAppInfo*)g_desktop_app_info_new (app_id); |
783 | + GAppInfo* app_info = (GAppInfo*)g_desktop_app_info_new (desktop_id); |
784 | if (app_info != NULL) { |
785 | GIcon* icon = g_app_info_get_icon (app_info); |
786 | if (icon != NULL) { |
787 | @@ -33,14 +34,14 @@ |
788 | } |
789 | g_object_unref (app_info); |
790 | } |
791 | - g_free (app_id); |
792 | + g_free (desktop_id); |
793 | return filename; |
794 | } |
795 | */ |
796 | import "C" |
797 | |
798 | -func AppIconFromId(appId string) string { |
799 | - name := C.app_icon_filename_from_id((*C.gchar)(C.CString(appId))) |
800 | +func AppIconFromDesktopId(desktopId string) string { |
801 | + name := C.app_icon_filename_from_desktop_id((*C.gchar)(C.CString(desktopId))) |
802 | defer C.g_free((C.gpointer)(name)) |
803 | return C.GoString((*C.char)(name)) |
804 | } |
805 | |
806 | === modified file 'click/cclick/cclick.go' |
807 | --- click/cclick/cclick.go 2014-07-04 12:13:34 +0000 |
808 | +++ click/cclick/cclick.go 2014-07-21 14:14:04 +0000 |
809 | @@ -68,9 +68,3 @@ |
810 | defer gfree(ver) |
811 | return C.GoString((*C.char)(ver)) |
812 | } |
813 | - |
814 | -func (ccu *CClickUser) CHasPackageName(pkgName string) bool { |
815 | - pkgname := gchar(pkgName) |
816 | - defer gfree(pkgname) |
817 | - return C.click_user_has_package_name(ccu.cref, pkgname) == C.TRUE |
818 | -} |
819 | |
820 | === modified file 'click/click.go' |
821 | --- click/click.go 2014-07-04 15:03:59 +0000 |
822 | +++ click/click.go 2014-07-21 14:14:04 +0000 |
823 | @@ -19,10 +19,18 @@ |
824 | package click |
825 | |
826 | import ( |
827 | + "encoding/json" |
828 | "errors" |
829 | + "fmt" |
830 | + "io/ioutil" |
831 | + "path/filepath" |
832 | "regexp" |
833 | + "strings" |
834 | "sync" |
835 | |
836 | + "launchpad.net/go-xdg/v0" |
837 | + |
838 | + "launchpad.net/ubuntu-push/click/cappinfo" |
839 | "launchpad.net/ubuntu-push/click/cclick" |
840 | ) |
841 | |
842 | @@ -31,27 +39,151 @@ |
843 | Package string |
844 | Application string |
845 | Version string |
846 | + Click bool |
847 | + original string |
848 | } |
849 | |
850 | +var hookPath = filepath.Join(xdg.Data.Home(), "ubuntu-push-client", "helpers") |
851 | +var hookExt = ".json" |
852 | + |
853 | // from https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId |
854 | // except the version is made optional |
855 | -var rx = regexp.MustCompile(`^([a-z0-9][a-z0-9+.-]+)_([a-zA-Z0-9+.-]+)(?:_([0-9][a-zA-Z0-9.+:~-]*))?$`) |
856 | +var rxClick = regexp.MustCompile(`^([a-z0-9][a-z0-9+.-]+)_([a-zA-Z0-9+.-]+)(?:_([0-9][a-zA-Z0-9.+:~-]*))?$`) |
857 | + |
858 | +// no / and not starting with . |
859 | +var rxLegacy = regexp.MustCompile(`^[^./][^/]*$`) |
860 | |
861 | var ( |
862 | ErrInvalidAppId = errors.New("invalid application id") |
863 | + ErrMissingAppId = errors.New("missing application id") |
864 | ) |
865 | |
866 | func ParseAppId(id string) (*AppId, error) { |
867 | - m := rx.FindStringSubmatch(id) |
868 | - if len(m) == 0 { |
869 | - return nil, ErrInvalidAppId |
870 | - } |
871 | - return &AppId{Package: m[1], Application: m[2], Version: m[3]}, nil |
872 | -} |
873 | - |
874 | -func AppInPackage(appId, pkgname string) bool { |
875 | - id, _ := ParseAppId(appId) |
876 | - return id != nil && id.Package == pkgname |
877 | + app := new(AppId) |
878 | + err := app.setFromString(id) |
879 | + if err == nil { |
880 | + return app, nil |
881 | + } else { |
882 | + return nil, err |
883 | + } |
884 | +} |
885 | + |
886 | +func (app *AppId) setFromString(id string) error { |
887 | + if strings.HasPrefix(id, "_") { // legacy |
888 | + appname := id[1:] |
889 | + if !rxLegacy.MatchString(appname) { |
890 | + return ErrInvalidAppId |
891 | + } |
892 | + app.Package = "" |
893 | + app.Application = appname |
894 | + app.Version = "" |
895 | + app.Click = false |
896 | + app.original = id |
897 | + return nil |
898 | + } else { |
899 | + m := rxClick.FindStringSubmatch(id) |
900 | + if len(m) == 0 { |
901 | + return ErrInvalidAppId |
902 | + } |
903 | + app.Package = m[1] |
904 | + app.Application = m[2] |
905 | + app.Version = m[3] |
906 | + app.Click = true |
907 | + app.original = id |
908 | + return nil |
909 | + } |
910 | +} |
911 | + |
912 | +func (app *AppId) InPackage(pkgname string) bool { |
913 | + return app.Package == pkgname |
914 | +} |
915 | + |
916 | +func (app *AppId) Original() string { |
917 | + return app.original |
918 | +} |
919 | + |
920 | +func (app *AppId) String() string { |
921 | + return app.Original() |
922 | +} |
923 | + |
924 | +func (app *AppId) Base() string { |
925 | + if app.Click { |
926 | + return app.Package + "_" + app.Application |
927 | + } else { |
928 | + return app.Application |
929 | + } |
930 | +} |
931 | + |
932 | +type hookFile struct { |
933 | + AppId string `json:"app_id"` |
934 | + Exec string `json:"exec"` |
935 | +} |
936 | + |
937 | +// Helper figures out the app id and executable of the untrusted |
938 | +// helper for this app. |
939 | +func (app *AppId) Helper() (helperAppId string, helperExec string) { |
940 | + if !app.Click { |
941 | + return "", "" |
942 | + } |
943 | + // xxx: should probably have a cache of this |
944 | + matches, err := filepath.Glob(filepath.Join(hookPath, app.Package+"_*"+hookExt)) |
945 | + if err != nil { |
946 | + return "", "" |
947 | + } |
948 | + var v hookFile |
949 | + for _, m := range matches { |
950 | + abs, err := filepath.EvalSymlinks(m) |
951 | + if err != nil { |
952 | + continue |
953 | + } |
954 | + data, err := ioutil.ReadFile(abs) |
955 | + if err != nil { |
956 | + continue |
957 | + } |
958 | + err = json.Unmarshal(data, &v) |
959 | + if err != nil { |
960 | + continue |
961 | + } |
962 | + if v.Exec != "" && (v.AppId == "" || v.AppId == app.Base()) { |
963 | + basename := filepath.Base(m) |
964 | + helperAppId = basename[:len(basename)-len(hookExt)] |
965 | + helperExec = filepath.Join(filepath.Dir(abs), v.Exec) |
966 | + return helperAppId, helperExec |
967 | + } |
968 | + } |
969 | + return "", "" |
970 | +} |
971 | + |
972 | +func (app *AppId) Versioned() string { |
973 | + if app.Click { |
974 | + if app.Version == "" { |
975 | + panic(fmt.Errorf("Versioned() on AppId without version/not verified: %#v", app)) |
976 | + } |
977 | + return app.Package + "_" + app.Application + "_" + app.Version |
978 | + } else { |
979 | + return app.Application |
980 | + } |
981 | +} |
982 | + |
983 | +func (app *AppId) DesktopId() string { |
984 | + return app.Versioned() + ".desktop" |
985 | +} |
986 | + |
987 | +func (app *AppId) Icon() string { |
988 | + return cappinfo.AppIconFromDesktopId(app.DesktopId()) |
989 | +} |
990 | + |
991 | +func (app *AppId) MarshalJSON() ([]byte, error) { |
992 | + return json.Marshal(app.Original()) |
993 | +} |
994 | + |
995 | +func (app *AppId) UnmarshalJSON(s []byte) error { |
996 | + var v string |
997 | + err := json.Unmarshal(s, &v) |
998 | + if err != nil { |
999 | + return err |
1000 | + } |
1001 | + return app.setFromString(v) |
1002 | } |
1003 | |
1004 | // ClickUser exposes the click package registry for the user. |
1005 | @@ -60,6 +192,24 @@ |
1006 | lock sync.Mutex |
1007 | } |
1008 | |
1009 | +type InstalledChecker interface { |
1010 | + Installed(app *AppId, setVersion bool) bool |
1011 | +} |
1012 | + |
1013 | +// ParseAndVerifyAppId parses the given app id and checks if the |
1014 | +// corresponding app is installed, returning the parsed id or |
1015 | +// ErrInvalidAppId, or the parsed id and ErrMissingAppId respectively. |
1016 | +func ParseAndVerifyAppId(id string, installedChecker InstalledChecker) (*AppId, error) { |
1017 | + app, err := ParseAppId(id) |
1018 | + if err != nil { |
1019 | + return nil, err |
1020 | + } |
1021 | + if installedChecker != nil && !installedChecker.Installed(app, true) { |
1022 | + return app, ErrMissingAppId |
1023 | + } |
1024 | + return app, nil |
1025 | +} |
1026 | + |
1027 | // User makes a new ClickUser object for the current user. |
1028 | func User() (*ClickUser, error) { |
1029 | cu := new(ClickUser) |
1030 | @@ -70,17 +220,24 @@ |
1031 | return cu, nil |
1032 | } |
1033 | |
1034 | -// HasPackage checks if the appId is installed for user. |
1035 | -func (cu *ClickUser) HasPackage(appId string) bool { |
1036 | +// Installed checks if the appId is installed for user, optionally setting |
1037 | +// the version if it was absent. |
1038 | +func (cu *ClickUser) Installed(app *AppId, setVersion bool) bool { |
1039 | cu.lock.Lock() |
1040 | defer cu.lock.Unlock() |
1041 | - id, err := ParseAppId(appId) |
1042 | - if err != nil { |
1043 | - return false |
1044 | - } |
1045 | - if id.Version != "" { |
1046 | - return cu.ccu.CGetVersion(id.Package) == id.Version |
1047 | + if app.Click { |
1048 | + ver := cu.ccu.CGetVersion(app.Package) |
1049 | + if ver == "" { |
1050 | + return false |
1051 | + } |
1052 | + if app.Version != "" { |
1053 | + return app.Version == ver |
1054 | + } else if setVersion { |
1055 | + app.Version = ver |
1056 | + } |
1057 | + return true |
1058 | } else { |
1059 | - return cu.ccu.CHasPackageName(id.Package) |
1060 | + _, err := xdg.Data.Find(filepath.Join("applications", app.DesktopId())) |
1061 | + return err == nil |
1062 | } |
1063 | } |
1064 | |
1065 | === modified file 'click/click_test.go' |
1066 | --- click/click_test.go 2014-07-04 12:35:41 +0000 |
1067 | +++ click/click_test.go 2014-07-21 14:14:04 +0000 |
1068 | @@ -17,6 +17,10 @@ |
1069 | package click |
1070 | |
1071 | import ( |
1072 | + "encoding/json" |
1073 | + "fmt" |
1074 | + "os" |
1075 | + "path/filepath" |
1076 | "testing" |
1077 | |
1078 | . "launchpad.net/gocheck" |
1079 | @@ -29,30 +33,79 @@ |
1080 | var _ = Suite(&clickSuite{}) |
1081 | |
1082 | func (cs *clickSuite) TestParseAppId(c *C) { |
1083 | - id, err := ParseAppId("com.ubuntu.clock_clock") |
1084 | + app, err := ParseAppId("com.ubuntu.clock_clock") |
1085 | c.Assert(err, IsNil) |
1086 | - c.Check(id.Package, Equals, "com.ubuntu.clock") |
1087 | - c.Check(id.Application, Equals, "clock") |
1088 | - c.Check(id.Version, Equals, "") |
1089 | + c.Check(app.Package, Equals, "com.ubuntu.clock") |
1090 | + c.Check(app.InPackage("com.ubuntu.clock"), Equals, true) |
1091 | + c.Check(app.Application, Equals, "clock") |
1092 | + c.Check(app.Version, Equals, "") |
1093 | + c.Check(app.Click, Equals, true) |
1094 | + c.Check(app.Original(), Equals, "com.ubuntu.clock_clock") |
1095 | + c.Check(fmt.Sprintf("%s", app), Equals, "com.ubuntu.clock_clock") |
1096 | |
1097 | - id, err = ParseAppId("com.ubuntu.clock_clock_10") |
1098 | + app, err = ParseAppId("com.ubuntu.clock_clock_10") |
1099 | c.Assert(err, IsNil) |
1100 | - c.Check(id.Package, Equals, "com.ubuntu.clock") |
1101 | - c.Check(id.Application, Equals, "clock") |
1102 | - c.Check(id.Version, Equals, "10") |
1103 | + c.Check(app.Package, Equals, "com.ubuntu.clock") |
1104 | + c.Check(app.InPackage("com.ubuntu.clock"), Equals, true) |
1105 | + c.Check(app.Application, Equals, "clock") |
1106 | + c.Check(app.Version, Equals, "10") |
1107 | + c.Check(app.Click, Equals, true) |
1108 | + c.Check(app.Original(), Equals, "com.ubuntu.clock_clock_10") |
1109 | + c.Check(fmt.Sprintf("%s", app), Equals, "com.ubuntu.clock_clock_10") |
1110 | + c.Check(app.Versioned(), Equals, "com.ubuntu.clock_clock_10") |
1111 | + c.Check(app.Base(), Equals, "com.ubuntu.clock_clock") |
1112 | + c.Check(app.DesktopId(), Equals, "com.ubuntu.clock_clock_10.desktop") |
1113 | |
1114 | for _, s := range []string{"com.ubuntu.clock_clock_10_4", "com.ubuntu.clock", ""} { |
1115 | - id, err = ParseAppId(s) |
1116 | - c.Check(id, IsNil) |
1117 | - c.Check(err, Equals, ErrInvalidAppId) |
1118 | - } |
1119 | -} |
1120 | - |
1121 | -func (cs *clickSuite) TestInPackage(c *C) { |
1122 | - c.Check(AppInPackage("com.ubuntu.clock_clock", "com.ubuntu.clock"), Equals, true) |
1123 | - c.Check(AppInPackage("com.ubuntu.clock_clock_10", "com.ubuntu.clock"), Equals, true) |
1124 | - c.Check(AppInPackage("com.ubuntu.clock", "com.ubuntu.clock"), Equals, false) |
1125 | - c.Check(AppInPackage("bananas", "fruit"), Equals, false) |
1126 | + app, err = ParseAppId(s) |
1127 | + c.Check(app, IsNil) |
1128 | + c.Check(err, Equals, ErrInvalidAppId) |
1129 | + } |
1130 | +} |
1131 | + |
1132 | +func (cs *clickSuite) TestVersionedPanic(c *C) { |
1133 | + app, err := ParseAppId("com.ubuntu.clock_clock") |
1134 | + c.Assert(err, IsNil) |
1135 | + c.Check(func() { app.Versioned() }, PanicMatches, `Versioned\(\) on AppId without version/not verified:.*`) |
1136 | +} |
1137 | + |
1138 | +func (cs *clickSuite) TestParseAppIdLegacy(c *C) { |
1139 | + app, err := ParseAppId("_python3.4") |
1140 | + c.Assert(err, IsNil) |
1141 | + c.Check(app.Package, Equals, "") |
1142 | + c.Check(app.InPackage(""), Equals, true) |
1143 | + c.Check(app.Application, Equals, "python3.4") |
1144 | + c.Check(app.Version, Equals, "") |
1145 | + c.Check(app.Click, Equals, false) |
1146 | + c.Check(app.Original(), Equals, "_python3.4") |
1147 | + c.Check(app.Versioned(), Equals, "python3.4") |
1148 | + c.Check(app.Base(), Equals, "python3.4") |
1149 | + c.Check(app.DesktopId(), Equals, "python3.4.desktop") |
1150 | + |
1151 | + for _, s := range []string{"_.foo", "_foo/", "_/foo"} { |
1152 | + app, err = ParseAppId(s) |
1153 | + c.Check(app, IsNil) |
1154 | + c.Check(err, Equals, ErrInvalidAppId) |
1155 | + } |
1156 | +} |
1157 | + |
1158 | +func (cs *clickSuite) TestJSON(c *C) { |
1159 | + for _, appId := range []string{"com.ubuntu.clock_clock", "com.ubuntu.clock_clock_10", "_python3.4"} { |
1160 | + app, err := ParseAppId(appId) |
1161 | + c.Assert(err, IsNil, Commentf(appId)) |
1162 | + b, err := json.Marshal(app) |
1163 | + c.Assert(err, IsNil, Commentf(appId)) |
1164 | + var vapp *AppId |
1165 | + err = json.Unmarshal(b, &vapp) |
1166 | + c.Assert(err, IsNil, Commentf(appId)) |
1167 | + c.Check(vapp, DeepEquals, app) |
1168 | + } |
1169 | +} |
1170 | + |
1171 | +func (cs *clickSuite) TestIcon(c *C) { |
1172 | + app, err := ParseAppId("_python3.4") |
1173 | + c.Assert(err, IsNil) |
1174 | + c.Check(app.Icon(), Equals, "/usr/share/pixmaps/python3.4.xpm") |
1175 | } |
1176 | |
1177 | func (s *clickSuite) TestUser(c *C) { |
1178 | @@ -61,26 +114,144 @@ |
1179 | c.Assert(u, NotNil) |
1180 | } |
1181 | |
1182 | -func (s *clickSuite) TestHasPackageNegative(c *C) { |
1183 | - u, err := User() |
1184 | - c.Assert(err, IsNil) |
1185 | - c.Check(u.HasPackage("com.foo.bar"), Equals, false) |
1186 | - c.Check(u.HasPackage("com.foo.bar_baz"), Equals, false) |
1187 | -} |
1188 | - |
1189 | -func (s *clickSuite) TestHasPackageVersionNegative(c *C) { |
1190 | - u, err := User() |
1191 | - c.Assert(err, IsNil) |
1192 | - c.Check(u.HasPackage("com.ubuntu.clock_clock_1000.0"), Equals, false) |
1193 | -} |
1194 | - |
1195 | -func (s *clickSuite) TestHasPackageClock(c *C) { |
1196 | +func (s *clickSuite) TestInstalledNegative(c *C) { |
1197 | + u, err := User() |
1198 | + c.Assert(err, IsNil) |
1199 | + app, err := ParseAppId("com.foo.bar_baz") |
1200 | + c.Assert(err, IsNil) |
1201 | + c.Check(u.Installed(app, false), Equals, false) |
1202 | +} |
1203 | + |
1204 | +func (s *clickSuite) TestInstalledVersionNegative(c *C) { |
1205 | + u, err := User() |
1206 | + c.Assert(err, IsNil) |
1207 | + app, err := ParseAppId("com.ubuntu.clock_clock_1000.0") |
1208 | + c.Assert(err, IsNil) |
1209 | + c.Check(u.Installed(app, false), Equals, false) |
1210 | +} |
1211 | + |
1212 | +func (s *clickSuite) TestInstalledClock(c *C) { |
1213 | u, err := User() |
1214 | c.Assert(err, IsNil) |
1215 | ver := u.ccu.CGetVersion("com.ubuntu.clock") |
1216 | if ver == "" { |
1217 | c.Skip("no com.ubuntu.clock pkg installed") |
1218 | } |
1219 | - c.Check(u.HasPackage("com.ubuntu.clock_clock"), Equals, true) |
1220 | - c.Check(u.HasPackage("com.ubuntu.clock_clock_"+ver), Equals, true) |
1221 | + app, err := ParseAppId("com.ubuntu.clock_clock") |
1222 | + c.Assert(err, IsNil) |
1223 | + c.Check(u.Installed(app, false), Equals, true) |
1224 | + app, err = ParseAppId("com.ubuntu.clock_clock_" + ver) |
1225 | + c.Assert(err, IsNil) |
1226 | + c.Check(u.Installed(app, false), Equals, true) |
1227 | + |
1228 | + app, err = ParseAppId("com.ubuntu.clock_clock_10" + ver) |
1229 | + c.Assert(err, IsNil) |
1230 | + c.Check(u.Installed(app, false), Equals, false) |
1231 | + |
1232 | + // setVersion |
1233 | + app, err = ParseAppId("com.ubuntu.clock_clock") |
1234 | + c.Assert(err, IsNil) |
1235 | + c.Check(u.Installed(app, true), Equals, true) |
1236 | + c.Check(app.Version, Equals, ver) |
1237 | +} |
1238 | + |
1239 | +func (s *clickSuite) TestInstalledLegacy(c *C) { |
1240 | + u, err := User() |
1241 | + c.Assert(err, IsNil) |
1242 | + app, err := ParseAppId("_python3.4") |
1243 | + c.Assert(err, IsNil) |
1244 | + c.Check(u.Installed(app, false), Equals, true) |
1245 | +} |
1246 | + |
1247 | +func (s *clickSuite) TestParseAndVerifyAppId(c *C) { |
1248 | + u, err := User() |
1249 | + c.Assert(err, IsNil) |
1250 | + |
1251 | + app, err := ParseAndVerifyAppId("_.foo", nil) |
1252 | + c.Assert(err, Equals, ErrInvalidAppId) |
1253 | + c.Check(app, IsNil) |
1254 | + |
1255 | + app, err = ParseAndVerifyAppId("com.foo.bar_baz", nil) |
1256 | + c.Assert(err, IsNil) |
1257 | + c.Check(app.Click, Equals, true) |
1258 | + c.Check(app.Application, Equals, "baz") |
1259 | + |
1260 | + app, err = ParseAndVerifyAppId("_non-existent-app", u) |
1261 | + c.Assert(err, Equals, ErrMissingAppId) |
1262 | + c.Check(app, NotNil) |
1263 | + c.Check(app.Original(), Equals, "_non-existent-app") |
1264 | + |
1265 | +} |
1266 | + |
1267 | +type helperSuite struct { |
1268 | + oldHookPath string |
1269 | + symlinkPath string |
1270 | +} |
1271 | + |
1272 | +var _ = Suite(&helperSuite{}) |
1273 | + |
1274 | +func (s *helperSuite) SetUpTest(c *C) { |
1275 | + s.oldHookPath = hookPath |
1276 | + hookPath = c.MkDir() |
1277 | + s.symlinkPath = c.MkDir() |
1278 | +} |
1279 | + |
1280 | +func (s *helperSuite) createHookfile(name string, content string) error { |
1281 | + symlink := filepath.Join(hookPath, name) + ".json" |
1282 | + filename := filepath.Join(s.symlinkPath, name) |
1283 | + f, err := os.Create(filename) |
1284 | + if err != nil { |
1285 | + return err |
1286 | + } |
1287 | + _, err = f.WriteString(content) |
1288 | + if err != nil { |
1289 | + return err |
1290 | + } |
1291 | + err = os.Symlink(filename, symlink) |
1292 | + if err != nil { |
1293 | + return err |
1294 | + } |
1295 | + return nil |
1296 | +} |
1297 | + |
1298 | +func (s *helperSuite) TearDownTest(c *C) { |
1299 | + hookPath = s.oldHookPath |
1300 | +} |
1301 | + |
1302 | +func (s *helperSuite) TestHelperBasic(c *C) { |
1303 | + c.Assert(s.createHookfile("com.example.test_test-helper_1", `{"exec": "tsthlpr"}`), IsNil) |
1304 | + app, err := ParseAppId("com.example.test_test-app_1") |
1305 | + c.Assert(err, IsNil) |
1306 | + hid, hex := app.Helper() |
1307 | + c.Check(hid, Equals, "com.example.test_test-helper_1") |
1308 | + c.Check(hex, Equals, filepath.Join(s.symlinkPath, "tsthlpr")) |
1309 | +} |
1310 | + |
1311 | +func (s *helperSuite) TestHelperFindsSpecific(c *C) { |
1312 | + // Glob() sorts, so the first one will come first |
1313 | + c.Assert(s.createHookfile("com.example.test_aaaa-helper_1", `{"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil) |
1314 | + c.Assert(s.createHookfile("com.example.test_test-helper_1", `{"exec": "tsthlpr", "app_id": "com.example.test_test-app"}`), IsNil) |
1315 | + app, err := ParseAppId("com.example.test_test-app_1") |
1316 | + c.Assert(err, IsNil) |
1317 | + hid, hex := app.Helper() |
1318 | + c.Check(hid, Equals, "com.example.test_test-helper_1") |
1319 | + c.Check(hex, Equals, filepath.Join(s.symlinkPath, "tsthlpr")) |
1320 | +} |
1321 | + |
1322 | +func (s *helperSuite) TestHelperCanFail(c *C) { |
1323 | + c.Assert(s.createHookfile("com.example.test_aaaa-helper_1", `{"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil) |
1324 | + app, err := ParseAppId("com.example.test_test-app_1") |
1325 | + c.Assert(err, IsNil) |
1326 | + hid, hex := app.Helper() |
1327 | + c.Check(hid, Equals, "") |
1328 | + c.Check(hex, Equals, "") |
1329 | +} |
1330 | + |
1331 | +func (s *clickSuite) TestHelperlegacy(c *C) { |
1332 | + appname := "ubuntu-system-settings" |
1333 | + app, err := ParseAppId("_" + appname) |
1334 | + c.Assert(err, IsNil) |
1335 | + hid, hex := app.Helper() |
1336 | + c.Check(hid, Equals, "") |
1337 | + c.Check(hex, Equals, "") |
1338 | } |
1339 | |
1340 | === added directory 'click/testing' |
1341 | === added file 'click/testing/helpers.go' |
1342 | --- click/testing/helpers.go 1970-01-01 00:00:00 +0000 |
1343 | +++ click/testing/helpers.go 2014-07-21 14:14:04 +0000 |
1344 | @@ -0,0 +1,31 @@ |
1345 | +/* |
1346 | + Copyright 2014 Canonical Ltd. |
1347 | + |
1348 | + This program is free software: you can redistribute it and/or modify it |
1349 | + under the terms of the GNU General Public License version 3, as published |
1350 | + by the Free Software Foundation. |
1351 | + |
1352 | + This program is distributed in the hope that it will be useful, but |
1353 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
1354 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1355 | + PURPOSE. See the GNU General Public License for more details. |
1356 | + |
1357 | + You should have received a copy of the GNU General Public License along |
1358 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
1359 | +*/ |
1360 | + |
1361 | +// Package testing contains helpers for testing related to click. |
1362 | +package testing |
1363 | + |
1364 | +import ( |
1365 | + "launchpad.net/ubuntu-push/click" |
1366 | +) |
1367 | + |
1368 | +// MustParseAppId parses an appId or panics. |
1369 | +func MustParseAppId(appId string) *click.AppId { |
1370 | + app, err := click.ParseAppId(appId) |
1371 | + if err != nil { |
1372 | + panic(err) |
1373 | + } |
1374 | + return app |
1375 | +} |
1376 | |
1377 | === modified file 'client/client.go' |
1378 | --- client/client.go 2014-07-06 13:57:37 +0000 |
1379 | +++ client/client.go 2014-07-21 14:14:04 +0000 |
1380 | @@ -22,6 +22,7 @@ |
1381 | "crypto/sha256" |
1382 | "encoding/base64" |
1383 | "encoding/hex" |
1384 | + "encoding/json" |
1385 | "encoding/pem" |
1386 | "errors" |
1387 | "fmt" |
1388 | @@ -31,14 +32,12 @@ |
1389 | "os/exec" |
1390 | "strings" |
1391 | |
1392 | + "code.google.com/p/go-uuid/uuid" |
1393 | + |
1394 | "launchpad.net/ubuntu-push/bus" |
1395 | "launchpad.net/ubuntu-push/bus/connectivity" |
1396 | - "launchpad.net/ubuntu-push/bus/emblemcounter" |
1397 | - "launchpad.net/ubuntu-push/bus/haptic" |
1398 | "launchpad.net/ubuntu-push/bus/networkmanager" |
1399 | - "launchpad.net/ubuntu-push/bus/notifications" |
1400 | "launchpad.net/ubuntu-push/bus/systemimage" |
1401 | - "launchpad.net/ubuntu-push/bus/urldispatcher" |
1402 | "launchpad.net/ubuntu-push/click" |
1403 | "launchpad.net/ubuntu-push/client/service" |
1404 | "launchpad.net/ubuntu-push/client/session" |
1405 | @@ -80,35 +79,40 @@ |
1406 | Unregister(appId string) error |
1407 | } |
1408 | |
1409 | +type PostalService interface { |
1410 | + // Starts the service |
1411 | + Start() error |
1412 | + // Post converts a push message into a presentable notification |
1413 | + // and a postal message, presents the former and stores the |
1414 | + // latter in the application's mailbox. |
1415 | + Post(app *click.AppId, nid string, payload json.RawMessage) error |
1416 | + // IsRunning() returns whether the service is running |
1417 | + IsRunning() bool |
1418 | + // Stop() stops the service |
1419 | + Stop() |
1420 | +} |
1421 | + |
1422 | // PushClient is the Ubuntu Push Notifications client-side daemon. |
1423 | type PushClient struct { |
1424 | - leveldbPath string |
1425 | - configPath string |
1426 | - config ClientConfig |
1427 | - log logger.Logger |
1428 | - pem []byte |
1429 | - idder identifier.Id |
1430 | - deviceId string |
1431 | - notificationsEndp bus.Endpoint |
1432 | - urlDispatcherEndp bus.Endpoint |
1433 | - connectivityEndp bus.Endpoint |
1434 | - emblemcounterEndp bus.Endpoint |
1435 | - hapticEndp bus.Endpoint |
1436 | - systemImageEndp bus.Endpoint |
1437 | - systemImageInfo *systemimage.InfoResult |
1438 | - connCh chan bool |
1439 | - hasConnectivity bool |
1440 | - actionsCh <-chan notifications.RawActionReply |
1441 | - session *session.ClientSession |
1442 | - sessionConnectedCh chan uint32 |
1443 | - pushServiceEndpoint bus.Endpoint |
1444 | - pushService PushService |
1445 | - postalServiceEndpoint bus.Endpoint |
1446 | - postalService *service.PostalService |
1447 | - unregisterCh chan string |
1448 | - trackAddressees map[string]bool |
1449 | - clickUser *click.ClickUser |
1450 | - hasPackage func(string) bool |
1451 | + leveldbPath string |
1452 | + configPath string |
1453 | + config ClientConfig |
1454 | + log logger.Logger |
1455 | + pem []byte |
1456 | + idder identifier.Id |
1457 | + deviceId string |
1458 | + connectivityEndp bus.Endpoint |
1459 | + systemImageEndp bus.Endpoint |
1460 | + systemImageInfo *systemimage.InfoResult |
1461 | + connCh chan bool |
1462 | + hasConnectivity bool |
1463 | + session *session.ClientSession |
1464 | + sessionConnectedCh chan uint32 |
1465 | + pushService PushService |
1466 | + postalService PostalService |
1467 | + unregisterCh chan *click.AppId |
1468 | + trackAddressees map[string]*click.AppId |
1469 | + installedChecker click.InstalledChecker |
1470 | } |
1471 | |
1472 | // Creates a new Ubuntu Push Notifications client-side daemon that will use |
1473 | @@ -144,22 +148,15 @@ |
1474 | if err != nil { |
1475 | return fmt.Errorf("libclick: %v", err) |
1476 | } |
1477 | - client.clickUser = clickUser |
1478 | // overridden for testing |
1479 | - client.hasPackage = clickUser.HasPackage |
1480 | + client.installedChecker = clickUser |
1481 | |
1482 | - client.unregisterCh = make(chan string, 10) |
1483 | + client.unregisterCh = make(chan *click.AppId, 10) |
1484 | |
1485 | // overridden for testing |
1486 | client.idder = identifier.New() |
1487 | - client.urlDispatcherEndp = bus.SessionBus.Endpoint(urldispatcher.BusAddress, client.log) |
1488 | client.connectivityEndp = bus.SystemBus.Endpoint(networkmanager.BusAddress, client.log) |
1489 | client.systemImageEndp = bus.SystemBus.Endpoint(systemimage.BusAddress, client.log) |
1490 | - client.notificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, client.log) |
1491 | - client.emblemcounterEndp = bus.SessionBus.Endpoint(emblemcounter.BusAddress, client.log) |
1492 | - client.hapticEndp = bus.SessionBus.Endpoint(haptic.BusAddress, client.log) |
1493 | - client.postalServiceEndpoint = bus.SessionBus.Endpoint(service.PostalServiceBusAddress, client.log) |
1494 | - client.pushServiceEndpoint = bus.SessionBus.Endpoint(service.PushServiceBusAddress, client.log) |
1495 | |
1496 | client.connCh = make(chan bool, 1) |
1497 | client.sessionConnectedCh = make(chan uint32, 1) |
1498 | @@ -204,6 +201,7 @@ |
1499 | setup.RegURL = purl |
1500 | setup.DeviceId = client.deviceId |
1501 | setup.AuthGetter = client.getAuthorization |
1502 | + setup.InstalledChecker = client.installedChecker |
1503 | return setup, nil |
1504 | } |
1505 | |
1506 | @@ -246,12 +244,7 @@ |
1507 | func (client *PushClient) takeTheBus() error { |
1508 | go connectivity.ConnectedState(client.connectivityEndp, |
1509 | client.config.ConnectivityConfig, client.log, client.connCh) |
1510 | - iniCh := make(chan uint32) |
1511 | - go func() { iniCh <- util.NewAutoRedialer(client.urlDispatcherEndp).Redial() }() |
1512 | - go func() { iniCh <- util.NewAutoRedialer(client.systemImageEndp).Redial() }() |
1513 | - <-iniCh |
1514 | - <-iniCh |
1515 | - |
1516 | + util.NewAutoRedialer(client.systemImageEndp).Redial() |
1517 | sysimg := systemimage.New(client.systemImageEndp, client.log) |
1518 | info, err := sysimg.Info() |
1519 | if err != nil { |
1520 | @@ -261,12 +254,6 @@ |
1521 | return err |
1522 | } |
1523 | |
1524 | -func (client *PushClient) takePostalServiceBus() error { |
1525 | - actionsCh, err := client.postalService.TakeTheBus() |
1526 | - client.actionsCh = actionsCh |
1527 | - return err |
1528 | -} |
1529 | - |
1530 | // initSession creates the session object |
1531 | func (client *PushClient) initSession() error { |
1532 | info := map[string]interface{}{ |
1533 | @@ -295,36 +282,44 @@ |
1534 | |
1535 | // StartAddresseeBatch starts a batch of checks for addressees. |
1536 | func (client *PushClient) StartAddresseeBatch() { |
1537 | - client.trackAddressees = make(map[string]bool, 10) |
1538 | + client.trackAddressees = make(map[string]*click.AppId, 10) |
1539 | } |
1540 | |
1541 | // CheckForAddressee check for the addressee presence. |
1542 | -func (client *PushClient) CheckForAddressee(notif *protocol.Notification) bool { |
1543 | +func (client *PushClient) CheckForAddressee(notif *protocol.Notification) *click.AppId { |
1544 | appId := notif.AppId |
1545 | - present, ok := client.trackAddressees[appId] |
1546 | + parsed, ok := client.trackAddressees[appId] |
1547 | if ok { |
1548 | - return present |
1549 | - } |
1550 | - present = client.hasPackage(appId) |
1551 | - client.trackAddressees[appId] = present |
1552 | - if !present { |
1553 | - client.unregisterCh <- appId |
1554 | - } |
1555 | - return present |
1556 | + return parsed |
1557 | + } |
1558 | + parsed, err := click.ParseAndVerifyAppId(appId, client.installedChecker) |
1559 | + switch err { |
1560 | + default: |
1561 | + client.log.Debugf("notification %#v for invalid app id %#v.", notif.MsgId, notif.AppId) |
1562 | + case click.ErrMissingAppId: |
1563 | + client.log.Debugf("notification %#v for missing app id %#v.", notif.MsgId, notif.AppId) |
1564 | + client.unregisterCh <- parsed |
1565 | + parsed = nil |
1566 | + case nil: |
1567 | + } |
1568 | + client.trackAddressees[appId] = parsed |
1569 | + return parsed |
1570 | } |
1571 | |
1572 | // handleUnregister deals with tokens of uninstalled apps |
1573 | -func (client *PushClient) handleUnregister(appId string) { |
1574 | - if !client.hasPackage(appId) { |
1575 | +func (client *PushClient) handleUnregister(app *click.AppId) { |
1576 | + if !client.installedChecker.Installed(app, false) { |
1577 | // xxx small chance of race here, in case the app gets |
1578 | // reinstalled and registers itself before we finish |
1579 | // the unregister; we need click and app launching |
1580 | // collaboration to do better. we redo the hasPackage |
1581 | // check here just before to keep the race window as |
1582 | // small as possible |
1583 | - err := client.pushService.Unregister(appId) |
1584 | + err := client.pushService.Unregister(app.Original()) // XXX WIP |
1585 | if err != nil { |
1586 | - client.log.Errorf("unregistering %s: %s", appId, err) |
1587 | + client.log.Errorf("unregistering %s: %s", app, err) |
1588 | + } else { |
1589 | + client.log.Debugf("unregistered token for %s", app) |
1590 | } |
1591 | } |
1592 | } |
1593 | @@ -398,69 +393,52 @@ |
1594 | // handleBroadcastNotification deals with receiving a broadcast notification |
1595 | func (client *PushClient) handleBroadcastNotification(msg *session.BroadcastNotification) error { |
1596 | if !client.filterBroadcastNotification(msg) { |
1597 | + client.log.Debugf("not posting broadcast notification %d; filtered.", msg.TopLevel) |
1598 | return nil |
1599 | } |
1600 | - not_id, err := client.postalService.InjectBroadcast() |
1601 | + // marshal the last decoded msg to json |
1602 | + payload, err := json.Marshal(msg.Decoded[len(msg.Decoded)-1]) |
1603 | + if err == nil { |
1604 | + appId, _ := click.ParseAppId("_ubuntu-system-settings") |
1605 | + err = client.postalService.Post(appId, uuid.New(), payload) |
1606 | + } |
1607 | if err != nil { |
1608 | - client.log.Errorf("showing notification: %s", err) |
1609 | - return err |
1610 | + client.log.Errorf("while posting broadcast notification %d: %v", msg.TopLevel, err) |
1611 | + } else { |
1612 | + client.log.Debugf("posted broadcast notification %d.", msg.TopLevel) |
1613 | } |
1614 | - client.log.Debugf("got notification id %d", not_id) |
1615 | - return nil |
1616 | + return err |
1617 | } |
1618 | |
1619 | // handleUnicastNotification deals with receiving a unicast notification |
1620 | -func (client *PushClient) handleUnicastNotification(msg *protocol.Notification) error { |
1621 | - appId, err := click.ParseAppId(msg.AppId) |
1622 | +func (client *PushClient) handleUnicastNotification(anotif session.AddressedNotification) error { |
1623 | + app := anotif.To |
1624 | + msg := anotif.Notification |
1625 | + err := client.postalService.Post(app, msg.MsgId, msg.Payload) |
1626 | if err != nil { |
1627 | - client.log.Debugf("notification %#v for invalid app id %#v.", msg.MsgId, msg.AppId) |
1628 | - return errors.New("invalid app id in notification") |
1629 | - } |
1630 | - client.log.Debugf("sending notification %#v for %#v.", msg.MsgId, msg.AppId) |
1631 | - return client.postalService.Inject(appId.Package, msg.AppId, msg.MsgId, string(msg.Payload)) |
1632 | -} |
1633 | - |
1634 | -// handleClick deals with the user clicking a notification |
1635 | -func (client *PushClient) handleClick(actionId string) error { |
1636 | - // “The string is a stark data structure and everywhere it is passed |
1637 | - // there is much duplication of process. It is a perfect vehicle for |
1638 | - // hiding information.” |
1639 | - // |
1640 | - // From ACM's SIGPLAN publication, (September, 1982), Article |
1641 | - // "Epigrams in Programming", by Alan J. Perlis of Yale University. |
1642 | - url := actionId |
1643 | - // XXX: branch for the broadcast notifications |
1644 | - if strings.HasPrefix(actionId, service.ACTION_ID_PREFIX) { |
1645 | - parts := strings.Split(actionId, "::") |
1646 | - url = parts[1] |
1647 | - } |
1648 | - if len(url) == len(actionId) || len(url) == 0 { |
1649 | - // it didn't start with the prefix |
1650 | - return nil |
1651 | - } |
1652 | - // it doesn't get much simpler... |
1653 | - urld := urldispatcher.New(client.urlDispatcherEndp, client.log) |
1654 | - return urld.DispatchURL(url) |
1655 | + client.log.Errorf("while posting unicast notification %s for %s: %v", msg.MsgId, msg.AppId, err) |
1656 | + } else { |
1657 | + client.log.Debugf("posted unicast notification %s for %s.", msg.MsgId, msg.AppId) |
1658 | + } |
1659 | + return err |
1660 | } |
1661 | |
1662 | // doLoop connects events with their handlers |
1663 | -func (client *PushClient) doLoop(connhandler func(bool), clickhandler func(string) error, bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(*protocol.Notification) error, errhandler func(error), unregisterhandler func(string)) { |
1664 | +func (client *PushClient) doLoop(connhandler func(bool), bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(session.AddressedNotification) error, errhandler func(error), unregisterhandler func(*click.AppId)) { |
1665 | for { |
1666 | select { |
1667 | case state := <-client.connCh: |
1668 | connhandler(state) |
1669 | - case action := <-client.actionsCh: |
1670 | - clickhandler(action.ActionId) |
1671 | case bcast := <-client.session.BroadcastCh: |
1672 | bcasthandler(bcast) |
1673 | - case ucast := <-client.session.NotificationsCh: |
1674 | - ucasthandler(ucast) |
1675 | + case aucast := <-client.session.NotificationsCh: |
1676 | + ucasthandler(aucast) |
1677 | case err := <-client.session.ErrCh: |
1678 | errhandler(err) |
1679 | case count := <-client.sessionConnectedCh: |
1680 | client.log.Debugf("Session connected after %d attempts", count) |
1681 | - case appId := <-client.unregisterCh: |
1682 | - unregisterhandler(appId) |
1683 | + case app := <-client.unregisterCh: |
1684 | + unregisterhandler(app) |
1685 | } |
1686 | } |
1687 | } |
1688 | @@ -479,20 +457,23 @@ |
1689 | // Loop calls doLoop with the "real" handlers |
1690 | func (client *PushClient) Loop() { |
1691 | client.doLoop(client.handleConnState, |
1692 | - client.handleClick, |
1693 | client.handleBroadcastNotification, |
1694 | client.handleUnicastNotification, |
1695 | client.handleErr, |
1696 | client.handleUnregister) |
1697 | } |
1698 | |
1699 | -func (client *PushClient) startService() error { |
1700 | +func (client *PushClient) setupPushService() error { |
1701 | setup, err := client.derivePushServiceSetup() |
1702 | if err != nil { |
1703 | return err |
1704 | } |
1705 | |
1706 | - client.pushService = service.NewPushService(client.pushServiceEndpoint, setup, client.log) |
1707 | + client.pushService = service.NewPushService(setup, client.log) |
1708 | + return nil |
1709 | +} |
1710 | + |
1711 | +func (client *PushClient) startPushService() error { |
1712 | if err := client.pushService.Start(); err != nil { |
1713 | return err |
1714 | } |
1715 | @@ -500,7 +481,7 @@ |
1716 | } |
1717 | |
1718 | func (client *PushClient) setupPostalService() error { |
1719 | - client.postalService = service.NewPostalService(client.postalServiceEndpoint, client.notificationsEndp, client.emblemcounterEndp, client.hapticEndp, client.log) |
1720 | + client.postalService = service.NewPostalService(client.installedChecker, client.log) |
1721 | return nil |
1722 | } |
1723 | |
1724 | @@ -516,11 +497,11 @@ |
1725 | return client.doStart( |
1726 | client.configure, |
1727 | client.getDeviceId, |
1728 | - client.startService, |
1729 | + client.setupPushService, |
1730 | client.setupPostalService, |
1731 | + client.startPushService, |
1732 | client.startPostalService, |
1733 | client.takeTheBus, |
1734 | - client.takePostalServiceBus, |
1735 | client.initSession, |
1736 | ) |
1737 | } |
1738 | |
1739 | === modified file 'client/client_test.go' |
1740 | --- client/client_test.go 2014-07-07 10:35:51 +0000 |
1741 | +++ client/client_test.go 2014-07-21 14:14:04 +0000 |
1742 | @@ -34,9 +34,10 @@ |
1743 | |
1744 | "launchpad.net/ubuntu-push/bus" |
1745 | "launchpad.net/ubuntu-push/bus/networkmanager" |
1746 | - "launchpad.net/ubuntu-push/bus/notifications" |
1747 | "launchpad.net/ubuntu-push/bus/systemimage" |
1748 | testibus "launchpad.net/ubuntu-push/bus/testing" |
1749 | + "launchpad.net/ubuntu-push/click" |
1750 | + clickhelp "launchpad.net/ubuntu-push/click/testing" |
1751 | "launchpad.net/ubuntu-push/client/service" |
1752 | "launchpad.net/ubuntu-push/client/session" |
1753 | "launchpad.net/ubuntu-push/client/session/seenstate" |
1754 | @@ -61,6 +62,63 @@ |
1755 | } |
1756 | } |
1757 | |
1758 | +type dumbCommon struct { |
1759 | + startCount int |
1760 | + stopCount int |
1761 | + runningCount int |
1762 | + running bool |
1763 | + err error |
1764 | +} |
1765 | + |
1766 | +func (d *dumbCommon) Start() error { |
1767 | + d.startCount++ |
1768 | + return d.err |
1769 | +} |
1770 | +func (d *dumbCommon) Stop() { |
1771 | + d.stopCount++ |
1772 | +} |
1773 | +func (d *dumbCommon) IsRunning() bool { |
1774 | + d.runningCount++ |
1775 | + return d.running |
1776 | +} |
1777 | + |
1778 | +type dumbPush struct { |
1779 | + dumbCommon |
1780 | + unregCount int |
1781 | + unregArgs []string |
1782 | +} |
1783 | + |
1784 | +func (d *dumbPush) Unregister(appId string) error { |
1785 | + d.unregCount++ |
1786 | + d.unregArgs = append(d.unregArgs, appId) |
1787 | + return d.err |
1788 | +} |
1789 | + |
1790 | +type postArgs struct { |
1791 | + app *click.AppId |
1792 | + nid string |
1793 | + payload json.RawMessage |
1794 | +} |
1795 | + |
1796 | +type dumbPostal struct { |
1797 | + dumbCommon |
1798 | + bcastCount int |
1799 | + postCount int |
1800 | + postArgs []postArgs |
1801 | +} |
1802 | + |
1803 | +func (d *dumbPostal) Post(app *click.AppId, nid string, payload json.RawMessage) error { |
1804 | + d.postCount++ |
1805 | + if app.Application == "ubuntu-system-settings" { |
1806 | + d.bcastCount++ |
1807 | + } |
1808 | + d.postArgs = append(d.postArgs, postArgs{app, nid, payload}) |
1809 | + return d.err |
1810 | +} |
1811 | + |
1812 | +var _ PostalService = (*dumbPostal)(nil) |
1813 | +var _ PushService = (*dumbPush)(nil) |
1814 | + |
1815 | type clientSuite struct { |
1816 | timeouts []time.Duration |
1817 | configPath string |
1818 | @@ -188,25 +246,12 @@ |
1819 | func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) { |
1820 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1821 | |
1822 | - // keep these in the same order as in the client struct, for sanity |
1823 | - c.Check(cli.notificationsEndp, IsNil) |
1824 | - c.Check(cli.urlDispatcherEndp, IsNil) |
1825 | c.Check(cli.connectivityEndp, IsNil) |
1826 | - c.Check(cli.emblemcounterEndp, IsNil) |
1827 | - c.Check(cli.hapticEndp, IsNil) |
1828 | c.Check(cli.systemImageEndp, IsNil) |
1829 | - c.Check(cli.pushServiceEndpoint, IsNil) |
1830 | - c.Check(cli.postalServiceEndpoint, IsNil) |
1831 | err := cli.configure() |
1832 | c.Assert(err, IsNil) |
1833 | - c.Check(cli.notificationsEndp, NotNil) |
1834 | - c.Check(cli.urlDispatcherEndp, NotNil) |
1835 | c.Check(cli.connectivityEndp, NotNil) |
1836 | - c.Check(cli.emblemcounterEndp, NotNil) |
1837 | - c.Check(cli.hapticEndp, NotNil) |
1838 | c.Check(cli.systemImageEndp, NotNil) |
1839 | - c.Check(cli.pushServiceEndpoint, NotNil) |
1840 | - c.Check(cli.postalServiceEndpoint, NotNil) |
1841 | } |
1842 | |
1843 | func (cs *clientSuite) TestConfigureSetsUpConnCh(c *C) { |
1844 | @@ -223,7 +268,9 @@ |
1845 | err := cli.configure() |
1846 | c.Assert(err, IsNil) |
1847 | c.Assert(cli.unregisterCh, NotNil) |
1848 | - c.Assert(cli.hasPackage("com.bar.baz_foo"), Equals, false) |
1849 | + app, err := click.ParseAppId("com.bar.baz_foo") |
1850 | + c.Assert(err, IsNil) |
1851 | + c.Assert(cli.installedChecker.Installed(app, false), Equals, false) |
1852 | } |
1853 | |
1854 | func (cs *clientSuite) TestConfigureBailsOnBadFilename(c *C) { |
1855 | @@ -279,29 +326,60 @@ |
1856 | addresses checking tests |
1857 | ******************************************************************/ |
1858 | |
1859 | +type testInstalledChecker func(*click.AppId, bool) bool |
1860 | + |
1861 | +func (tic testInstalledChecker) Installed(app *click.AppId, setVersion bool) bool { |
1862 | + return tic(app, setVersion) |
1863 | +} |
1864 | + |
1865 | +var ( |
1866 | + appId1 = "com.example.app1_app1" |
1867 | + appId2 = "com.example.app2_app2" |
1868 | + appIdHello = "com.example.test_hello" |
1869 | + app1 = clickhelp.MustParseAppId(appId1) |
1870 | + app2 = clickhelp.MustParseAppId(appId2) |
1871 | + appHello = clickhelp.MustParseAppId(appIdHello) |
1872 | +) |
1873 | + |
1874 | func (cs *clientSuite) TestCheckForAddressee(c *C) { |
1875 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1876 | - cli.unregisterCh = make(chan string, 5) |
1877 | + cli.log = cs.log |
1878 | + cli.unregisterCh = make(chan *click.AppId, 5) |
1879 | cli.StartAddresseeBatch() |
1880 | calls := 0 |
1881 | - cli.hasPackage = func(appId string) bool { |
1882 | + cli.installedChecker = testInstalledChecker(func(app *click.AppId, setVersion bool) bool { |
1883 | calls++ |
1884 | - if appId == "app1" { |
1885 | + c.Assert(setVersion, Equals, true) |
1886 | + if app.Original() == appId1 { |
1887 | return false |
1888 | } |
1889 | return true |
1890 | - } |
1891 | - c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: "app1"}), Equals, false) |
1892 | + }) |
1893 | + |
1894 | + c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: "bad-id"}), IsNil) |
1895 | + c.Check(calls, Equals, 0) |
1896 | + c.Assert(cli.unregisterCh, HasLen, 0) |
1897 | + c.Check(cs.log.Captured(), Matches, `DEBUG notification "" for invalid app id "bad-id".\n`) |
1898 | + cs.log.ResetCapture() |
1899 | + |
1900 | + c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: appId1}), IsNil) |
1901 | c.Check(calls, Equals, 1) |
1902 | c.Assert(cli.unregisterCh, HasLen, 1) |
1903 | - c.Check(<-cli.unregisterCh, Equals, "app1") |
1904 | - c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: "app2"}), Equals, true) |
1905 | - c.Check(calls, Equals, 2) |
1906 | - c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: "app1"}), Equals, false) |
1907 | - c.Check(calls, Equals, 2) |
1908 | - c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: "app2"}), Equals, true) |
1909 | + c.Check(<-cli.unregisterCh, DeepEquals, app1) |
1910 | + c.Check(cs.log.Captured(), Matches, `DEBUG notification "" for missing app id "com.example.app1_app1".\n`) |
1911 | + cs.log.ResetCapture() |
1912 | + |
1913 | + c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: appId2}), DeepEquals, app2) |
1914 | + c.Check(calls, Equals, 2) |
1915 | + c.Check(cs.log.Captured(), Matches, "") |
1916 | + cs.log.ResetCapture() |
1917 | + |
1918 | + c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: appId1}), IsNil) |
1919 | + c.Check(calls, Equals, 2) |
1920 | + c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: appId2}), DeepEquals, app2) |
1921 | c.Check(calls, Equals, 2) |
1922 | c.Check(cli.unregisterCh, HasLen, 0) |
1923 | + c.Check(cs.log.Captured(), Matches, "") |
1924 | } |
1925 | |
1926 | /***************************************************************** |
1927 | @@ -358,9 +436,10 @@ |
1928 | c.Assert(err, IsNil) |
1929 | cli.deviceId = "zoo" |
1930 | expected := &service.PushServiceSetup{ |
1931 | - DeviceId: "zoo", |
1932 | - AuthGetter: func(string) string { return "" }, |
1933 | - RegURL: helpers.ParseURL("reg://"), |
1934 | + DeviceId: "zoo", |
1935 | + AuthGetter: func(string) string { return "" }, |
1936 | + RegURL: helpers.ParseURL("reg://"), |
1937 | + InstalledChecker: cli.installedChecker, |
1938 | } |
1939 | // sanity check that we are looking at all fields |
1940 | vExpected := reflect.ValueOf(expected).Elem() |
1941 | @@ -396,66 +475,59 @@ |
1942 | startService tests |
1943 | ******************************************************************/ |
1944 | |
1945 | -func (cs *clientSuite) TestStartServiceWorks(c *C) { |
1946 | - cs.writeTestConfig(map[string]interface{}{ |
1947 | - "auth_helper": helpers.ScriptAbsPath("dummyauth.sh"), |
1948 | - }) |
1949 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1950 | - err := cli.configure() |
1951 | - c.Assert(err, IsNil) |
1952 | - cli.log = cs.log |
1953 | - cli.deviceId = "fake-id" |
1954 | - cli.pushServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil) |
1955 | - cli.postalServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil) |
1956 | - c.Check(cli.pushService, IsNil) |
1957 | - c.Check(cli.startService(), IsNil) |
1958 | - c.Assert(cli.pushService, NotNil) |
1959 | - pushService := cli.pushService.(*service.PushService) |
1960 | - c.Check(pushService.IsRunning(), Equals, true) |
1961 | - c.Assert(cli.setupPostalService(), IsNil) |
1962 | - c.Assert(cli.startPostalService(), IsNil) |
1963 | - c.Check(cli.postalService.IsRunning(), Equals, true) |
1964 | - pushService.Stop() |
1965 | - cli.postalService.Stop() |
1966 | -} |
1967 | - |
1968 | -func (cs *clientSuite) TestStartServiceSetupError(c *C) { |
1969 | +func (cs *clientSuite) TestStartPushServiceCallsStart(c *C) { |
1970 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1971 | + d := new(dumbPush) |
1972 | + cli.pushService = d |
1973 | + c.Check(cli.startPushService(), IsNil) |
1974 | + c.Check(d.startCount, Equals, 1) |
1975 | +} |
1976 | + |
1977 | +func (cs *clientSuite) TestStartPostServiceCallsStart(c *C) { |
1978 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1979 | + d := new(dumbPostal) |
1980 | + cli.postalService = d |
1981 | + c.Check(cli.startPostalService(), IsNil) |
1982 | + c.Check(d.startCount, Equals, 1) |
1983 | +} |
1984 | + |
1985 | +func (cs *clientSuite) TestSetupPushServiceSetupError(c *C) { |
1986 | cs.writeTestConfig(map[string]interface{}{ |
1987 | "registration_url": "%gh", |
1988 | }) |
1989 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1990 | err := cli.configure() |
1991 | c.Assert(err, IsNil) |
1992 | - err = cli.startService() |
1993 | + err = cli.setupPushService() |
1994 | c.Check(err, ErrorMatches, "cannot parse registration url:.*") |
1995 | } |
1996 | |
1997 | -func (cs *clientSuite) TestStartServiceErrorsOnNilLog(c *C) { |
1998 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
1999 | - c.Check(cli.log, IsNil) |
2000 | - c.Check(cli.startService(), NotNil) |
2001 | - c.Assert(cli.setupPostalService(), IsNil) |
2002 | - c.Assert(cli.startPostalService(), NotNil) |
2003 | -} |
2004 | - |
2005 | -func (cs *clientSuite) TestStartServiceErrorsOnBusDialPushFail(c *C) { |
2006 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2007 | - cli.log = cs.log |
2008 | - cli.pushServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil) |
2009 | - cli.postalServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil) |
2010 | - c.Check(cli.startService(), NotNil) |
2011 | - c.Assert(cli.setupPostalService(), IsNil) |
2012 | - c.Assert(cli.startPostalService(), NotNil) |
2013 | -} |
2014 | - |
2015 | -func (cs *clientSuite) TestStartServiceErrorsOnBusDialPostalFail(c *C) { |
2016 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2017 | - cli.log = cs.log |
2018 | - cli.pushServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil) |
2019 | - cli.postalServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil) |
2020 | - c.Check(cli.startService(), IsNil) |
2021 | - c.Assert(cli.setupPostalService(), IsNil) |
2022 | - c.Assert(cli.startPostalService(), NotNil) |
2023 | +func (cs *clientSuite) TestSetupPushService(c *C) { |
2024 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2025 | + c.Assert(cli.configure(), IsNil) |
2026 | + c.Check(cli.pushService, IsNil) |
2027 | + c.Check(cli.setupPushService(), IsNil) |
2028 | + c.Check(cli.pushService, NotNil) |
2029 | +} |
2030 | + |
2031 | +func (cs *clientSuite) TestStartPushErrorsOnPushStartError(c *C) { |
2032 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2033 | + d := new(dumbPush) |
2034 | + err := errors.New("potato") |
2035 | + d.err = err |
2036 | + cli.pushService = d |
2037 | + c.Check(cli.startPushService(), Equals, err) |
2038 | + c.Check(d.startCount, Equals, 1) |
2039 | +} |
2040 | + |
2041 | +func (cs *clientSuite) TestStartPostalErrorsOnPostalStartError(c *C) { |
2042 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2043 | + d := new(dumbPostal) |
2044 | + err := errors.New("potato") |
2045 | + d.err = err |
2046 | + cli.postalService = d |
2047 | + c.Check(cli.startPostalService(), Equals, err) |
2048 | + c.Check(d.startCount, Equals, 1) |
2049 | } |
2050 | |
2051 | /***************************************************************** |
2052 | @@ -499,11 +571,6 @@ |
2053 | defer ts.Close() |
2054 | |
2055 | // testing endpoints |
2056 | - nCond := condition.Fail2Work(3) |
2057 | - nEndp := testibus.NewMultiValuedTestingEndpoint(nCond, condition.Work(true), |
2058 | - []interface{}{uint32(1), "hello"}) |
2059 | - uCond := condition.Fail2Work(5) |
2060 | - uEndp := testibus.NewTestingEndpoint(uCond, condition.Work(false)) |
2061 | cCond := condition.Fail2Work(7) |
2062 | cEndp := testibus.NewTestingEndpoint(cCond, condition.Work(true), |
2063 | uint32(networkmanager.ConnectedGlobal), |
2064 | @@ -511,46 +578,26 @@ |
2065 | siCond := condition.Fail2Work(2) |
2066 | siEndp := testibus.NewMultiValuedTestingEndpoint(siCond, condition.Work(true), []interface{}{int32(101), "mako", "daily", "Unknown", map[string]string{}}) |
2067 | testibus.SetWatchTicker(cEndp, make(chan bool)) |
2068 | - ecCond := condition.Fail2Work(13) |
2069 | - ecEndp := testibus.NewTestingEndpoint(ecCond, condition.Work(true)) |
2070 | - haCond := condition.Fail2Work(2) |
2071 | - haEndp := testibus.NewTestingEndpoint(haCond, condition.Work(true)) |
2072 | // ok, create the thing |
2073 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2074 | cli.log = cs.log |
2075 | err := cli.configure() |
2076 | c.Assert(err, IsNil) |
2077 | - // the user actions channel has not been set up |
2078 | - c.Check(cli.actionsCh, IsNil) |
2079 | |
2080 | // and stomp on things for testing |
2081 | cli.config.ConnectivityConfig.ConnectivityCheckURL = ts.URL |
2082 | cli.config.ConnectivityConfig.ConnectivityCheckMD5 = staticHash |
2083 | - cli.notificationsEndp = nEndp |
2084 | - cli.urlDispatcherEndp = uEndp |
2085 | cli.connectivityEndp = cEndp |
2086 | - cli.emblemcounterEndp = ecEndp |
2087 | - cli.hapticEndp = haEndp |
2088 | cli.systemImageEndp = siEndp |
2089 | |
2090 | c.Assert(cli.takeTheBus(), IsNil) |
2091 | - c.Assert(cli.setupPostalService(), IsNil) |
2092 | - c.Assert(cli.takePostalServiceBus(), IsNil) |
2093 | - // the notifications and urldispatcher endpoints retried until connected |
2094 | - c.Check(nCond.OK(), Equals, true) |
2095 | - c.Check(uCond.OK(), Equals, true) |
2096 | - // the user actions channel has now been set up |
2097 | - c.Check(cli.actionsCh, NotNil) |
2098 | + |
2099 | c.Check(takeNextBool(cli.connCh), Equals, false) |
2100 | c.Check(takeNextBool(cli.connCh), Equals, true) |
2101 | // the connectivity endpoint retried until connected |
2102 | c.Check(cCond.OK(), Equals, true) |
2103 | // the systemimage endpoint retried until connected |
2104 | c.Check(siCond.OK(), Equals, true) |
2105 | - // the emblemcounter endpoint retried until connected |
2106 | - c.Check(ecCond.OK(), Equals, true) |
2107 | - // the haptic endpoint retried until connected |
2108 | - c.Check(haCond.OK(), Equals, true) |
2109 | } |
2110 | |
2111 | // takeTheBus can, in fact, fail |
2112 | @@ -559,21 +606,13 @@ |
2113 | err := cli.configure() |
2114 | cli.log = cs.log |
2115 | c.Assert(err, IsNil) |
2116 | - // the user actions channel has not been set up |
2117 | - c.Check(cli.actionsCh, IsNil) |
2118 | |
2119 | // and stomp on things for testing |
2120 | - cli.notificationsEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
2121 | - cli.urlDispatcherEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
2122 | cli.connectivityEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
2123 | - cli.emblemcounterEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
2124 | - cli.hapticEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
2125 | cli.systemImageEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
2126 | |
2127 | c.Check(cli.takeTheBus(), NotNil) |
2128 | c.Assert(cli.setupPostalService(), IsNil) |
2129 | - c.Assert(cli.takePostalServiceBus(), NotNil) |
2130 | - c.Check(cli.actionsCh, IsNil) |
2131 | } |
2132 | |
2133 | /***************************************************************** |
2134 | @@ -763,41 +802,40 @@ |
2135 | func (cs *clientSuite) TestHandleBroadcastNotification(c *C) { |
2136 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2137 | cli.systemImageInfo = siInfoRes |
2138 | - endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
2139 | - cli.notificationsEndp = endp |
2140 | cli.log = cs.log |
2141 | - cli.postalServiceEndpoint = testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
2142 | - cli.setupPostalService() |
2143 | + d := new(dumbPostal) |
2144 | + cli.postalService = d |
2145 | c.Check(cli.handleBroadcastNotification(positiveBroadcastNotification), IsNil) |
2146 | - // check we sent the notification |
2147 | - args := testibus.GetCallArgs(endp) |
2148 | - c.Assert(args, HasLen, 1) |
2149 | - c.Check(args[0].Member, Equals, "Notify") |
2150 | - c.Check(cs.log.Captured(), Matches, `(?s).* got notification id \d+\s*`) |
2151 | + // we dun posted |
2152 | + c.Check(d.bcastCount, Equals, 1) |
2153 | + c.Assert(d.postArgs, HasLen, 1) |
2154 | + expectedApp, _ := click.ParseAppId("_ubuntu-system-settings") |
2155 | + c.Check(d.postArgs[0].app, DeepEquals, expectedApp) |
2156 | + expectedData, _ := json.Marshal(positiveBroadcastNotification.Decoded[0]) |
2157 | + c.Check([]byte(d.postArgs[0].payload), DeepEquals, expectedData) |
2158 | } |
2159 | |
2160 | func (cs *clientSuite) TestHandleBroadcastNotificationNothingToDo(c *C) { |
2161 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2162 | cli.systemImageInfo = siInfoRes |
2163 | - endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
2164 | - cli.notificationsEndp = endp |
2165 | cli.log = cs.log |
2166 | + d := new(dumbPostal) |
2167 | + cli.postalService = d |
2168 | c.Check(cli.handleBroadcastNotification(negativeBroadcastNotification), IsNil) |
2169 | - // check we sent the notification |
2170 | - args := testibus.GetCallArgs(endp) |
2171 | - c.Assert(args, HasLen, 0) |
2172 | - c.Check(cs.log.Captured(), Matches, "") |
2173 | + // we not dun no posted |
2174 | + c.Check(d.bcastCount, Equals, 0) |
2175 | } |
2176 | |
2177 | func (cs *clientSuite) TestHandleBroadcastNotificationFail(c *C) { |
2178 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2179 | cli.systemImageInfo = siInfoRes |
2180 | cli.log = cs.log |
2181 | - endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) |
2182 | - cli.notificationsEndp = endp |
2183 | - cli.postalServiceEndpoint = testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
2184 | - cli.setupPostalService() |
2185 | - c.Check(cli.handleBroadcastNotification(positiveBroadcastNotification), NotNil) |
2186 | + d := new(dumbPostal) |
2187 | + err := errors.New("potato") |
2188 | + d.err = err |
2189 | + cli.postalService = d |
2190 | + c.Check(cli.handleBroadcastNotification(positiveBroadcastNotification), Equals, err) |
2191 | + c.Check(d.bcastCount, Equals, 1) |
2192 | } |
2193 | |
2194 | /***************************************************************** |
2195 | @@ -805,60 +843,34 @@ |
2196 | ******************************************************************/ |
2197 | |
2198 | var payload = `{"message": "aGVsbG8=", "notification": {"card": {"icon": "icon-value", "summary": "summary-value", "body": "body-value", "actions": []}}}` |
2199 | -var notif = &protocol.Notification{AppId: "com.example.test_hello", Payload: []byte(payload), MsgId: "42"} |
2200 | +var notif = &protocol.Notification{AppId: appIdHello, Payload: []byte(payload), MsgId: "42"} |
2201 | |
2202 | func (cs *clientSuite) TestHandleUcastNotification(c *C) { |
2203 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2204 | - postEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1)) |
2205 | - cli.log = cs.log |
2206 | - cli.postalServiceEndpoint = postEndp |
2207 | - c.Assert(cli.setupPostalService(), IsNil) |
2208 | - c.Assert(cli.startPostalService(), IsNil) |
2209 | - c.Check(cli.handleUnicastNotification(notif), IsNil) |
2210 | - // check we sent the notification |
2211 | - args := testibus.GetCallArgs(postEndp) |
2212 | - c.Assert(len(args), Not(Equals), 0) |
2213 | - c.Check(args[len(args)-1].Member, Equals, "::Signal") |
2214 | - c.Check(cs.log.Captured(), Matches, `(?m).*sending notification "42" for "com.example.test_hello".*`) |
2215 | -} |
2216 | - |
2217 | -func (cs *clientSuite) TestHandleUcastFailsOnBadAppId(c *C) { |
2218 | - notif := &protocol.Notification{AppId: "bad-app-id", MsgId: "-1"} |
2219 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2220 | - cli.log = cs.log |
2221 | - c.Check(cli.handleUnicastNotification(notif), ErrorMatches, "invalid app id in notification") |
2222 | -} |
2223 | - |
2224 | -/***************************************************************** |
2225 | - handleClick tests |
2226 | -******************************************************************/ |
2227 | - |
2228 | -var ACTION_ID_BROADCAST = service.ACTION_ID_PREFIX + service.SystemUpdateUrl + service.ACTION_ID_SUFFIX |
2229 | - |
2230 | -func (cs *clientSuite) TestHandleClick(c *C) { |
2231 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2232 | - cli.log = cs.log |
2233 | - endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
2234 | - cli.urlDispatcherEndp = endp |
2235 | - // check we don't fail on something random |
2236 | - c.Check(cli.handleClick("something random"), IsNil) |
2237 | - // ... but we don't send anything either |
2238 | - args := testibus.GetCallArgs(endp) |
2239 | - c.Assert(args, HasLen, 0) |
2240 | - // check we worked with the right action id |
2241 | - c.Check(cli.handleClick(ACTION_ID_BROADCAST), IsNil) |
2242 | - // check we sent the notification |
2243 | - args = testibus.GetCallArgs(endp) |
2244 | - c.Assert(args, HasLen, 1) |
2245 | - c.Check(args[0].Member, Equals, "DispatchURL") |
2246 | - c.Check(args[0].Args, DeepEquals, []interface{}{service.SystemUpdateUrl}) |
2247 | - // check we worked with the right action id |
2248 | - c.Check(cli.handleClick(service.ACTION_ID_PREFIX+"foo"), IsNil) |
2249 | - // check we sent the notification |
2250 | - args = testibus.GetCallArgs(endp) |
2251 | - c.Assert(args, HasLen, 2) |
2252 | - c.Check(args[1].Member, Equals, "DispatchURL") |
2253 | - c.Check(args[1].Args, DeepEquals, []interface{}{"foo"}) |
2254 | + cli.log = cs.log |
2255 | + d := new(dumbPostal) |
2256 | + cli.postalService = d |
2257 | + |
2258 | + c.Check(cli.handleUnicastNotification(session.AddressedNotification{appHello, notif}), IsNil) |
2259 | + // check we sent the notification |
2260 | + c.Check(d.postCount, Equals, 1) |
2261 | + c.Assert(d.postArgs, HasLen, 1) |
2262 | + c.Check(d.postArgs[0].app, Equals, appHello) |
2263 | + c.Check(d.postArgs[0].nid, Equals, notif.MsgId) |
2264 | + c.Check(d.postArgs[0].payload, DeepEquals, notif.Payload) |
2265 | +} |
2266 | + |
2267 | +func (cs *clientSuite) TestHandleUcastNotificationError(c *C) { |
2268 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2269 | + cli.log = cs.log |
2270 | + d := new(dumbPostal) |
2271 | + cli.postalService = d |
2272 | + fail := errors.New("fail") |
2273 | + d.err = fail |
2274 | + |
2275 | + c.Check(cli.handleUnicastNotification(session.AddressedNotification{appHello, notif}), Equals, fail) |
2276 | + // check we sent the notification |
2277 | + c.Check(d.postCount, Equals, 1) |
2278 | } |
2279 | |
2280 | /***************************************************************** |
2281 | @@ -882,41 +894,43 @@ |
2282 | func (cs *clientSuite) TestHandleUnregister(c *C) { |
2283 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2284 | cli.log = cs.log |
2285 | - cli.hasPackage = func(appId string) bool { |
2286 | - c.Check(appId, Equals, "app1") |
2287 | + cli.installedChecker = testInstalledChecker(func(app *click.AppId, setVersion bool) bool { |
2288 | + c.Check(setVersion, Equals, false) |
2289 | + c.Check(app.Original(), Equals, appId1) |
2290 | return false |
2291 | - } |
2292 | + }) |
2293 | ps := &testPushService{} |
2294 | cli.pushService = ps |
2295 | - cli.handleUnregister("app1") |
2296 | - c.Assert(ps.unregistered, Equals, "app1") |
2297 | - c.Check(cs.log.Captured(), Equals, "") |
2298 | + cli.handleUnregister(app1) |
2299 | + c.Assert(ps.unregistered, Equals, appId1) |
2300 | + c.Check(cs.log.Captured(), Equals, "DEBUG unregistered token for com.example.app1_app1\n") |
2301 | } |
2302 | |
2303 | func (cs *clientSuite) TestHandleUnregisterNop(c *C) { |
2304 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2305 | cli.log = cs.log |
2306 | - cli.hasPackage = func(appId string) bool { |
2307 | - c.Check(appId, Equals, "app1") |
2308 | + cli.installedChecker = testInstalledChecker(func(app *click.AppId, setVersion bool) bool { |
2309 | + c.Check(setVersion, Equals, false) |
2310 | + c.Check(app.Original(), Equals, appId1) |
2311 | return true |
2312 | - } |
2313 | + }) |
2314 | ps := &testPushService{} |
2315 | cli.pushService = ps |
2316 | - cli.handleUnregister("app1") |
2317 | + cli.handleUnregister(app1) |
2318 | c.Assert(ps.unregistered, Equals, "") |
2319 | } |
2320 | |
2321 | func (cs *clientSuite) TestHandleUnregisterError(c *C) { |
2322 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2323 | cli.log = cs.log |
2324 | - cli.hasPackage = func(appId string) bool { |
2325 | + cli.installedChecker = testInstalledChecker(func(app *click.AppId, setVersion bool) bool { |
2326 | return false |
2327 | - } |
2328 | + }) |
2329 | fail := errors.New("BAD") |
2330 | ps := &testPushService{err: fail} |
2331 | cli.pushService = ps |
2332 | - cli.handleUnregister("app1") |
2333 | - c.Check(cs.log.Captured(), Matches, "ERROR unregistering app1: BAD\n") |
2334 | + cli.handleUnregister(app1) |
2335 | + c.Check(cs.log.Captured(), Matches, "ERROR unregistering com.example.app1_app1: BAD\n") |
2336 | } |
2337 | |
2338 | /***************************************************************** |
2339 | @@ -924,11 +938,10 @@ |
2340 | ******************************************************************/ |
2341 | |
2342 | var nopConn = func(bool) {} |
2343 | -var nopClick = func(string) error { return nil } |
2344 | var nopBcast = func(*session.BroadcastNotification) error { return nil } |
2345 | -var nopUcast = func(*protocol.Notification) error { return nil } |
2346 | +var nopUcast = func(session.AddressedNotification) error { return nil } |
2347 | var nopError = func(error) {} |
2348 | -var nopUnregister = func(string) {} |
2349 | +var nopUnregister = func(*click.AppId) {} |
2350 | |
2351 | func (cs *clientSuite) TestDoLoopConn(c *C) { |
2352 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2353 | @@ -939,21 +952,7 @@ |
2354 | c.Assert(cli.initSession(), IsNil) |
2355 | |
2356 | ch := make(chan bool, 1) |
2357 | - go cli.doLoop(func(bool) { ch <- true }, nopClick, nopBcast, nopUcast, nopError, nopUnregister) |
2358 | - c.Check(takeNextBool(ch), Equals, true) |
2359 | -} |
2360 | - |
2361 | -func (cs *clientSuite) TestDoLoopClick(c *C) { |
2362 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2363 | - cli.log = cs.log |
2364 | - cli.systemImageInfo = siInfoRes |
2365 | - c.Assert(cli.initSession(), IsNil) |
2366 | - aCh := make(chan notifications.RawActionReply, 1) |
2367 | - aCh <- notifications.RawActionReply{} |
2368 | - cli.actionsCh = aCh |
2369 | - |
2370 | - ch := make(chan bool, 1) |
2371 | - go cli.doLoop(nopConn, func(_ string) error { ch <- true; return nil }, nopBcast, nopUcast, nopError, nopUnregister) |
2372 | + go cli.doLoop(func(bool) { ch <- true }, nopBcast, nopUcast, nopError, nopUnregister) |
2373 | c.Check(takeNextBool(ch), Equals, true) |
2374 | } |
2375 | |
2376 | @@ -966,7 +965,7 @@ |
2377 | cli.session.BroadcastCh <- &session.BroadcastNotification{} |
2378 | |
2379 | ch := make(chan bool, 1) |
2380 | - go cli.doLoop(nopConn, nopClick, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopError, nopUnregister) |
2381 | + go cli.doLoop(nopConn, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopError, nopUnregister) |
2382 | c.Check(takeNextBool(ch), Equals, true) |
2383 | } |
2384 | |
2385 | @@ -975,11 +974,11 @@ |
2386 | cli.log = cs.log |
2387 | cli.systemImageInfo = siInfoRes |
2388 | c.Assert(cli.initSession(), IsNil) |
2389 | - cli.session.NotificationsCh = make(chan *protocol.Notification, 1) |
2390 | - cli.session.NotificationsCh <- &protocol.Notification{} |
2391 | + cli.session.NotificationsCh = make(chan session.AddressedNotification, 1) |
2392 | + cli.session.NotificationsCh <- session.AddressedNotification{} |
2393 | |
2394 | ch := make(chan bool, 1) |
2395 | - go cli.doLoop(nopConn, nopClick, nopBcast, func(*protocol.Notification) error { ch <- true; return nil }, nopError, nopUnregister) |
2396 | + go cli.doLoop(nopConn, nopBcast, func(session.AddressedNotification) error { ch <- true; return nil }, nopError, nopUnregister) |
2397 | c.Check(takeNextBool(ch), Equals, true) |
2398 | } |
2399 | |
2400 | @@ -992,7 +991,7 @@ |
2401 | cli.session.ErrCh <- nil |
2402 | |
2403 | ch := make(chan bool, 1) |
2404 | - go cli.doLoop(nopConn, nopClick, nopBcast, nopUcast, func(error) { ch <- true }, nopUnregister) |
2405 | + go cli.doLoop(nopConn, nopBcast, nopUcast, func(error) { ch <- true }, nopUnregister) |
2406 | c.Check(takeNextBool(ch), Equals, true) |
2407 | } |
2408 | |
2409 | @@ -1001,11 +1000,11 @@ |
2410 | cli.log = cs.log |
2411 | cli.systemImageInfo = siInfoRes |
2412 | c.Assert(cli.initSession(), IsNil) |
2413 | - cli.unregisterCh = make(chan string, 1) |
2414 | - cli.unregisterCh <- "app1" |
2415 | + cli.unregisterCh = make(chan *click.AppId, 1) |
2416 | + cli.unregisterCh <- app1 |
2417 | |
2418 | ch := make(chan bool, 1) |
2419 | - go cli.doLoop(nopConn, nopClick, nopBcast, nopUcast, nopError, func(appId string) { c.Check(appId, Equals, "app1"); ch <- true }) |
2420 | + go cli.doLoop(nopConn, nopBcast, nopUcast, nopError, func(app *click.AppId) { c.Check(app.Original(), Equals, appId1); ch <- true }) |
2421 | c.Check(takeNextBool(ch), Equals, true) |
2422 | } |
2423 | |
2424 | @@ -1044,17 +1043,14 @@ |
2425 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2426 | cli.connCh = make(chan bool) |
2427 | cli.sessionConnectedCh = make(chan uint32) |
2428 | - aCh := make(chan notifications.RawActionReply, 1) |
2429 | - cli.actionsCh = aCh |
2430 | cli.log = cs.log |
2431 | - cli.notificationsEndp = testibus.NewMultiValuedTestingEndpoint(condition.Work(true), |
2432 | - condition.Work(true), []interface{}{uint32(1), "hello"}) |
2433 | - cli.urlDispatcherEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
2434 | cli.connectivityEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), |
2435 | uint32(networkmanager.ConnectedGlobal)) |
2436 | cli.systemImageInfo = siInfoRes |
2437 | - cli.postalServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1)) |
2438 | - cli.setupPostalService() |
2439 | + d := new(dumbPostal) |
2440 | + cli.postalService = d |
2441 | + c.Assert(cli.startPostalService(), IsNil) |
2442 | + |
2443 | c.Assert(cli.initSession(), IsNil) |
2444 | |
2445 | cli.session.BroadcastCh = make(chan *session.BroadcastNotification) |
2446 | @@ -1073,13 +1069,6 @@ |
2447 | tick() |
2448 | c.Check(cs.log.Captured(), Matches, "(?ms).*Session connected after 42 attempts$") |
2449 | |
2450 | - // * actionsCh to the click handler/url dispatcher |
2451 | - aCh <- notifications.RawActionReply{ActionId: ACTION_ID_BROADCAST} |
2452 | - tick() |
2453 | - uargs := testibus.GetCallArgs(cli.urlDispatcherEndp) |
2454 | - c.Assert(uargs, HasLen, 1) |
2455 | - c.Check(uargs[0].Member, Equals, "DispatchURL") |
2456 | - |
2457 | // loop() should have connected: |
2458 | // * connCh to the connectivity checker |
2459 | c.Check(cli.hasConnectivity, Equals, false) |
2460 | @@ -1091,10 +1080,10 @@ |
2461 | c.Check(cli.hasConnectivity, Equals, false) |
2462 | |
2463 | // * session.BroadcastCh to the notifications handler |
2464 | + c.Check(d.bcastCount, Equals, 0) |
2465 | cli.session.BroadcastCh <- positiveBroadcastNotification |
2466 | tick() |
2467 | - nargs := testibus.GetCallArgs(cli.notificationsEndp) |
2468 | - c.Check(nargs, HasLen, 1) |
2469 | + c.Check(d.bcastCount, Equals, 1) |
2470 | |
2471 | // * session.ErrCh to the error handler |
2472 | cli.session.ErrCh <- nil |
2473 | @@ -1117,6 +1106,7 @@ |
2474 | } |
2475 | |
2476 | func (cs *clientSuite) TestStart(c *C) { |
2477 | + c.Skip("no dbus") |
2478 | if !cs.hasDbus() { |
2479 | c.Skip("no dbus") |
2480 | } |
2481 | @@ -1132,7 +1122,7 @@ |
2482 | // no session, |
2483 | c.Check(cli.session, IsNil) |
2484 | // no bus, |
2485 | - c.Check(cli.notificationsEndp, IsNil) |
2486 | + c.Check(cli.systemImageEndp, IsNil) |
2487 | // no nuthin'. |
2488 | |
2489 | // so we start, |
2490 | @@ -1147,7 +1137,7 @@ |
2491 | // and a session, |
2492 | c.Check(cli.session, NotNil) |
2493 | // and a bus, |
2494 | - c.Check(cli.notificationsEndp, NotNil) |
2495 | + c.Check(cli.systemImageEndp, NotNil) |
2496 | // and a service, |
2497 | c.Check(cli.pushService, NotNil) |
2498 | // and everthying us just peachy! |
2499 | |
2500 | === modified file 'client/service/common.go' |
2501 | --- client/service/common.go 2014-07-04 19:21:42 +0000 |
2502 | +++ client/service/common.go 2014-07-21 14:14:04 +0000 |
2503 | @@ -30,10 +30,11 @@ |
2504 | ) |
2505 | |
2506 | type DBusService struct { |
2507 | - lock sync.RWMutex |
2508 | - state ServiceState |
2509 | - Log logger.Logger |
2510 | - Bus bus.Endpoint |
2511 | + lock sync.RWMutex |
2512 | + state ServiceState |
2513 | + installedChecker click.InstalledChecker |
2514 | + Log logger.Logger |
2515 | + Bus bus.Endpoint |
2516 | } |
2517 | |
2518 | // the service can be in a numnber of states |
2519 | @@ -50,6 +51,7 @@ |
2520 | ErrAlreadyStarted = errors.New("already started") |
2521 | ErrBadArgCount = errors.New("wrong number of arguments") |
2522 | ErrBadArgType = errors.New("bad argument type") |
2523 | + ErrBadJSON = errors.New("bad json data") |
2524 | ErrBadAppId = errors.New("package must be prefix of app id") |
2525 | ) |
2526 | |
2527 | @@ -105,17 +107,21 @@ |
2528 | // grabDBusPackageAndAppId() extracts the appId from a dbus-provided |
2529 | // []interface{}, and checks it against the package in the last |
2530 | // element of the dbus path. |
2531 | -func grabDBusPackageAndAppId(path string, args []interface{}, numExtra int) (pkgname string, appId string, err error) { |
2532 | +func (svc *DBusService) grabDBusPackageAndAppId(path string, args []interface{}, numExtra int) (app *click.AppId, err error) { |
2533 | if len(args) != 1+numExtra { |
2534 | - return "", "", ErrBadArgCount |
2535 | + return nil, ErrBadArgCount |
2536 | } |
2537 | - appId, ok := args[0].(string) |
2538 | + id, ok := args[0].(string) |
2539 | if !ok { |
2540 | - return "", "", ErrBadArgType |
2541 | - } |
2542 | - pkgname = string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:]))) |
2543 | - if !click.AppInPackage(appId, pkgname) { |
2544 | - return "", "", ErrBadAppId |
2545 | + return nil, ErrBadArgType |
2546 | + } |
2547 | + pkgname := string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:]))) |
2548 | + app, err = click.ParseAndVerifyAppId(id, svc.installedChecker) |
2549 | + if err != nil { |
2550 | + return nil, ErrBadAppId |
2551 | + } |
2552 | + if !app.InPackage(pkgname) { |
2553 | + return nil, ErrBadAppId |
2554 | } |
2555 | return |
2556 | } |
2557 | |
2558 | === modified file 'client/service/common_test.go' |
2559 | --- client/service/common_test.go 2014-07-04 19:21:42 +0000 |
2560 | +++ client/service/common_test.go 2014-07-21 14:14:04 +0000 |
2561 | @@ -25,17 +25,20 @@ |
2562 | var _ = Suite(&commonSuite{}) |
2563 | |
2564 | func (cs *commonSuite) TestGrabDBusPackageAndAppIdWorks(c *C) { |
2565 | + svc := new(DBusService) |
2566 | aDBusPath := "/com/ubuntu/Postal/com_2eexample_2etest" |
2567 | aPackage := "com.example.test" |
2568 | anAppId := aPackage + "_test" |
2569 | - pkg, app, err := grabDBusPackageAndAppId(aDBusPath, []interface{}{anAppId}, 0) |
2570 | + app, err := svc.grabDBusPackageAndAppId(aDBusPath, []interface{}{anAppId}, 0) |
2571 | c.Check(err, IsNil) |
2572 | - c.Check(pkg, Equals, aPackage) |
2573 | - c.Check(app, Equals, anAppId) |
2574 | + c.Check(app.Package, Equals, aPackage) |
2575 | + c.Check(app.Original(), Equals, anAppId) |
2576 | } |
2577 | |
2578 | func (cs *commonSuite) TestGrabDBusPackageAndAppIdFails(c *C) { |
2579 | + svc := new(DBusService) |
2580 | aDBusPath := "/com/ubuntu/Postal/com_2eexample_2etest" |
2581 | + aDBusPath2 := "/com/ubuntu/Postal/com_2efoo_2ebar" |
2582 | aPackage := "com.example.test" |
2583 | anAppId := aPackage + "_test" |
2584 | |
2585 | @@ -50,11 +53,11 @@ |
2586 | {aDBusPath, []interface{}{anAppId, anAppId}, 0, ErrBadArgCount}, |
2587 | {aDBusPath, []interface{}{1}, 0, ErrBadArgType}, |
2588 | {aDBusPath, []interface{}{aPackage}, 0, ErrBadAppId}, |
2589 | + {aDBusPath2, []interface{}{anAppId}, 0, ErrBadAppId}, |
2590 | } { |
2591 | comment := Commentf("iteration #%d", i) |
2592 | - pkg, app, err := grabDBusPackageAndAppId(s.path, s.args, s.numExtra) |
2593 | + app, err := svc.grabDBusPackageAndAppId(s.path, s.args, s.numExtra) |
2594 | c.Check(err, Equals, s.errt, comment) |
2595 | - c.Check(pkg, Equals, "", comment) |
2596 | - c.Check(app, Equals, "", comment) |
2597 | + c.Check(app, IsNil, comment) |
2598 | } |
2599 | } |
2600 | |
2601 | === added file 'client/service/mbox.go' |
2602 | --- client/service/mbox.go 1970-01-01 00:00:00 +0000 |
2603 | +++ client/service/mbox.go 2014-07-21 14:14:04 +0000 |
2604 | @@ -0,0 +1,80 @@ |
2605 | +/* |
2606 | + Copyright 2014 Canonical Ltd. |
2607 | + |
2608 | + This program is free software: you can redistribute it and/or modify it |
2609 | + under the terms of the GNU General Public License version 3, as published |
2610 | + by the Free Software Foundation. |
2611 | + |
2612 | + This program is distributed in the hope that it will be useful, but |
2613 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
2614 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2615 | + PURPOSE. See the GNU General Public License for more details. |
2616 | + |
2617 | + You should have received a copy of the GNU General Public License along |
2618 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
2619 | +*/ |
2620 | + |
2621 | +package service |
2622 | + |
2623 | +import ( |
2624 | + "encoding/json" |
2625 | +) |
2626 | + |
2627 | +var mBoxMaxMessagesSize = 128 * 1024 |
2628 | + |
2629 | +// mBox can hold a size-limited amount of notification messages for one application. |
2630 | +type mBox struct { |
2631 | + evicted int |
2632 | + curSize int |
2633 | + messages []string |
2634 | + nids []string |
2635 | +} |
2636 | + |
2637 | +func (box *mBox) evictFor(sz int) { |
2638 | + evictedSize := 0 |
2639 | + i := box.evicted |
2640 | + n := len(box.messages) |
2641 | + for evictedSize < sz && i < n { |
2642 | + evictedSize += len(box.messages[i]) |
2643 | + box.messages[i] = "" |
2644 | + box.nids[i] = "" |
2645 | + box.evicted++ |
2646 | + i++ |
2647 | + } |
2648 | + box.curSize -= evictedSize |
2649 | +} |
2650 | + |
2651 | +// Append appends a message with notification id to the mbox. |
2652 | +func (box *mBox) Append(message json.RawMessage, nid string) { |
2653 | + sz := len(message) |
2654 | + if box.curSize+sz > mBoxMaxMessagesSize { |
2655 | + // make space |
2656 | + box.evictFor(sz) |
2657 | + } |
2658 | + n := len(box.messages) |
2659 | + evicted := box.evicted |
2660 | + if evicted > 0 { |
2661 | + if evicted == n { |
2662 | + // all evicted, just start from scratch |
2663 | + box.messages = box.messages[0:0] |
2664 | + box.nids = box.nids[0:0] |
2665 | + box.evicted = 0 |
2666 | + } else if evicted >= cap(box.messages)/2 { |
2667 | + // amortize: do a copy only each cap/2 evicted |
2668 | + copy(box.messages, box.messages[box.evicted:]) |
2669 | + kept := n - box.evicted |
2670 | + box.messages = box.messages[0:kept] |
2671 | + copy(box.nids, box.nids[box.evicted:]) |
2672 | + box.nids = box.nids[0:kept] |
2673 | + box.evicted = 0 |
2674 | + } |
2675 | + } |
2676 | + box.messages = append(box.messages, string(message)) |
2677 | + box.nids = append(box.nids, nid) |
2678 | + box.curSize += sz |
2679 | +} |
2680 | + |
2681 | +// AllMessages gets all messages from the mbox. |
2682 | +func (box *mBox) AllMessages() []string { |
2683 | + return box.messages[box.evicted:] |
2684 | +} |
2685 | |
2686 | === added file 'client/service/mbox_test.go' |
2687 | --- client/service/mbox_test.go 1970-01-01 00:00:00 +0000 |
2688 | +++ client/service/mbox_test.go 2014-07-21 14:14:04 +0000 |
2689 | @@ -0,0 +1,132 @@ |
2690 | +/* |
2691 | + Copyright 2014 Canonical Ltd. |
2692 | + |
2693 | + This program is free software: you can redistribute it and/or modify it |
2694 | + under the terms of the GNU General Public License version 3, as published |
2695 | + by the Free Software Foundation. |
2696 | + |
2697 | + This program is distributed in the hope that it will be useful, but |
2698 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
2699 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2700 | + PURPOSE. See the GNU General Public License for more details. |
2701 | + |
2702 | + You should have received a copy of the GNU General Public License along |
2703 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
2704 | +*/ |
2705 | + |
2706 | +package service |
2707 | + |
2708 | +import ( |
2709 | + "encoding/json" |
2710 | + "fmt" |
2711 | + "strings" |
2712 | + |
2713 | + . "launchpad.net/gocheck" |
2714 | +) |
2715 | + |
2716 | +type mBoxSuite struct { |
2717 | + prevMBoxMaxMessagesSize int |
2718 | +} |
2719 | + |
2720 | +var _ = Suite(&mBoxSuite{}) |
2721 | + |
2722 | +func (s *mBoxSuite) SetUpSuite(c *C) { |
2723 | + s.prevMBoxMaxMessagesSize = mBoxMaxMessagesSize |
2724 | + mBoxMaxMessagesSize = 100 |
2725 | +} |
2726 | + |
2727 | +func (s *mBoxSuite) TearDownSuite(c *C) { |
2728 | + mBoxMaxMessagesSize = s.prevMBoxMaxMessagesSize |
2729 | +} |
2730 | + |
2731 | +func (s *mBoxSuite) TestAppend(c *C) { |
2732 | + mbox := &mBox{} |
2733 | + m1 := json.RawMessage(`{"m":1}`) |
2734 | + m2 := json.RawMessage(`{"m":2}`) |
2735 | + mbox.Append(m1, "n1") |
2736 | + mbox.Append(m2, "n2") |
2737 | + c.Check(mbox.messages, DeepEquals, []string{string(m1), string(m2)}) |
2738 | + c.Check(mbox.nids, DeepEquals, []string{"n1", "n2"}) |
2739 | +} |
2740 | + |
2741 | +func (s *mBoxSuite) TestAllMessagesEmpty(c *C) { |
2742 | + mbox := &mBox{} |
2743 | + c.Check(mbox.AllMessages(), HasLen, 0) |
2744 | +} |
2745 | + |
2746 | +func (s *mBoxSuite) TestAllMessages(c *C) { |
2747 | + mbox := &mBox{} |
2748 | + m1 := json.RawMessage(`{"m":1}`) |
2749 | + m2 := json.RawMessage(`{"m":2}`) |
2750 | + mbox.Append(m1, "n1") |
2751 | + mbox.Append(m2, "n2") |
2752 | + c.Check(mbox.AllMessages(), DeepEquals, []string{string(m1), string(m2)}) |
2753 | +} |
2754 | + |
2755 | +func blobMessage(n int, sz int) json.RawMessage { |
2756 | + return json.RawMessage(fmt.Sprintf(`{"n":%d,"b":"%s"}`, n, strings.Repeat("x", sz-14))) |
2757 | +} |
2758 | + |
2759 | +func (s *mBoxSuite) TestAppendEvictSome(c *C) { |
2760 | + mbox := &mBox{} |
2761 | + m1 := blobMessage(1, 25) |
2762 | + m2 := blobMessage(2, 25) |
2763 | + m3 := blobMessage(3, 50) |
2764 | + mbox.Append(m1, "n1") |
2765 | + mbox.Append(m2, "n2") |
2766 | + mbox.Append(m3, "n3") |
2767 | + c.Check(mbox.curSize, Equals, 100) |
2768 | + c.Check(mbox.evicted, Equals, 0) |
2769 | + m4 := blobMessage(4, 23) |
2770 | + mbox.Append(m4, "n4") |
2771 | + c.Assert(mbox.evicted, Equals, 1) |
2772 | + c.Check(mbox.curSize, Equals, 25+50+23) |
2773 | + c.Check(mbox.AllMessages(), DeepEquals, []string{string(m2), string(m3), string(m4)}) |
2774 | + c.Check(mbox.messages[:1], DeepEquals, []string{""}) |
2775 | + c.Check(mbox.nids, DeepEquals, []string{"", "n2", "n3", "n4"}) |
2776 | +} |
2777 | + |
2778 | +func (s *mBoxSuite) TestAppendEvictSomeCopyOver(c *C) { |
2779 | + mbox := &mBox{} |
2780 | + m1 := blobMessage(1, 25) |
2781 | + m2 := blobMessage(2, 25) |
2782 | + m3 := blobMessage(3, 25) |
2783 | + m4 := blobMessage(4, 25) |
2784 | + mbox.Append(m1, "n1") |
2785 | + mbox.Append(m2, "n2") |
2786 | + mbox.Append(m3, "n3") |
2787 | + mbox.Append(m4, "n4") |
2788 | + c.Check(mbox.curSize, Equals, 100) |
2789 | + c.Check(mbox.evicted, Equals, 0) |
2790 | + m5 := blobMessage(5, 40) |
2791 | + mbox.Append(m5, "n5") |
2792 | + c.Assert(mbox.evicted, Equals, 0) |
2793 | + c.Check(mbox.curSize, Equals, 90) |
2794 | + c.Check(mbox.AllMessages(), DeepEquals, []string{string(m3), string(m4), string(m5)}) |
2795 | + c.Check(mbox.nids, DeepEquals, []string{"n3", "n4", "n5"}) |
2796 | + // do it again |
2797 | + m6 := blobMessage(6, 40) |
2798 | + mbox.Append(m6, "n6") |
2799 | + c.Assert(mbox.evicted, Equals, 0) |
2800 | + c.Check(mbox.curSize, Equals, 80) |
2801 | + c.Check(mbox.AllMessages(), DeepEquals, []string{string(m5), string(m6)}) |
2802 | + c.Check(mbox.nids, DeepEquals, []string{"n5", "n6"}) |
2803 | +} |
2804 | + |
2805 | +func (s *mBoxSuite) TestAppendEvictEverything(c *C) { |
2806 | + mbox := &mBox{} |
2807 | + m1 := blobMessage(1, 25) |
2808 | + m2 := blobMessage(2, 25) |
2809 | + m3 := blobMessage(3, 50) |
2810 | + mbox.Append(m1, "n1") |
2811 | + mbox.Append(m2, "n2") |
2812 | + mbox.Append(m3, "n3") |
2813 | + c.Check(mbox.curSize, Equals, 100) |
2814 | + c.Check(mbox.evicted, Equals, 0) |
2815 | + m4 := blobMessage(4, 90) |
2816 | + mbox.Append(m4, "n4") |
2817 | + c.Assert(mbox.evicted, Equals, 0) |
2818 | + c.Check(mbox.curSize, Equals, 90) |
2819 | + c.Check(mbox.AllMessages(), DeepEquals, []string{string(m4)}) |
2820 | + c.Check(mbox.nids, DeepEquals, []string{"n4"}) |
2821 | +} |
2822 | |
2823 | === modified file 'client/service/postal.go' |
2824 | --- client/service/postal.go 2014-07-06 20:48:50 +0000 |
2825 | +++ client/service/postal.go 2014-07-21 14:14:04 +0000 |
2826 | @@ -17,6 +17,8 @@ |
2827 | package service |
2828 | |
2829 | import ( |
2830 | + "encoding/json" |
2831 | + "os" |
2832 | "sync" |
2833 | |
2834 | "code.google.com/p/go-uuid/uuid" |
2835 | @@ -25,24 +27,43 @@ |
2836 | "launchpad.net/ubuntu-push/bus/emblemcounter" |
2837 | "launchpad.net/ubuntu-push/bus/haptic" |
2838 | "launchpad.net/ubuntu-push/bus/notifications" |
2839 | + "launchpad.net/ubuntu-push/bus/urldispatcher" |
2840 | + "launchpad.net/ubuntu-push/bus/windowstack" |
2841 | + "launchpad.net/ubuntu-push/click" |
2842 | "launchpad.net/ubuntu-push/launch_helper" |
2843 | "launchpad.net/ubuntu-push/logger" |
2844 | "launchpad.net/ubuntu-push/messaging" |
2845 | + "launchpad.net/ubuntu-push/messaging/reply" |
2846 | "launchpad.net/ubuntu-push/nih" |
2847 | "launchpad.net/ubuntu-push/sounds" |
2848 | "launchpad.net/ubuntu-push/util" |
2849 | ) |
2850 | |
2851 | +type messageHandler func(*click.AppId, string, *launch_helper.HelperOutput) error |
2852 | + |
2853 | // PostalService is the dbus api |
2854 | type PostalService struct { |
2855 | DBusService |
2856 | - mbox map[string][]string |
2857 | - msgHandler func(string, string, *launch_helper.HelperOutput) error |
2858 | - HelperLauncher launch_helper.HelperLauncher |
2859 | - messagingMenu *messaging.MessagingMenu |
2860 | - emblemcounterEndp bus.Endpoint |
2861 | - hapticEndp bus.Endpoint |
2862 | - notificationsEndp bus.Endpoint |
2863 | + mbox map[string]*mBox |
2864 | + msgHandler messageHandler |
2865 | + launchers map[string]launch_helper.HelperLauncher |
2866 | + HelperPool launch_helper.HelperPool |
2867 | + messagingMenu *messaging.MessagingMenu |
2868 | + // the endpoints are only exposed for testing from client |
2869 | + // XXX: uncouple some more so this isn't necessary |
2870 | + EmblemCounterEndp bus.Endpoint |
2871 | + HapticEndp bus.Endpoint |
2872 | + NotificationsEndp bus.Endpoint |
2873 | + URLDispatcherEndp bus.Endpoint |
2874 | + WindowStackEndp bus.Endpoint |
2875 | + // presenters: |
2876 | + emblemCounter *emblemcounter.EmblemCounter |
2877 | + haptic *haptic.Haptic |
2878 | + notifications *notifications.RawNotifications |
2879 | + sound *sounds.Sound |
2880 | + // the url dispatcher, used for stuff. |
2881 | + urlDispatcher urldispatcher.URLDispatcher |
2882 | + windowStack *windowstack.WindowStack |
2883 | } |
2884 | |
2885 | var ( |
2886 | @@ -55,33 +76,34 @@ |
2887 | |
2888 | var ( |
2889 | SystemUpdateUrl = "settings:///system/system-update" |
2890 | - ACTION_ID_PREFIX = "ubuntu-push-client::" |
2891 | - ACTION_ID_SUFFIX = "::0" |
2892 | + useTrivialHelper = os.Getenv("UBUNTU_PUSH_USE_TRIVIAL_HELPER") != "" |
2893 | ) |
2894 | |
2895 | // NewPostalService() builds a new service and returns it. |
2896 | -func NewPostalService(busEndp bus.Endpoint, notificationsEndp bus.Endpoint, emblemcounterEndp bus.Endpoint, hapticEndp bus.Endpoint, log logger.Logger) *PostalService { |
2897 | +func NewPostalService(installedChecker click.InstalledChecker, log logger.Logger) *PostalService { |
2898 | var svc = &PostalService{} |
2899 | svc.Log = log |
2900 | - svc.Bus = busEndp |
2901 | - svc.messagingMenu = messaging.New(log) |
2902 | - svc.HelperLauncher = launch_helper.NewTrivialHelperLauncher(log) |
2903 | - svc.notificationsEndp = notificationsEndp |
2904 | - svc.emblemcounterEndp = emblemcounterEndp |
2905 | - svc.hapticEndp = hapticEndp |
2906 | + svc.Bus = bus.SessionBus.Endpoint(PostalServiceBusAddress, log) |
2907 | + svc.installedChecker = installedChecker |
2908 | + svc.NotificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, log) |
2909 | + svc.EmblemCounterEndp = bus.SessionBus.Endpoint(emblemcounter.BusAddress, log) |
2910 | + svc.HapticEndp = bus.SessionBus.Endpoint(haptic.BusAddress, log) |
2911 | + svc.URLDispatcherEndp = bus.SessionBus.Endpoint(urldispatcher.BusAddress, log) |
2912 | + svc.WindowStackEndp = bus.SessionBus.Endpoint(windowstack.BusAddress, log) |
2913 | svc.msgHandler = svc.messageHandler |
2914 | + svc.launchers = launch_helper.DefaultLaunchers(log) |
2915 | return svc |
2916 | } |
2917 | |
2918 | // SetMessageHandler() sets the message-handling callback |
2919 | -func (svc *PostalService) SetMessageHandler(callback func(string, string, *launch_helper.HelperOutput) error) { |
2920 | +func (svc *PostalService) SetMessageHandler(callback messageHandler) { |
2921 | svc.lock.RLock() |
2922 | defer svc.lock.RUnlock() |
2923 | svc.msgHandler = callback |
2924 | } |
2925 | |
2926 | // GetMessageHandler() returns the (possibly nil) messaging handler callback |
2927 | -func (svc *PostalService) GetMessageHandler() func(string, string, *launch_helper.HelperOutput) error { |
2928 | +func (svc *PostalService) GetMessageHandler() messageHandler { |
2929 | svc.lock.RLock() |
2930 | defer svc.lock.RUnlock() |
2931 | return svc.msgHandler |
2932 | @@ -89,34 +111,106 @@ |
2933 | |
2934 | // Start() dials the bus, grab the name, and listens for method calls. |
2935 | func (svc *PostalService) Start() error { |
2936 | - return svc.DBusService.Start(bus.DispatchMap{ |
2937 | - "Messages": svc.notifications, |
2938 | - "Post": svc.inject, |
2939 | + err := svc.DBusService.Start(bus.DispatchMap{ |
2940 | + "PopAll": svc.popAll, |
2941 | + "Post": svc.post, |
2942 | }, PostalServiceBusAddress) |
2943 | -} |
2944 | - |
2945 | -func (svc *PostalService) TakeTheBus() (<-chan notifications.RawActionReply, error) { |
2946 | + if err != nil { |
2947 | + return err |
2948 | + } |
2949 | + actionsCh, err := svc.takeTheBus() |
2950 | + if err != nil { |
2951 | + return err |
2952 | + } |
2953 | + svc.urlDispatcher = urldispatcher.New(svc.URLDispatcherEndp, svc.Log) |
2954 | + svc.notifications = notifications.Raw(svc.NotificationsEndp, svc.Log) |
2955 | + svc.emblemCounter = emblemcounter.New(svc.EmblemCounterEndp, svc.Log) |
2956 | + svc.haptic = haptic.New(svc.HapticEndp, svc.Log) |
2957 | + svc.sound = sounds.New(svc.Log) |
2958 | + svc.messagingMenu = messaging.New(svc.Log) |
2959 | + if useTrivialHelper { |
2960 | + svc.HelperPool = launch_helper.NewTrivialHelperPool(svc.Log) |
2961 | + } else { |
2962 | + svc.HelperPool = launch_helper.NewHelperPool(svc.launchers, svc.Log) |
2963 | + } |
2964 | + svc.windowStack = windowstack.New(svc.WindowStackEndp, svc.Log) |
2965 | + |
2966 | + go svc.consumeHelperResults(svc.HelperPool.Start()) |
2967 | + go svc.handleActions(actionsCh, svc.messagingMenu.Ch) |
2968 | + return nil |
2969 | +} |
2970 | + |
2971 | +// xxx Stop() closing channels and helper launcher |
2972 | + |
2973 | +// handleactions loops on the actions channels waiting for actions and handling them |
2974 | +func (svc *PostalService) handleActions(actionsCh <-chan *notifications.RawAction, mmuActionsCh <-chan *reply.MMActionReply) { |
2975 | +Handle: |
2976 | + for { |
2977 | + select { |
2978 | + case action, ok := <-actionsCh: |
2979 | + if !ok { |
2980 | + break Handle |
2981 | + } |
2982 | + if action == nil { |
2983 | + svc.Log.Debugf("handleActions got nil action; ignoring") |
2984 | + } else { |
2985 | + url := action.Action |
2986 | + // this ignores the error (it's been logged already) |
2987 | + svc.urlDispatcher.DispatchURL(url) |
2988 | + } |
2989 | + case mmuAction, ok := <-mmuActionsCh: |
2990 | + if !ok { |
2991 | + break Handle |
2992 | + } |
2993 | + if mmuAction == nil { |
2994 | + svc.Log.Debugf("handleActions (MMU) got nil action; ignoring") |
2995 | + } else { |
2996 | + svc.Log.Debugf("handleActions (MMU) got: %v", mmuAction) |
2997 | + url := mmuAction.Action |
2998 | + // remove the notification from the messagingmenu map |
2999 | + svc.messagingMenu.RemoveNotification(mmuAction.Notification) |
3000 | + // this ignores the error (it's been logged already) |
3001 | + svc.urlDispatcher.DispatchURL(url) |
3002 | + } |
3003 | + |
3004 | + } |
3005 | + } |
3006 | +} |
3007 | + |
3008 | +func (svc *PostalService) takeTheBus() (<-chan *notifications.RawAction, error) { |
3009 | + endps := []struct { |
3010 | + name string |
3011 | + endp bus.Endpoint |
3012 | + }{ |
3013 | + {"notifications", svc.NotificationsEndp}, |
3014 | + {"emblemcounter", svc.EmblemCounterEndp}, |
3015 | + {"haptic", svc.HapticEndp}, |
3016 | + {"urldispatcher", svc.URLDispatcherEndp}, |
3017 | + {"windowstack", svc.WindowStackEndp}, |
3018 | + } |
3019 | + for _, endp := range endps { |
3020 | + if endp.endp == nil { |
3021 | + svc.Log.Errorf("endpoint for %s is nil", endp.name) |
3022 | + return nil, ErrNotConfigured |
3023 | + } |
3024 | + } |
3025 | + |
3026 | var wg sync.WaitGroup |
3027 | - endps := []bus.Endpoint{ |
3028 | - svc.notificationsEndp, |
3029 | - svc.emblemcounterEndp, |
3030 | - svc.hapticEndp, |
3031 | - } |
3032 | wg.Add(len(endps)) |
3033 | for _, endp := range endps { |
3034 | - go func(endp bus.Endpoint) { |
3035 | + go func(name string, endp bus.Endpoint) { |
3036 | util.NewAutoRedialer(endp).Redial() |
3037 | + svc.Log.Debugf("%s dialed in", name) |
3038 | wg.Done() |
3039 | - }(endp) |
3040 | + }(endp.name, endp.endp) |
3041 | } |
3042 | wg.Wait() |
3043 | - actionsCh, err := notifications.Raw(svc.notificationsEndp, svc.Log).WatchActions() |
3044 | |
3045 | - return actionsCh, err |
3046 | + return notifications.Raw(svc.NotificationsEndp, svc.Log).WatchActions() |
3047 | } |
3048 | |
3049 | -func (svc *PostalService) notifications(path string, args, _ []interface{}) ([]interface{}, error) { |
3050 | - _, appId, err := grabDBusPackageAndAppId(path, args, 0) |
3051 | +func (svc *PostalService) popAll(path string, args, _ []interface{}) ([]interface{}, error) { |
3052 | + app, err := svc.grabDBusPackageAndAppId(path, args, 0) |
3053 | if err != nil { |
3054 | return nil, err |
3055 | } |
3056 | @@ -127,7 +221,12 @@ |
3057 | if svc.mbox == nil { |
3058 | return []interface{}{[]string(nil)}, nil |
3059 | } |
3060 | - msgs := svc.mbox[appId] |
3061 | + appId := app.Original() |
3062 | + box, ok := svc.mbox[appId] |
3063 | + if !ok { |
3064 | + return []interface{}{[]string(nil)}, nil |
3065 | + } |
3066 | + msgs := box.AllMessages() |
3067 | delete(svc.mbox, appId) |
3068 | |
3069 | return []interface{}{msgs}, nil |
3070 | @@ -135,8 +234,8 @@ |
3071 | |
3072 | var newNid = uuid.New |
3073 | |
3074 | -func (svc *PostalService) inject(path string, args, _ []interface{}) ([]interface{}, error) { |
3075 | - pkg, appId, err := grabDBusPackageAndAppId(path, args, 1) |
3076 | +func (svc *PostalService) post(path string, args, _ []interface{}) ([]interface{}, error) { |
3077 | + app, err := svc.grabDBusPackageAndAppId(path, args, 1) |
3078 | if err != nil { |
3079 | return nil, err |
3080 | } |
3081 | @@ -144,55 +243,83 @@ |
3082 | if !ok { |
3083 | return nil, ErrBadArgType |
3084 | } |
3085 | + var dummy interface{} |
3086 | + rawJSON := json.RawMessage(notif) |
3087 | + err = json.Unmarshal(rawJSON, &dummy) |
3088 | + if err != nil { |
3089 | + return nil, ErrBadJSON |
3090 | + } |
3091 | |
3092 | nid := newNid() |
3093 | |
3094 | - return nil, svc.Inject(pkg, appId, nid, notif) |
3095 | + return nil, svc.Post(app, nid, rawJSON) |
3096 | } |
3097 | |
3098 | -// Inject() signals to an application over dbus that a notification |
3099 | +// Post() signals to an application over dbus that a notification |
3100 | // has arrived. |
3101 | -func (svc *PostalService) Inject(pkgname string, appname string, nid string, notif string) error { |
3102 | +func (svc *PostalService) Post(app *click.AppId, nid string, payload json.RawMessage) error { |
3103 | + arg := launch_helper.HelperInput{ |
3104 | + App: app, |
3105 | + NotificationId: nid, |
3106 | + Payload: payload, |
3107 | + } |
3108 | + var kind string |
3109 | + if app.Click { |
3110 | + kind = "click" |
3111 | + } else { |
3112 | + kind = "legacy" |
3113 | + } |
3114 | + svc.HelperPool.Run(kind, &arg) |
3115 | + return nil |
3116 | +} |
3117 | + |
3118 | +func (svc *PostalService) consumeHelperResults(ch chan *launch_helper.HelperResult) { |
3119 | + for res := range ch { |
3120 | + svc.handleHelperResult(res) |
3121 | + } |
3122 | +} |
3123 | + |
3124 | +func (svc *PostalService) handleHelperResult(res *launch_helper.HelperResult) { |
3125 | svc.lock.Lock() |
3126 | defer svc.lock.Unlock() |
3127 | if svc.mbox == nil { |
3128 | - svc.mbox = make(map[string][]string) |
3129 | - } |
3130 | - output := svc.HelperLauncher.Run(appname, []byte(notif)) |
3131 | - // XXX also track the nid in the mbox |
3132 | - svc.mbox[appname] = append(svc.mbox[appname], string(output.Message)) |
3133 | + svc.mbox = make(map[string]*mBox) |
3134 | + } |
3135 | + |
3136 | + app := res.Input.App |
3137 | + nid := res.Input.NotificationId |
3138 | + output := res.HelperOutput |
3139 | + |
3140 | + appId := app.Original() |
3141 | + box, ok := svc.mbox[appId] |
3142 | + if !ok { |
3143 | + box = new(mBox) |
3144 | + svc.mbox[appId] = box |
3145 | + } |
3146 | + box.Append(output.Message, nid) |
3147 | |
3148 | if svc.msgHandler != nil { |
3149 | - err := svc.msgHandler(appname, nid, output) |
3150 | + err := svc.msgHandler(app, nid, &output) |
3151 | if err != nil { |
3152 | svc.DBusService.Log.Errorf("msgHandler returned %v", err) |
3153 | - return err |
3154 | + return |
3155 | } |
3156 | svc.DBusService.Log.Debugf("call to msgHandler successful") |
3157 | } |
3158 | |
3159 | - return svc.Bus.Signal("Post", "/"+string(nih.Quote([]byte(pkgname))), []interface{}{appname}) |
3160 | -} |
3161 | - |
3162 | -func (svc *PostalService) messageHandler(appname string, nid string, output *launch_helper.HelperOutput) error { |
3163 | - svc.messagingMenu.Present(appname, nid, output.Notification) |
3164 | - nots := notifications.Raw(svc.notificationsEndp, svc.Log) |
3165 | - _, err := nots.Present(appname, nid, output.Notification) |
3166 | - emblemcounter.New(svc.emblemcounterEndp, svc.Log).Present(appname, nid, output.Notification) |
3167 | - haptic.New(svc.hapticEndp, svc.Log).Present(appname, nid, output.Notification) |
3168 | - sounds.New(svc.Log).Present(appname, nid, output.Notification) |
3169 | - |
3170 | - return err |
3171 | -} |
3172 | - |
3173 | -func (svc *PostalService) InjectBroadcast() (uint32, error) { |
3174 | - // XXX: call a helper? |
3175 | - // XXX: Present force us to send the url as the notificationId |
3176 | - icon := "update_manager_icon" |
3177 | - summary := "There's an updated system image." |
3178 | - body := "Tap to open the system updater." |
3179 | - actions := []string{"Switch to app"} // action value not visible on the phone |
3180 | - card := &launch_helper.Card{Icon: icon, Summary: summary, Body: body, Actions: actions, Popup: true} |
3181 | - output := &launch_helper.HelperOutput{[]byte(""), &launch_helper.Notification{Card: card}} |
3182 | - return 0, svc.msgHandler("ubuntu-push-client", SystemUpdateUrl, output) |
3183 | + svc.Bus.Signal("Post", "/"+string(nih.Quote([]byte(app.Package))), []interface{}{appId}) |
3184 | +} |
3185 | + |
3186 | +func (svc *PostalService) messageHandler(app *click.AppId, nid string, output *launch_helper.HelperOutput) error { |
3187 | + if !svc.windowStack.IsAppFocused(app) { |
3188 | + svc.messagingMenu.Present(app, nid, output.Notification) |
3189 | + _, err := svc.notifications.Present(app, nid, output.Notification) |
3190 | + svc.emblemCounter.Present(app, nid, output.Notification) |
3191 | + svc.haptic.Present(app, nid, output.Notification) |
3192 | + svc.sound.Present(app, nid, output.Notification) |
3193 | + return err |
3194 | + } else { |
3195 | + svc.Log.Debugf("Notification skipped because app is focused.") |
3196 | + return nil |
3197 | + } |
3198 | } |
3199 | |
3200 | === modified file 'client/service/postal_test.go' |
3201 | --- client/service/postal_test.go 2014-07-07 12:44:51 +0000 |
3202 | +++ client/service/postal_test.go 2014-07-21 14:14:04 +0000 |
3203 | @@ -17,135 +17,341 @@ |
3204 | package service |
3205 | |
3206 | import ( |
3207 | + "encoding/json" |
3208 | "errors" |
3209 | + "io/ioutil" |
3210 | + "os" |
3211 | + "path/filepath" |
3212 | "sort" |
3213 | + "time" |
3214 | + |
3215 | + "code.google.com/p/go-uuid/uuid" |
3216 | |
3217 | . "launchpad.net/gocheck" |
3218 | |
3219 | "launchpad.net/ubuntu-push/bus" |
3220 | + "launchpad.net/ubuntu-push/bus/notifications" |
3221 | testibus "launchpad.net/ubuntu-push/bus/testing" |
3222 | + "launchpad.net/ubuntu-push/bus/windowstack" |
3223 | + "launchpad.net/ubuntu-push/click" |
3224 | + clickhelp "launchpad.net/ubuntu-push/click/testing" |
3225 | "launchpad.net/ubuntu-push/launch_helper" |
3226 | + "launchpad.net/ubuntu-push/messaging/reply" |
3227 | helpers "launchpad.net/ubuntu-push/testing" |
3228 | "launchpad.net/ubuntu-push/testing/condition" |
3229 | ) |
3230 | |
3231 | +// takeNext takes a value from given channel with a 5s timeout |
3232 | +func takeNextBool(ch <-chan bool) bool { |
3233 | + select { |
3234 | + case <-time.After(5 * time.Second): |
3235 | + panic("channel stuck: too long waiting") |
3236 | + case v := <-ch: |
3237 | + return v |
3238 | + } |
3239 | +} |
3240 | + |
3241 | +// takeNextBytes takes a value from given channel with a 5s timeout |
3242 | +func takeNextBytes(ch <-chan []byte) []byte { |
3243 | + select { |
3244 | + case <-time.After(5 * time.Second): |
3245 | + panic("channel stuck: too long waiting") |
3246 | + case v := <-ch: |
3247 | + return v |
3248 | + } |
3249 | +} |
3250 | + |
3251 | +// takeNextHelperOutput takes a value from given channel with a 5s timeout |
3252 | +func takeNextHelperOutput(ch <-chan *launch_helper.HelperOutput) *launch_helper.HelperOutput { |
3253 | + select { |
3254 | + case <-time.After(5 * time.Second): |
3255 | + panic("channel stuck: too long waiting") |
3256 | + case v := <-ch: |
3257 | + return v |
3258 | + } |
3259 | +} |
3260 | + |
3261 | +func takeNextError(ch <-chan error) error { |
3262 | + select { |
3263 | + case <-time.After(5 * time.Second): |
3264 | + panic("channel stuck: too long waiting") |
3265 | + case v := <-ch: |
3266 | + return v |
3267 | + } |
3268 | +} |
3269 | + |
3270 | +func installTickMessageHandler(svc *PostalService) chan error { |
3271 | + ch := make(chan error) |
3272 | + msgHandler := svc.GetMessageHandler() |
3273 | + svc.SetMessageHandler(func(app *click.AppId, nid string, output *launch_helper.HelperOutput) error { |
3274 | + var err error |
3275 | + if msgHandler != nil { |
3276 | + err = msgHandler(app, nid, output) |
3277 | + } |
3278 | + ch <- err |
3279 | + return err |
3280 | + }) |
3281 | + return ch |
3282 | +} |
3283 | + |
3284 | +type fakeHelperLauncher struct { |
3285 | + i int |
3286 | + ch chan []byte |
3287 | + done func(string) |
3288 | +} |
3289 | + |
3290 | +func (fhl *fakeHelperLauncher) InstallObserver(done func(string)) error { |
3291 | + fhl.done = done |
3292 | + return nil |
3293 | +} |
3294 | +func (fhl *fakeHelperLauncher) RemoveObserver() error { return nil } |
3295 | +func (fhl *fakeHelperLauncher) Stop(_, _ string) error { return nil } |
3296 | +func (fhl *fakeHelperLauncher) HelperInfo(app *click.AppId) (string, string) { |
3297 | + if app.Click { |
3298 | + return "helpId", "bar" |
3299 | + } else { |
3300 | + return "", "lhex" |
3301 | + } |
3302 | +} |
3303 | +func (fhl *fakeHelperLauncher) Launch(_, _, f1, f2 string) (string, error) { |
3304 | + dat, err := ioutil.ReadFile(f1) |
3305 | + if err != nil { |
3306 | + return "", err |
3307 | + } |
3308 | + err = ioutil.WriteFile(f2, dat, os.ModeTemporary) |
3309 | + if err != nil { |
3310 | + return "", err |
3311 | + } |
3312 | + |
3313 | + id := []string{"0", "1", "2"}[fhl.i] |
3314 | + fhl.i++ |
3315 | + |
3316 | + fhl.ch <- dat |
3317 | + |
3318 | + return id, nil |
3319 | +} |
3320 | + |
3321 | type postalSuite struct { |
3322 | - log *helpers.TestLogger |
3323 | - bus bus.Endpoint |
3324 | - notifBus bus.Endpoint |
3325 | - counterBus bus.Endpoint |
3326 | - hapticBus bus.Endpoint |
3327 | -} |
3328 | - |
3329 | -var _ = Suite(&postalSuite{}) |
3330 | - |
3331 | -func (ss *postalSuite) SetUpTest(c *C) { |
3332 | - ss.log = helpers.NewTestLogger(c, "debug") |
3333 | - ss.bus = testibus.NewTestingEndpoint(condition.Work(true), nil) |
3334 | - ss.notifBus = testibus.NewTestingEndpoint(condition.Work(true), nil) |
3335 | - ss.counterBus = testibus.NewTestingEndpoint(condition.Work(true), nil) |
3336 | - ss.hapticBus = testibus.NewTestingEndpoint(condition.Work(true), nil) |
3337 | -} |
3338 | - |
3339 | -func (ss *postalSuite) TestStart(c *C) { |
3340 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
3341 | + log *helpers.TestLogger |
3342 | + bus bus.Endpoint |
3343 | + notifBus bus.Endpoint |
3344 | + counterBus bus.Endpoint |
3345 | + hapticBus bus.Endpoint |
3346 | + urlDispBus bus.Endpoint |
3347 | + winStackBus bus.Endpoint |
3348 | + fakeLauncher *fakeHelperLauncher |
3349 | + getTempDir func(string) (string, error) |
3350 | +} |
3351 | + |
3352 | +type ualPostalSuite struct { |
3353 | + postalSuite |
3354 | +} |
3355 | + |
3356 | +type trivialPostalSuite struct { |
3357 | + postalSuite |
3358 | +} |
3359 | + |
3360 | +var _ = Suite(&ualPostalSuite{}) |
3361 | +var _ = Suite(&trivialPostalSuite{}) |
3362 | + |
3363 | +func (ps *postalSuite) SetUpTest(c *C) { |
3364 | + ps.log = helpers.NewTestLogger(c, "debug") |
3365 | + ps.bus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
3366 | + ps.notifBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
3367 | + ps.counterBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
3368 | + ps.hapticBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
3369 | + ps.urlDispBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
3370 | + ps.winStackBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{}) |
3371 | + ps.fakeLauncher = &fakeHelperLauncher{ch: make(chan []byte)} |
3372 | + |
3373 | + ps.getTempDir = launch_helper.GetTempDir |
3374 | + d := c.MkDir() |
3375 | + launch_helper.GetTempDir = func(pkgName string) (string, error) { |
3376 | + tmpDir := filepath.Join(d, pkgName) |
3377 | + return tmpDir, os.MkdirAll(tmpDir, 0700) |
3378 | + } |
3379 | +} |
3380 | + |
3381 | +func (ps *postalSuite) TearDownTest(c *C) { |
3382 | + launch_helper.GetTempDir = ps.getTempDir |
3383 | +} |
3384 | + |
3385 | +func (ts *trivialPostalSuite) SetUpTest(c *C) { |
3386 | + ts.postalSuite.SetUpTest(c) |
3387 | + useTrivialHelper = true |
3388 | +} |
3389 | + |
3390 | +func (ts *trivialPostalSuite) TearDownTest(c *C) { |
3391 | + ts.postalSuite.TearDownTest(c) |
3392 | + useTrivialHelper = false |
3393 | +} |
3394 | + |
3395 | +func (ps *postalSuite) replaceBuses(pst *PostalService) *PostalService { |
3396 | + pst.Bus = ps.bus |
3397 | + pst.NotificationsEndp = ps.notifBus |
3398 | + pst.EmblemCounterEndp = ps.counterBus |
3399 | + pst.HapticEndp = ps.hapticBus |
3400 | + pst.URLDispatcherEndp = ps.urlDispBus |
3401 | + pst.WindowStackEndp = ps.winStackBus |
3402 | + pst.launchers = map[string]launch_helper.HelperLauncher{} |
3403 | + return pst |
3404 | +} |
3405 | + |
3406 | +func (ps *postalSuite) TestStart(c *C) { |
3407 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3408 | c.Check(svc.IsRunning(), Equals, false) |
3409 | c.Check(svc.Start(), IsNil) |
3410 | c.Check(svc.IsRunning(), Equals, true) |
3411 | svc.Stop() |
3412 | } |
3413 | |
3414 | -func (ss *postalSuite) TestStartTwice(c *C) { |
3415 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
3416 | +func (ps *postalSuite) TestStartTwice(c *C) { |
3417 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3418 | c.Check(svc.Start(), IsNil) |
3419 | c.Check(svc.Start(), Equals, ErrAlreadyStarted) |
3420 | svc.Stop() |
3421 | } |
3422 | |
3423 | -func (ss *postalSuite) TestStartNoLog(c *C) { |
3424 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, nil) |
3425 | - c.Check(svc.Start(), Equals, ErrNotConfigured) |
3426 | -} |
3427 | - |
3428 | -func (ss *postalSuite) TestStartNoBus(c *C) { |
3429 | - svc := NewPostalService(nil, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
3430 | - c.Check(svc.Start(), Equals, ErrNotConfigured) |
3431 | -} |
3432 | - |
3433 | -func (ss *postalSuite) TestTakeTheBustFail(c *C) { |
3434 | - nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(false), []interface{}{uint32(1), "hello"}) |
3435 | - svc := NewPostalService(ss.bus, nEndp, ss.counterBus, ss.hapticBus, ss.log) |
3436 | - _, err := svc.TakeTheBus() |
3437 | +func (ps *postalSuite) TestStartNoLog(c *C) { |
3438 | + svc := ps.replaceBuses(NewPostalService(nil, nil)) |
3439 | + c.Check(svc.Start(), Equals, ErrNotConfigured) |
3440 | +} |
3441 | + |
3442 | +func (ps *postalSuite) TestStartNoBus(c *C) { |
3443 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3444 | + svc.Bus = nil |
3445 | + c.Check(svc.Start(), Equals, ErrNotConfigured) |
3446 | + |
3447 | + svc = ps.replaceBuses(NewPostalService(nil, ps.log)) |
3448 | + svc.NotificationsEndp = nil |
3449 | + c.Check(svc.Start(), Equals, ErrNotConfigured) |
3450 | +} |
3451 | + |
3452 | +func (ps *postalSuite) TestTakeTheBusFail(c *C) { |
3453 | + nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(false)) |
3454 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3455 | + svc.NotificationsEndp = nEndp |
3456 | + _, err := svc.takeTheBus() |
3457 | c.Check(err, NotNil) |
3458 | } |
3459 | |
3460 | -func (ss *postalSuite) TestTakeTheBustOk(c *C) { |
3461 | +func (ps *postalSuite) TestTakeTheBusOk(c *C) { |
3462 | nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(true), []interface{}{uint32(1), "hello"}) |
3463 | - svc := NewPostalService(ss.bus, nEndp, ss.counterBus, ss.hapticBus, ss.log) |
3464 | - _, err := svc.TakeTheBus() |
3465 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3466 | + svc.NotificationsEndp = nEndp |
3467 | + _, err := svc.takeTheBus() |
3468 | c.Check(err, IsNil) |
3469 | } |
3470 | |
3471 | -func (ss *postalSuite) TestStartFailsOnBusDialFailure(c *C) { |
3472 | - bus := testibus.NewTestingEndpoint(condition.Work(false), nil) |
3473 | - svc := NewPostalService(bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
3474 | +func (ps *postalSuite) TestStartFailsOnBusDialFailure(c *C) { |
3475 | + // XXX actually, we probably want to autoredial this |
3476 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3477 | + svc.Bus = testibus.NewTestingEndpoint(condition.Work(false), nil) |
3478 | c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`) |
3479 | svc.Stop() |
3480 | } |
3481 | |
3482 | -func (ss *postalSuite) TestStartGrabsName(c *C) { |
3483 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
3484 | +func (ps *postalSuite) TestStartGrabsName(c *C) { |
3485 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3486 | c.Assert(svc.Start(), IsNil) |
3487 | - callArgs := testibus.GetCallArgs(ss.bus) |
3488 | + callArgs := testibus.GetCallArgs(ps.bus) |
3489 | defer svc.Stop() |
3490 | c.Assert(callArgs, NotNil) |
3491 | c.Check(callArgs[0].Member, Equals, "::GrabName") |
3492 | } |
3493 | |
3494 | -func (ss *postalSuite) TestStopClosesBus(c *C) { |
3495 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
3496 | +func (ps *postalSuite) TestStopClosesBus(c *C) { |
3497 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3498 | c.Assert(svc.Start(), IsNil) |
3499 | svc.Stop() |
3500 | - callArgs := testibus.GetCallArgs(ss.bus) |
3501 | + callArgs := testibus.GetCallArgs(ps.bus) |
3502 | c.Assert(callArgs, NotNil) |
3503 | c.Check(callArgs[len(callArgs)-1].Member, Equals, "::Close") |
3504 | } |
3505 | |
3506 | // |
3507 | -// Injection tests |
3508 | - |
3509 | -func (ss *postalSuite) TestInjectWorks(c *C) { |
3510 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
3511 | - svc.msgHandler = nil |
3512 | - rvs, err := svc.inject(aPackageOnBus, []interface{}{anAppId, "world"}, nil) |
3513 | - c.Assert(err, IsNil) |
3514 | - c.Check(rvs, IsNil) |
3515 | - rvs, err = svc.inject(aPackageOnBus, []interface{}{anAppId, "there"}, nil) |
3516 | - c.Assert(err, IsNil) |
3517 | - c.Check(rvs, IsNil) |
3518 | - c.Assert(svc.mbox, HasLen, 1) |
3519 | - c.Assert(svc.mbox[anAppId], HasLen, 2) |
3520 | - c.Check(svc.mbox[anAppId][0], Equals, "world") |
3521 | - c.Check(svc.mbox[anAppId][1], Equals, "there") |
3522 | - |
3523 | - // and check it fired the right signal (twice) |
3524 | - callArgs := testibus.GetCallArgs(ss.bus) |
3525 | - c.Assert(callArgs, HasLen, 2) |
3526 | - c.Check(callArgs[0].Member, Equals, "::Signal") |
3527 | - c.Check(callArgs[0].Args, DeepEquals, []interface{}{"Post", aPackageOnBus, []interface{}{anAppId}}) |
3528 | - c.Check(callArgs[1], DeepEquals, callArgs[0]) |
3529 | -} |
3530 | - |
3531 | -func (ss *postalSuite) TestInjectFailsIfInjectFails(c *C) { |
3532 | +// Post() tests |
3533 | + |
3534 | +func (ps *postalSuite) TestPostWorks(c *C) { |
3535 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3536 | + svc.msgHandler = nil |
3537 | + ch := installTickMessageHandler(svc) |
3538 | + fakeLauncher2 := &fakeHelperLauncher{ch: make(chan []byte)} |
3539 | + svc.launchers = map[string]launch_helper.HelperLauncher{ |
3540 | + "click": ps.fakeLauncher, |
3541 | + "legacy": fakeLauncher2, |
3542 | + } |
3543 | + c.Assert(svc.Start(), IsNil) |
3544 | + rvs, err := svc.post(aPackageOnBus, []interface{}{anAppId, `{"message":{"world":1}}`}, nil) |
3545 | + c.Assert(err, IsNil) |
3546 | + c.Check(rvs, IsNil) |
3547 | + rvs, err = svc.post(aPackageOnBus, []interface{}{anAppId, `{"message":{"moon":1}}`}, nil) |
3548 | + c.Assert(err, IsNil) |
3549 | + c.Check(rvs, IsNil) |
3550 | + rvs, err = svc.post("_", []interface{}{"_classic-app", `{"message":{"mars":42}}`}, nil) |
3551 | + c.Assert(err, IsNil) |
3552 | + c.Check(rvs, IsNil) |
3553 | + |
3554 | + if ps.fakeLauncher.done != nil { |
3555 | + // wait for the two posts to "launch" |
3556 | + takeNextBytes(ps.fakeLauncher.ch) |
3557 | + takeNextBytes(ps.fakeLauncher.ch) |
3558 | + takeNextBytes(fakeLauncher2.ch) |
3559 | + |
3560 | + go ps.fakeLauncher.done("0") // OneDone |
3561 | + go ps.fakeLauncher.done("1") // OneDone |
3562 | + go fakeLauncher2.done("0") |
3563 | + } |
3564 | + |
3565 | + c.Check(takeNextError(ch), IsNil) // one, |
3566 | + c.Check(takeNextError(ch), IsNil) // two, |
3567 | + c.Check(takeNextError(ch), IsNil) // three posts |
3568 | + c.Assert(svc.mbox, HasLen, 2) |
3569 | + box, ok := svc.mbox[anAppId] |
3570 | + c.Check(ok, Equals, true) |
3571 | + msgs := box.AllMessages() |
3572 | + c.Assert(msgs, HasLen, 2) |
3573 | + c.Check(msgs[0], Equals, `{"world":1}`) |
3574 | + c.Check(msgs[1], Equals, `{"moon":1}`) |
3575 | + box, ok = svc.mbox["_classic-app"] |
3576 | + c.Assert(ok, Equals, true) |
3577 | + msgs = box.AllMessages() |
3578 | + c.Assert(msgs, HasLen, 1) |
3579 | + c.Check(msgs[0], Equals, `{"mars":42}`) |
3580 | +} |
3581 | + |
3582 | +func (ps *postalSuite) TestPostSignal(c *C) { |
3583 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3584 | + svc.msgHandler = nil |
3585 | + |
3586 | + hInp := &launch_helper.HelperInput{ |
3587 | + App: clickhelp.MustParseAppId(anAppId), |
3588 | + } |
3589 | + res := &launch_helper.HelperResult{Input: hInp} |
3590 | + |
3591 | + svc.handleHelperResult(res) |
3592 | + |
3593 | + // and check it fired the right signal |
3594 | + callArgs := testibus.GetCallArgs(ps.bus) |
3595 | + l := len(callArgs) |
3596 | + if l < 1 { |
3597 | + c.Fatal("not enough elements in resposne from GetCallArgs") |
3598 | + } |
3599 | + c.Check(callArgs[l-1].Member, Equals, "::Signal") |
3600 | + c.Check(callArgs[l-1].Args, DeepEquals, []interface{}{"Post", aPackageOnBus, []interface{}{anAppId}}) |
3601 | +} |
3602 | + |
3603 | +func (ps *postalSuite) TestPostFailsIfPostFails(c *C) { |
3604 | bus := testibus.NewTestingEndpoint(condition.Work(true), |
3605 | condition.Work(false)) |
3606 | - svc := NewPostalService(bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
3607 | - svc.SetMessageHandler(func(string, string, *launch_helper.HelperOutput) error { return errors.New("fail") }) |
3608 | - _, err := svc.inject("/hello", []interface{}{"xyzzy"}, nil) |
3609 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3610 | + svc.Bus = bus |
3611 | + svc.SetMessageHandler(func(*click.AppId, string, *launch_helper.HelperOutput) error { return errors.New("fail") }) |
3612 | + _, err := svc.post("/hello", []interface{}{"xyzzy"}, nil) |
3613 | c.Check(err, NotNil) |
3614 | } |
3615 | |
3616 | -func (ss *postalSuite) TestInjectFailsIfBadArgs(c *C) { |
3617 | +func (ps *postalSuite) TestPostFailsIfBadArgs(c *C) { |
3618 | for i, s := range []struct { |
3619 | args []interface{} |
3620 | errt error |
3621 | @@ -154,69 +360,119 @@ |
3622 | {[]interface{}{}, ErrBadArgCount}, |
3623 | {[]interface{}{1}, ErrBadArgCount}, |
3624 | {[]interface{}{anAppId, 1}, ErrBadArgType}, |
3625 | + {[]interface{}{anAppId, "zoom"}, ErrBadJSON}, |
3626 | {[]interface{}{1, "hello"}, ErrBadArgType}, |
3627 | {[]interface{}{1, 2, 3}, ErrBadArgCount}, |
3628 | {[]interface{}{"bar", "hello"}, ErrBadAppId}, |
3629 | } { |
3630 | - reg, err := new(PostalService).inject(aPackageOnBus, s.args, nil) |
3631 | + reg, err := new(PostalService).post(aPackageOnBus, s.args, nil) |
3632 | c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
3633 | c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
3634 | } |
3635 | } |
3636 | |
3637 | // |
3638 | -// Injection (Broadcast) tests |
3639 | - |
3640 | -func (ss *postalSuite) TestInjectBroadcast(c *C) { |
3641 | - bus := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
3642 | - svc := NewPostalService(ss.bus, bus, ss.counterBus, ss.hapticBus, ss.log) |
3643 | - //svc.msgHandler = nil |
3644 | - rvs, err := svc.InjectBroadcast() |
3645 | - c.Assert(err, IsNil) |
3646 | - c.Check(rvs, Equals, uint32(0)) |
3647 | - c.Assert(err, IsNil) |
3648 | - // and check it fired the right signal (twice) |
3649 | - callArgs := testibus.GetCallArgs(bus) |
3650 | - c.Assert(callArgs, HasLen, 1) |
3651 | - c.Check(callArgs[0].Member, Equals, "Notify") |
3652 | - c.Check(callArgs[0].Args[0:6], DeepEquals, []interface{}{"ubuntu-push-client", uint32(0), "update_manager_icon", |
3653 | - "There's an updated system image.", "Tap to open the system updater.", |
3654 | - []string{"ubuntu-push-client::settings:///system/system-update::0", "Switch to app"}}) |
3655 | - // TODO: check the map in callArgs? |
3656 | - // c.Check(callArgs[0].Args[7]["x-canonical-secondary-icon"], NotNil) |
3657 | - // c.Check(callArgs[0].Args[7]["x-canonical-snap-decisions"], NotNil) |
3658 | +// Post (Broadcast) tests |
3659 | + |
3660 | +func (ps *postalSuite) TestPostBroadcast(c *C) { |
3661 | + |
3662 | + bus := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1)) |
3663 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3664 | + |
3665 | + svc.NotificationsEndp = bus |
3666 | + svc.launchers = map[string]launch_helper.HelperLauncher{ |
3667 | + "legacy": ps.fakeLauncher, |
3668 | + } |
3669 | + c.Assert(svc.Start(), IsNil) |
3670 | + |
3671 | + msgId := uuid.New() |
3672 | + svc.SetMessageHandler(func(app *click.AppId, nid string, output *launch_helper.HelperOutput) error { |
3673 | + expectedAppId, _ := click.ParseAppId("_ubuntu-system-settings") |
3674 | + c.Check(app, DeepEquals, expectedAppId) |
3675 | + c.Check(nid, Equals, msgId) |
3676 | + return nil |
3677 | + }) |
3678 | + decoded := map[string]interface{}{ |
3679 | + "daily/mako": []interface{}{float64(102), "tubular"}, |
3680 | + } |
3681 | + // marshal decoded to json |
3682 | + payload, _ := json.Marshal(decoded) |
3683 | + appId, _ := click.ParseAppId("_ubuntu-system-settings") |
3684 | + err := svc.Post(appId, msgId, payload) |
3685 | + c.Assert(err, IsNil) |
3686 | + |
3687 | + if ps.fakeLauncher.done != nil { |
3688 | + inputData := takeNextBytes(ps.fakeLauncher.ch) |
3689 | + expectedData, _ := json.Marshal(decoded) |
3690 | + c.Check(inputData, DeepEquals, expectedData) |
3691 | + go ps.fakeLauncher.done("0") // OneDone |
3692 | + } |
3693 | } |
3694 | |
3695 | -func (ss *postalSuite) TestInjectBroadcastFails(c *C) { |
3696 | +func (ps *postalSuite) TestPostBroadcastDoesNotFail(c *C) { |
3697 | bus := testibus.NewTestingEndpoint(condition.Work(true), |
3698 | condition.Work(false)) |
3699 | - svc := NewPostalService(ss.bus, bus, ss.counterBus, ss.hapticBus, ss.log) |
3700 | - svc.SetMessageHandler(func(string, string, *launch_helper.HelperOutput) error { return errors.New("fail") }) |
3701 | - _, err := svc.InjectBroadcast() |
3702 | - c.Check(err, NotNil) |
3703 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3704 | + svc.launchers = map[string]launch_helper.HelperLauncher{ |
3705 | + "legacy": ps.fakeLauncher, |
3706 | + } |
3707 | + c.Assert(svc.Start(), IsNil) |
3708 | + svc.NotificationsEndp = bus |
3709 | + svc.SetMessageHandler(func(*click.AppId, string, *launch_helper.HelperOutput) error { |
3710 | + ps.log.Debugf("about to fail") |
3711 | + return errors.New("fail") |
3712 | + }) |
3713 | + ch := installTickMessageHandler(svc) |
3714 | + decoded := map[string]interface{}{ |
3715 | + "daily/mako": []interface{}{float64(102), "tubular"}, |
3716 | + } |
3717 | + // marshal decoded to json |
3718 | + payload, _ := json.Marshal(decoded) |
3719 | + appId, _ := click.ParseAppId("_ubuntu-system-settings") |
3720 | + msgId := uuid.New() |
3721 | + err := svc.Post(appId, msgId, payload) |
3722 | + c.Assert(err, IsNil) |
3723 | + |
3724 | + if ps.fakeLauncher.done != nil { |
3725 | + takeNextBytes(ps.fakeLauncher.ch) |
3726 | + go ps.fakeLauncher.done("0") // OneDone |
3727 | + } |
3728 | + |
3729 | + c.Check(takeNextError(ch), NotNil) // the messagehandler failed |
3730 | + c.Check(err, IsNil) // but broadcast was oblivious |
3731 | + c.Check(ps.log.Captured(), Matches, `(?sm).*about to fail$`) |
3732 | } |
3733 | |
3734 | // |
3735 | // Notifications tests |
3736 | -func (ss *postalSuite) TestNotificationsWorks(c *C) { |
3737 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
3738 | - nots, err := svc.notifications(aPackageOnBus, []interface{}{anAppId}, nil) |
3739 | - c.Assert(err, IsNil) |
3740 | - c.Assert(nots, NotNil) |
3741 | - c.Assert(nots, HasLen, 1) |
3742 | - c.Check(nots[0], HasLen, 0) |
3743 | - if svc.mbox == nil { |
3744 | - svc.mbox = make(map[string][]string) |
3745 | - } |
3746 | - svc.mbox[anAppId] = append(svc.mbox[anAppId], "this", "thing") |
3747 | - nots, err = svc.notifications(aPackageOnBus, []interface{}{anAppId}, nil) |
3748 | - c.Assert(err, IsNil) |
3749 | - c.Assert(nots, NotNil) |
3750 | - c.Assert(nots, HasLen, 1) |
3751 | - c.Check(nots[0], DeepEquals, []string{"this", "thing"}) |
3752 | +func (ps *postalSuite) TestNotificationsWorks(c *C) { |
3753 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3754 | + nots, err := svc.popAll(aPackageOnBus, []interface{}{anAppId}, nil) |
3755 | + c.Assert(err, IsNil) |
3756 | + c.Assert(nots, NotNil) |
3757 | + c.Assert(nots, HasLen, 1) |
3758 | + c.Check(nots[0], HasLen, 0) |
3759 | + c.Assert(svc.mbox, IsNil) |
3760 | + svc.mbox = make(map[string]*mBox) |
3761 | + nots, err = svc.popAll(aPackageOnBus, []interface{}{anAppId}, nil) |
3762 | + c.Assert(err, IsNil) |
3763 | + c.Assert(nots, NotNil) |
3764 | + c.Assert(nots, HasLen, 1) |
3765 | + c.Check(nots[0], HasLen, 0) |
3766 | + box := new(mBox) |
3767 | + svc.mbox[anAppId] = box |
3768 | + m1 := json.RawMessage(`"m1"`) |
3769 | + m2 := json.RawMessage(`"m2"`) |
3770 | + box.Append(m1, "n1") |
3771 | + box.Append(m2, "n2") |
3772 | + nots, err = svc.popAll(aPackageOnBus, []interface{}{anAppId}, nil) |
3773 | + c.Assert(err, IsNil) |
3774 | + c.Assert(nots, NotNil) |
3775 | + c.Assert(nots, HasLen, 1) |
3776 | + c.Check(nots[0], DeepEquals, []string{string(m1), string(m2)}) |
3777 | } |
3778 | |
3779 | -func (ss *postalSuite) TestNotificationsFailsIfBadArgs(c *C) { |
3780 | +func (ps *postalSuite) TestNotificationsFailsIfBadArgs(c *C) { |
3781 | for i, s := range []struct { |
3782 | args []interface{} |
3783 | errt error |
3784 | @@ -226,52 +482,77 @@ |
3785 | {[]interface{}{1}, ErrBadArgType}, |
3786 | {[]interface{}{"potato"}, ErrBadAppId}, |
3787 | } { |
3788 | - reg, err := new(PostalService).notifications(aPackageOnBus, s.args, nil) |
3789 | + reg, err := new(PostalService).popAll(aPackageOnBus, s.args, nil) |
3790 | c.Check(reg, IsNil, Commentf("iteration #%d", i)) |
3791 | c.Check(err, Equals, s.errt, Commentf("iteration #%d", i)) |
3792 | } |
3793 | } |
3794 | |
3795 | -func (ss *postalSuite) TestMessageHandlerPublicAPI(c *C) { |
3796 | +func (ps *postalSuite) TestMessageHandlerPublicAPI(c *C) { |
3797 | svc := new(PostalService) |
3798 | c.Assert(svc.msgHandler, IsNil) |
3799 | var ext = &launch_helper.HelperOutput{} |
3800 | e := errors.New("Hello") |
3801 | - f := func(app string, nid string, s *launch_helper.HelperOutput) error { ext = s; return e } |
3802 | + f := func(_ *click.AppId, _ string, s *launch_helper.HelperOutput) error { ext = s; return e } |
3803 | c.Check(svc.GetMessageHandler(), IsNil) |
3804 | svc.SetMessageHandler(f) |
3805 | c.Check(svc.GetMessageHandler(), NotNil) |
3806 | hOutput := &launch_helper.HelperOutput{[]byte("37"), nil} |
3807 | - c.Check(svc.msgHandler("", "", hOutput), Equals, e) |
3808 | + c.Check(svc.msgHandler(nil, "", hOutput), Equals, e) |
3809 | c.Check(ext, DeepEquals, hOutput) |
3810 | } |
3811 | |
3812 | -func (ss *postalSuite) TestInjectCallsMessageHandler(c *C) { |
3813 | - var ext = &launch_helper.HelperOutput{} |
3814 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
3815 | - f := func(app string, nid string, s *launch_helper.HelperOutput) error { ext = s; return nil } |
3816 | +func (ps *postalSuite) TestPostCallsMessageHandler(c *C) { |
3817 | + ch := make(chan *launch_helper.HelperOutput) |
3818 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3819 | + svc.launchers = map[string]launch_helper.HelperLauncher{ |
3820 | + "click": ps.fakeLauncher, |
3821 | + } |
3822 | + c.Assert(svc.Start(), IsNil) |
3823 | + // check the message handler gets called |
3824 | + f := func(_ *click.AppId, _ string, s *launch_helper.HelperOutput) error { ch <- s; return nil } |
3825 | svc.SetMessageHandler(f) |
3826 | - c.Check(svc.Inject("pkg", "app", "thing", "{}"), IsNil) |
3827 | - c.Check(ext, DeepEquals, &launch_helper.HelperOutput{}) |
3828 | + c.Check(svc.Post(&click.AppId{Click: true}, "thing", json.RawMessage("{}")), IsNil) |
3829 | + |
3830 | + if ps.fakeLauncher.done != nil { |
3831 | + takeNextBytes(ps.fakeLauncher.ch) |
3832 | + |
3833 | + go ps.fakeLauncher.done("0") // OneDone |
3834 | + } |
3835 | + |
3836 | + c.Check(takeNextHelperOutput(ch), DeepEquals, &launch_helper.HelperOutput{}) |
3837 | err := errors.New("ouch") |
3838 | - svc.SetMessageHandler(func(string, string, *launch_helper.HelperOutput) error { return err }) |
3839 | - c.Check(svc.Inject("pkg", "app", "", "{}"), Equals, err) |
3840 | + svc.SetMessageHandler(func(*click.AppId, string, *launch_helper.HelperOutput) error { return err }) |
3841 | + // but the error doesn't bubble out |
3842 | + c.Check(svc.Post(&click.AppId{}, "", json.RawMessage("{}")), IsNil) |
3843 | } |
3844 | |
3845 | -func (ss *postalSuite) TestMessageHandlerPresents(c *C) { |
3846 | - endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
3847 | - svc := NewPostalService(endp, endp, endp, endp, ss.log) |
3848 | +func (ps *postalSuite) TestMessageHandlerPresents(c *C) { |
3849 | + endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1)) |
3850 | + svc := NewPostalService(nil, ps.log) |
3851 | + svc.Bus = endp |
3852 | + svc.EmblemCounterEndp = endp |
3853 | + svc.HapticEndp = endp |
3854 | + svc.NotificationsEndp = endp |
3855 | + svc.URLDispatcherEndp = ps.urlDispBus |
3856 | + svc.WindowStackEndp = ps.winStackBus |
3857 | + svc.launchers = map[string]launch_helper.HelperLauncher{} |
3858 | + c.Assert(svc.Start(), IsNil) |
3859 | + |
3860 | // Persist is false so we just check the log |
3861 | card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true, Persist: false} |
3862 | vib := &launch_helper.Vibration{Duration: 500} |
3863 | emb := &launch_helper.EmblemCounter{Count: 2, Visible: true} |
3864 | output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card, EmblemCounter: emb, Vibrate: vib}} |
3865 | - err := svc.messageHandler("com.example.test_test", "", output) |
3866 | + err := svc.messageHandler(&click.AppId{}, "", output) |
3867 | c.Assert(err, IsNil) |
3868 | args := testibus.GetCallArgs(endp) |
3869 | - c.Assert(args, HasLen, 4) |
3870 | - mm := make([]string, len(args)) |
3871 | - for i, m := range args { |
3872 | + l := len(args) |
3873 | + if l < 4 { |
3874 | + c.Fatal("not enough elements in resposne from GetCallArgs") |
3875 | + } |
3876 | + mm := make([]string, 4) |
3877 | + for i, m := range args[l-4:] { |
3878 | mm[i] = m.Member |
3879 | } |
3880 | sort.Strings(mm) |
3881 | @@ -279,33 +560,91 @@ |
3882 | // For dbus-backed presenters, just check the right dbus methods are called |
3883 | c.Check(mm, DeepEquals, []string{"::SetProperty", "::SetProperty", "Notify", "VibratePattern"}) |
3884 | // For the other ones, check the logs |
3885 | - c.Check(ss.log.Captured(), Matches, `(?sm).* no persistable card:.*`) |
3886 | - c.Check(ss.log.Captured(), Matches, `(?sm).* no Sound in the notification.*`) |
3887 | + c.Check(ps.log.Captured(), Matches, `(?sm).* no persistable card:.*`) |
3888 | + c.Check(ps.log.Captured(), Matches, `(?sm).* no Sound in the notification.*`) |
3889 | } |
3890 | |
3891 | -func (ss *postalSuite) TestMessageHandlerReportsFailedNotifies(c *C) { |
3892 | - endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) |
3893 | - svc := NewPostalService(ss.bus, endp, ss.counterBus, ss.hapticBus, ss.log) |
3894 | +func (ps *postalSuite) TestMessageHandlerReportsFailedNotifies(c *C) { |
3895 | + endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), 1) |
3896 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3897 | + svc.NotificationsEndp = endp |
3898 | + c.Assert(svc.Start(), IsNil) |
3899 | card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true} |
3900 | notif := &launch_helper.Notification{Card: card} |
3901 | output := &launch_helper.HelperOutput{Notification: notif} |
3902 | - err := svc.messageHandler("", "", output) |
3903 | + err := svc.messageHandler(&click.AppId{}, "", output) |
3904 | c.Assert(err, NotNil) |
3905 | } |
3906 | |
3907 | -func (ss *postalSuite) TestMessageHandlerReportsButIgnoresUnmarshalErrors(c *C) { |
3908 | - svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log) |
3909 | +func (ps *postalSuite) TestMessageHandlerInhibition(c *C) { |
3910 | + endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{{0, "com.example.test_test-app", true, 0}}) |
3911 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3912 | + svc.WindowStackEndp = endp |
3913 | + c.Assert(svc.Start(), IsNil) |
3914 | + output := &launch_helper.HelperOutput{} // Doesn't matter |
3915 | + err := svc.messageHandler(clickhelp.MustParseAppId("com.example.test_test-app_0"), "", output) |
3916 | + c.Check(err, IsNil) |
3917 | + c.Check(ps.log.Captured(), Matches, `(?sm).* Notification skipped because app is focused.*`) |
3918 | +} |
3919 | + |
3920 | +func (ps *postalSuite) TestMessageHandlerReportsButIgnoresUnmarshalErrors(c *C) { |
3921 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3922 | + c.Assert(svc.Start(), IsNil) |
3923 | output := &launch_helper.HelperOutput{[]byte(`broken`), nil} |
3924 | - err := svc.messageHandler("", "", output) |
3925 | + err := svc.messageHandler(nil, "", output) |
3926 | c.Check(err, IsNil) |
3927 | - c.Check(ss.log.Captured(), Matches, "(?msi).*skipping notification: nil.*") |
3928 | + c.Check(ps.log.Captured(), Matches, "(?msi).*skipping notification: nil.*") |
3929 | } |
3930 | |
3931 | -func (ss *postalSuite) TestMessageHandlerReportsButIgnoresNilNotifies(c *C) { |
3932 | - endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) |
3933 | - svc := NewPostalService(ss.bus, endp, ss.counterBus, ss.hapticBus, ss.log) |
3934 | +func (ps *postalSuite) TestMessageHandlerReportsButIgnoresNilNotifies(c *C) { |
3935 | + endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) |
3936 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3937 | + c.Assert(svc.Start(), IsNil) |
3938 | + svc.NotificationsEndp = endp |
3939 | output := &launch_helper.HelperOutput{[]byte(`{}`), nil} |
3940 | - err := svc.messageHandler("", "", output) |
3941 | + err := svc.messageHandler(nil, "", output) |
3942 | c.Assert(err, IsNil) |
3943 | - c.Check(ss.log.Captured(), Matches, "(?msi).*skipping notification: nil.*") |
3944 | + c.Check(ps.log.Captured(), Matches, "(?msi).*skipping notification: nil.*") |
3945 | +} |
3946 | + |
3947 | +func (ps *postalSuite) TestHandleActionsDispatches(c *C) { |
3948 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3949 | + c.Assert(svc.Start(), IsNil) |
3950 | + aCh := make(chan *notifications.RawAction) |
3951 | + rCh := make(chan *reply.MMActionReply) |
3952 | + bCh := make(chan bool) |
3953 | + go func() { |
3954 | + aCh <- nil // just in case? |
3955 | + aCh <- ¬ifications.RawAction{Action: "potato://"} |
3956 | + close(aCh) |
3957 | + bCh <- true |
3958 | + }() |
3959 | + go svc.handleActions(aCh, rCh) |
3960 | + takeNextBool(bCh) |
3961 | + args := testibus.GetCallArgs(ps.urlDispBus) |
3962 | + c.Assert(args, HasLen, 1) |
3963 | + c.Check(args[0].Member, Equals, "DispatchURL") |
3964 | + c.Assert(args[0].Args, HasLen, 1) |
3965 | + c.Assert(args[0].Args[0], Equals, "potato://") |
3966 | +} |
3967 | + |
3968 | +func (ps *postalSuite) TestHandleMMUActionsDispatches(c *C) { |
3969 | + svc := ps.replaceBuses(NewPostalService(nil, ps.log)) |
3970 | + c.Assert(svc.Start(), IsNil) |
3971 | + aCh := make(chan *notifications.RawAction) |
3972 | + rCh := make(chan *reply.MMActionReply) |
3973 | + bCh := make(chan bool) |
3974 | + go func() { |
3975 | + rCh <- nil // just in case? |
3976 | + rCh <- &reply.MMActionReply{Action: "potato://", Notification: "foo.bar"} |
3977 | + close(rCh) |
3978 | + bCh <- true |
3979 | + }() |
3980 | + go svc.handleActions(aCh, rCh) |
3981 | + takeNextBool(bCh) |
3982 | + args := testibus.GetCallArgs(ps.urlDispBus) |
3983 | + c.Assert(args, HasLen, 1) |
3984 | + c.Check(args[0].Member, Equals, "DispatchURL") |
3985 | + c.Assert(args[0].Args, HasLen, 1) |
3986 | + c.Assert(args[0].Args[0], Equals, "potato://") |
3987 | } |
3988 | |
3989 | === modified file 'client/service/service.go' |
3990 | --- client/service/service.go 2014-07-04 19:23:40 +0000 |
3991 | +++ client/service/service.go 2014-07-21 14:14:04 +0000 |
3992 | @@ -26,17 +26,20 @@ |
3993 | "net/url" |
3994 | "os" |
3995 | |
3996 | + http13 "launchpad.net/ubuntu-push/http13client" |
3997 | + |
3998 | "launchpad.net/ubuntu-push/bus" |
3999 | - http13 "launchpad.net/ubuntu-push/http13client" |
4000 | + "launchpad.net/ubuntu-push/click" |
4001 | "launchpad.net/ubuntu-push/logger" |
4002 | "launchpad.net/ubuntu-push/nih" |
4003 | ) |
4004 | |
4005 | // PushServiceSetup encapsulates the params for setting up a PushService. |
4006 | type PushServiceSetup struct { |
4007 | - RegURL *url.URL |
4008 | - DeviceId string |
4009 | - AuthGetter func(string) string |
4010 | + RegURL *url.URL |
4011 | + DeviceId string |
4012 | + AuthGetter func(string) string |
4013 | + InstalledChecker click.InstalledChecker |
4014 | } |
4015 | |
4016 | // PushService is the dbus api |
4017 | @@ -57,10 +60,11 @@ |
4018 | ) |
4019 | |
4020 | // NewPushService() builds a new service and returns it. |
4021 | -func NewPushService(bus bus.Endpoint, setup *PushServiceSetup, log logger.Logger) *PushService { |
4022 | +func NewPushService(setup *PushServiceSetup, log logger.Logger) *PushService { |
4023 | var svc = &PushService{} |
4024 | svc.Log = log |
4025 | - svc.Bus = bus |
4026 | + svc.Bus = bus.SessionBus.Endpoint(PushServiceBusAddress, log) |
4027 | + svc.installedChecker = setup.InstalledChecker |
4028 | svc.regURL = setup.RegURL |
4029 | svc.deviceId = setup.DeviceId |
4030 | svc.authGetter = setup.AuthGetter |
4031 | @@ -158,18 +162,18 @@ |
4032 | } |
4033 | |
4034 | func (svc *PushService) register(path string, args, _ []interface{}) ([]interface{}, error) { |
4035 | - _, appId, err := grabDBusPackageAndAppId(path, args, 0) |
4036 | + app, err := svc.grabDBusPackageAndAppId(path, args, 0) |
4037 | if err != nil { |
4038 | return nil, err |
4039 | } |
4040 | |
4041 | - rawAppId := string(nih.Quote([]byte(appId))) |
4042 | + rawAppId := string(nih.Quote([]byte(app.Original()))) |
4043 | rv := os.Getenv("PUSH_REG_" + rawAppId) |
4044 | if rv != "" { |
4045 | return []interface{}{rv}, nil |
4046 | } |
4047 | |
4048 | - reply, err := svc.manageReg("/register", appId) |
4049 | + reply, err := svc.manageReg("/register", app.Original()) |
4050 | if err != nil { |
4051 | return nil, err |
4052 | } |
4053 | @@ -183,12 +187,12 @@ |
4054 | } |
4055 | |
4056 | func (svc *PushService) unregister(path string, args, _ []interface{}) ([]interface{}, error) { |
4057 | - _, appId, err := grabDBusPackageAndAppId(path, args, 0) |
4058 | + app, err := svc.grabDBusPackageAndAppId(path, args, 0) |
4059 | if err != nil { |
4060 | return nil, err |
4061 | } |
4062 | |
4063 | - return nil, svc.Unregister(appId) |
4064 | + return nil, svc.Unregister(app.Original()) |
4065 | } |
4066 | |
4067 | func (svc *PushService) Unregister(appId string) error { |
4068 | |
4069 | === modified file 'client/service/service_test.go' |
4070 | --- client/service/service_test.go 2014-07-04 19:23:40 +0000 |
4071 | +++ client/service/service_test.go 2014-07-21 14:14:04 +0000 |
4072 | @@ -64,15 +64,15 @@ |
4073 | return "" |
4074 | }, |
4075 | } |
4076 | - svc := NewPushService(ss.bus, setup, ss.log) |
4077 | - c.Check(svc.Bus, Equals, ss.bus) |
4078 | + svc := NewPushService(setup, ss.log) |
4079 | c.Check(svc.regURL, DeepEquals, helpers.ParseURL("http://reg")) |
4080 | c.Check(fmt.Sprintf("%#v", svc.authGetter), Equals, fmt.Sprintf("%#v", setup.AuthGetter)) |
4081 | // ... |
4082 | } |
4083 | |
4084 | func (ss *serviceSuite) TestStart(c *C) { |
4085 | - svc := NewPushService(ss.bus, testSetup, ss.log) |
4086 | + svc := NewPushService(testSetup, ss.log) |
4087 | + svc.Bus = ss.bus |
4088 | c.Check(svc.IsRunning(), Equals, false) |
4089 | c.Check(svc.Start(), IsNil) |
4090 | c.Check(svc.IsRunning(), Equals, true) |
4091 | @@ -80,31 +80,36 @@ |
4092 | } |
4093 | |
4094 | func (ss *serviceSuite) TestStartTwice(c *C) { |
4095 | - svc := NewPushService(ss.bus, testSetup, ss.log) |
4096 | + svc := NewPushService(testSetup, ss.log) |
4097 | + svc.Bus = ss.bus |
4098 | c.Check(svc.Start(), IsNil) |
4099 | c.Check(svc.Start(), Equals, ErrAlreadyStarted) |
4100 | svc.Stop() |
4101 | } |
4102 | |
4103 | func (ss *serviceSuite) TestStartNoLog(c *C) { |
4104 | - svc := NewPushService(ss.bus, testSetup, nil) |
4105 | + svc := NewPushService(testSetup, nil) |
4106 | + svc.Bus = ss.bus |
4107 | c.Check(svc.Start(), Equals, ErrNotConfigured) |
4108 | } |
4109 | |
4110 | func (ss *serviceSuite) TestStartNoBus(c *C) { |
4111 | - svc := NewPushService(nil, testSetup, ss.log) |
4112 | + svc := NewPushService(testSetup, ss.log) |
4113 | + svc.Bus = nil |
4114 | c.Check(svc.Start(), Equals, ErrNotConfigured) |
4115 | } |
4116 | |
4117 | func (ss *serviceSuite) TestStartFailsOnBusDialFailure(c *C) { |
4118 | bus := testibus.NewTestingEndpoint(condition.Work(false), nil) |
4119 | - svc := NewPushService(bus, testSetup, ss.log) |
4120 | + svc := NewPushService(testSetup, ss.log) |
4121 | + svc.Bus = bus |
4122 | c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`) |
4123 | svc.Stop() |
4124 | } |
4125 | |
4126 | func (ss *serviceSuite) TestStartGrabsName(c *C) { |
4127 | - svc := NewPushService(ss.bus, testSetup, ss.log) |
4128 | + svc := NewPushService(testSetup, ss.log) |
4129 | + svc.Bus = ss.bus |
4130 | c.Assert(svc.Start(), IsNil) |
4131 | callArgs := testibus.GetCallArgs(ss.bus) |
4132 | defer svc.Stop() |
4133 | @@ -113,7 +118,8 @@ |
4134 | } |
4135 | |
4136 | func (ss *serviceSuite) TestStopClosesBus(c *C) { |
4137 | - svc := NewPushService(ss.bus, testSetup, ss.log) |
4138 | + svc := NewPushService(testSetup, ss.log) |
4139 | + svc.Bus = ss.bus |
4140 | c.Assert(svc.Start(), IsNil) |
4141 | svc.Stop() |
4142 | callArgs := testibus.GetCallArgs(ss.bus) |
4143 | @@ -132,7 +138,8 @@ |
4144 | return "Auth " + s |
4145 | }, |
4146 | } |
4147 | - svc := NewPushService(ss.bus, setup, ss.log) |
4148 | + svc := NewPushService(setup, ss.log) |
4149 | + svc.Bus = ss.bus |
4150 | url, auth := svc.getAuthorization("/op") |
4151 | c.Check(auth, Equals, "Auth http://foo/op") |
4152 | c.Assert(len(ch), Equals, 1) |
4153 | @@ -141,7 +148,8 @@ |
4154 | } |
4155 | |
4156 | func (ss *serviceSuite) TestGetRegAuthDoesNotPanic(c *C) { |
4157 | - svc := NewPushService(ss.bus, testSetup, ss.log) |
4158 | + svc := NewPushService(testSetup, ss.log) |
4159 | + svc.Bus = ss.bus |
4160 | _, auth := svc.getAuthorization("/op") |
4161 | c.Check(auth, Equals, "") |
4162 | } |
4163 | @@ -185,7 +193,8 @@ |
4164 | RegURL: helpers.ParseURL(ts.URL), |
4165 | AuthGetter: func(string) string { return "tok" }, |
4166 | } |
4167 | - svc := NewPushService(ss.bus, setup, ss.log) |
4168 | + svc := NewPushService(setup, ss.log) |
4169 | + svc.Bus = ss.bus |
4170 | // this'll check (un)quoting, too |
4171 | reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
4172 | c.Assert(err, IsNil) |
4173 | @@ -210,7 +219,8 @@ |
4174 | |
4175 | func (ss *serviceSuite) TestManageRegFailsOnBadAuth(c *C) { |
4176 | // ... no auth added |
4177 | - svc := NewPushService(ss.bus, testSetup, ss.log) |
4178 | + svc := NewPushService(testSetup, ss.log) |
4179 | + svc.Bus = ss.bus |
4180 | reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
4181 | c.Check(reg, IsNil) |
4182 | c.Check(err, Equals, ErrBadAuth) |
4183 | @@ -222,7 +232,8 @@ |
4184 | RegURL: helpers.ParseURL("xyzzy://"), |
4185 | AuthGetter: func(string) string { return "tok" }, |
4186 | } |
4187 | - svc := NewPushService(ss.bus, setup, ss.log) |
4188 | + svc := NewPushService(setup, ss.log) |
4189 | + svc.Bus = ss.bus |
4190 | reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
4191 | c.Check(reg, IsNil) |
4192 | c.Check(err, ErrorMatches, "unable to request registration: .*") |
4193 | @@ -238,7 +249,8 @@ |
4194 | RegURL: helpers.ParseURL(ts.URL), |
4195 | AuthGetter: func(string) string { return "tok" }, |
4196 | } |
4197 | - svc := NewPushService(ss.bus, setup, ss.log) |
4198 | + svc := NewPushService(setup, ss.log) |
4199 | + svc.Bus = ss.bus |
4200 | reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
4201 | c.Check(err, Equals, ErrBadRequest) |
4202 | c.Check(reg, IsNil) |
4203 | @@ -254,7 +266,8 @@ |
4204 | RegURL: helpers.ParseURL(ts.URL), |
4205 | AuthGetter: func(string) string { return "tok" }, |
4206 | } |
4207 | - svc := NewPushService(ss.bus, setup, ss.log) |
4208 | + svc := NewPushService(setup, ss.log) |
4209 | + svc.Bus = ss.bus |
4210 | reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
4211 | c.Check(err, Equals, ErrBadServer) |
4212 | c.Check(reg, IsNil) |
4213 | @@ -278,7 +291,8 @@ |
4214 | RegURL: helpers.ParseURL(ts.URL), |
4215 | AuthGetter: func(string) string { return "tok" }, |
4216 | } |
4217 | - svc := NewPushService(ss.bus, setup, ss.log) |
4218 | + svc := NewPushService(setup, ss.log) |
4219 | + svc.Bus = ss.bus |
4220 | // this'll check (un)quoting, too |
4221 | reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
4222 | c.Check(reg, IsNil) |
4223 | @@ -303,7 +317,8 @@ |
4224 | RegURL: helpers.ParseURL(ts.URL), |
4225 | AuthGetter: func(string) string { return "tok" }, |
4226 | } |
4227 | - svc := NewPushService(ss.bus, setup, ss.log) |
4228 | + svc := NewPushService(setup, ss.log) |
4229 | + svc.Bus = ss.bus |
4230 | // this'll check (un)quoting, too |
4231 | reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil) |
4232 | c.Check(reg, IsNil) |
4233 | @@ -328,7 +343,8 @@ |
4234 | RegURL: helpers.ParseURL(ts.URL), |
4235 | AuthGetter: func(string) string { return "tok" }, |
4236 | } |
4237 | - svc := NewPushService(ss.bus, setup, ss.log) |
4238 | + svc := NewPushService(setup, ss.log) |
4239 | + svc.Bus = ss.bus |
4240 | // this'll check (un)quoting, too |
4241 | reg, err := svc.unregister(aPackageOnBus, []interface{}{anAppId}, nil) |
4242 | c.Assert(err, IsNil) |
4243 | @@ -355,7 +371,8 @@ |
4244 | RegURL: helpers.ParseURL(ts.URL), |
4245 | AuthGetter: func(string) string { return "tok" }, |
4246 | } |
4247 | - svc := NewPushService(ss.bus, setup, ss.log) |
4248 | + svc := NewPushService(setup, ss.log) |
4249 | + svc.Bus = ss.bus |
4250 | err := svc.Unregister(anAppId) |
4251 | c.Assert(err, IsNil) |
4252 | c.Check(invoked, HasLen, 1) |
4253 | |
4254 | === modified file 'client/session/session.go' |
4255 | --- client/session/session.go 2014-07-03 19:50:38 +0000 |
4256 | +++ client/session/session.go 2014-07-21 14:14:04 +0000 |
4257 | @@ -31,6 +31,7 @@ |
4258 | "sync/atomic" |
4259 | "time" |
4260 | |
4261 | + "launchpad.net/ubuntu-push/click" |
4262 | "launchpad.net/ubuntu-push/client/gethosts" |
4263 | "launchpad.net/ubuntu-push/client/session/seenstate" |
4264 | "launchpad.net/ubuntu-push/logger" |
4265 | @@ -81,7 +82,14 @@ |
4266 | // AddresseeChecking can check if a notification can be delivered. |
4267 | type AddresseeChecking interface { |
4268 | StartAddresseeBatch() |
4269 | - CheckForAddressee(*protocol.Notification) bool |
4270 | + CheckForAddressee(*protocol.Notification) *click.AppId |
4271 | +} |
4272 | + |
4273 | +// AddressedNotification carries both a protocol.Notification and a parsed |
4274 | +// AppId addressee. |
4275 | +type AddressedNotification struct { |
4276 | + To *click.AppId |
4277 | + Notification *protocol.Notification |
4278 | } |
4279 | |
4280 | // ClientSessionConfig groups the client session configuration. |
4281 | @@ -126,7 +134,7 @@ |
4282 | stateP *uint32 |
4283 | ErrCh chan error |
4284 | BroadcastCh chan *BroadcastNotification |
4285 | - NotificationsCh chan *protocol.Notification |
4286 | + NotificationsCh chan AddressedNotification |
4287 | // authorization |
4288 | auth string |
4289 | // autoredial knobs |
4290 | @@ -436,13 +444,14 @@ |
4291 | sess.AddresseeChecker.StartAddresseeBatch() |
4292 | for i := range notifs { |
4293 | notif := ¬ifs[i] |
4294 | - if !sess.AddresseeChecker.CheckForAddressee(notif) { |
4295 | + to := sess.AddresseeChecker.CheckForAddressee(notif) |
4296 | + if to == nil { |
4297 | continue |
4298 | } |
4299 | sess.Log.Debugf("unicast app:%v msg:%s payload:%s", |
4300 | notif.AppId, notif.MsgId, notif.Payload) |
4301 | sess.Log.Debugf("sending ucast over") |
4302 | - sess.NotificationsCh <- notif |
4303 | + sess.NotificationsCh <- AddressedNotification{to, notif} |
4304 | sess.Log.Debugf("sent ucast over") |
4305 | } |
4306 | return nil |
4307 | @@ -572,7 +581,7 @@ |
4308 | if err == nil { |
4309 | sess.ErrCh = make(chan error, 1) |
4310 | sess.BroadcastCh = make(chan *BroadcastNotification) |
4311 | - sess.NotificationsCh = make(chan *protocol.Notification) |
4312 | + sess.NotificationsCh = make(chan AddressedNotification) |
4313 | go func() { sess.ErrCh <- looper() }() |
4314 | } |
4315 | } |
4316 | |
4317 | === modified file 'client/session/session_test.go' |
4318 | --- client/session/session_test.go 2014-07-03 19:50:38 +0000 |
4319 | +++ client/session/session_test.go 2014-07-21 14:14:04 +0000 |
4320 | @@ -32,6 +32,7 @@ |
4321 | |
4322 | . "launchpad.net/gocheck" |
4323 | |
4324 | + "launchpad.net/ubuntu-push/click" |
4325 | "launchpad.net/ubuntu-push/client/gethosts" |
4326 | "launchpad.net/ubuntu-push/client/session/seenstate" |
4327 | "launchpad.net/ubuntu-push/protocol" |
4328 | @@ -636,7 +637,7 @@ |
4329 | s.sess.proto = &testProtocol{up: s.upCh, down: s.downCh} |
4330 | // make the message channel buffered |
4331 | s.sess.BroadcastCh = make(chan *BroadcastNotification, 5) |
4332 | - s.sess.NotificationsCh = make(chan *protocol.Notification, 5) |
4333 | + s.sess.NotificationsCh = make(chan AddressedNotification, 5) |
4334 | } |
4335 | |
4336 | func (s *msgSuite) TestHandlePingWorks(c *C) { |
4337 | @@ -807,9 +808,17 @@ |
4338 | ac.ops <- "start" |
4339 | } |
4340 | |
4341 | -func (ac *testAddresseeChecking) CheckForAddressee(notif *protocol.Notification) bool { |
4342 | +func (ac *testAddresseeChecking) CheckForAddressee(notif *protocol.Notification) *click.AppId { |
4343 | ac.ops <- notif.AppId |
4344 | - return notif.AppId != ac.missing |
4345 | + if notif.AppId != ac.missing { |
4346 | + id, err := click.ParseAppId(notif.AppId) |
4347 | + if err != nil { |
4348 | + panic(err) |
4349 | + } |
4350 | + return id |
4351 | + } else { |
4352 | + return nil |
4353 | + } |
4354 | } |
4355 | |
4356 | func (s *msgSuite) TestHandleNotificationsWorks(c *C) { |
4357 | @@ -817,12 +826,12 @@ |
4358 | s.sess.AddresseeChecker = ac |
4359 | s.sess.setShouldDelay() |
4360 | n1 := protocol.Notification{ |
4361 | - AppId: "app1", |
4362 | + AppId: "com.example.app1_app1", |
4363 | MsgId: "a", |
4364 | Payload: json.RawMessage(`{"m": 1}`), |
4365 | } |
4366 | n2 := protocol.Notification{ |
4367 | - AppId: "app2", |
4368 | + AppId: "com.example.app2_app2", |
4369 | MsgId: "b", |
4370 | Payload: json.RawMessage(`{"m": 2}`), |
4371 | } |
4372 | @@ -837,28 +846,38 @@ |
4373 | c.Check(<-s.errCh, Equals, nil) |
4374 | c.Check(s.sess.ShouldDelay(), Equals, false) |
4375 | c.Assert(s.sess.NotificationsCh, HasLen, 2) |
4376 | - c.Check(<-s.sess.NotificationsCh, DeepEquals, &n1) |
4377 | - c.Check(<-s.sess.NotificationsCh, DeepEquals, &n2) |
4378 | + app1, err := click.ParseAppId("com.example.app1_app1") |
4379 | + c.Assert(err, IsNil) |
4380 | + c.Check(<-s.sess.NotificationsCh, DeepEquals, AddressedNotification{ |
4381 | + To: app1, |
4382 | + Notification: &n1, |
4383 | + }) |
4384 | + app2, err := click.ParseAppId("com.example.app2_app2") |
4385 | + c.Assert(err, IsNil) |
4386 | + c.Check(<-s.sess.NotificationsCh, DeepEquals, AddressedNotification{ |
4387 | + To: app2, |
4388 | + Notification: &n2, |
4389 | + }) |
4390 | c.Check(ac.ops, HasLen, 3) |
4391 | c.Check(<-ac.ops, Equals, "start") |
4392 | - c.Check(<-ac.ops, Equals, "app1") |
4393 | - c.Check(<-ac.ops, Equals, "app2") |
4394 | + c.Check(<-ac.ops, Equals, "com.example.app1_app1") |
4395 | + c.Check(<-ac.ops, Equals, "com.example.app2_app2") |
4396 | } |
4397 | |
4398 | func (s *msgSuite) TestHandleNotificationsAddresseeCheck(c *C) { |
4399 | ac := &testAddresseeChecking{ |
4400 | ops: make(chan string, 10), |
4401 | - missing: "app1", |
4402 | + missing: "com.example.app1_app1", |
4403 | } |
4404 | s.sess.AddresseeChecker = ac |
4405 | s.sess.setShouldDelay() |
4406 | n1 := protocol.Notification{ |
4407 | - AppId: "app1", |
4408 | + AppId: "com.example.app1_app1", |
4409 | MsgId: "a", |
4410 | Payload: json.RawMessage(`{"m": 1}`), |
4411 | } |
4412 | n2 := protocol.Notification{ |
4413 | - AppId: "app2", |
4414 | + AppId: "com.example.app2_app2", |
4415 | MsgId: "b", |
4416 | Payload: json.RawMessage(`{"m": 2}`), |
4417 | } |
4418 | @@ -873,22 +892,27 @@ |
4419 | c.Check(<-s.errCh, Equals, nil) |
4420 | c.Check(s.sess.ShouldDelay(), Equals, false) |
4421 | c.Assert(s.sess.NotificationsCh, HasLen, 1) |
4422 | - c.Check(<-s.sess.NotificationsCh, DeepEquals, &n2) |
4423 | + app2, err := click.ParseAppId("com.example.app2_app2") |
4424 | + c.Assert(err, IsNil) |
4425 | + c.Check(<-s.sess.NotificationsCh, DeepEquals, AddressedNotification{ |
4426 | + To: app2, |
4427 | + Notification: &n2, |
4428 | + }) |
4429 | c.Check(ac.ops, HasLen, 3) |
4430 | c.Check(<-ac.ops, Equals, "start") |
4431 | - c.Check(<-ac.ops, Equals, "app1") |
4432 | + c.Check(<-ac.ops, Equals, "com.example.app1_app1") |
4433 | } |
4434 | |
4435 | func (s *msgSuite) TestHandleNotificationsFiltersSeen(c *C) { |
4436 | ac := &testAddresseeChecking{ops: make(chan string, 10)} |
4437 | s.sess.AddresseeChecker = ac |
4438 | n1 := protocol.Notification{ |
4439 | - AppId: "app1", |
4440 | + AppId: "com.example.app1_app1", |
4441 | MsgId: "a", |
4442 | Payload: json.RawMessage(`{"m": 1}`), |
4443 | } |
4444 | n2 := protocol.Notification{ |
4445 | - AppId: "app2", |
4446 | + AppId: "com.example.app2_app2", |
4447 | MsgId: "b", |
4448 | Payload: json.RawMessage(`{"m": 2}`), |
4449 | } |
4450 | @@ -902,8 +926,18 @@ |
4451 | s.upCh <- nil // ack ok |
4452 | c.Check(<-s.errCh, Equals, nil) |
4453 | c.Assert(s.sess.NotificationsCh, HasLen, 2) |
4454 | - c.Check(<-s.sess.NotificationsCh, DeepEquals, &n1) |
4455 | - c.Check(<-s.sess.NotificationsCh, DeepEquals, &n2) |
4456 | + app1, err := click.ParseAppId("com.example.app1_app1") |
4457 | + c.Assert(err, IsNil) |
4458 | + c.Check(<-s.sess.NotificationsCh, DeepEquals, AddressedNotification{ |
4459 | + To: app1, |
4460 | + Notification: &n1, |
4461 | + }) |
4462 | + app2, err := click.ParseAppId("com.example.app2_app2") |
4463 | + c.Assert(err, IsNil) |
4464 | + c.Check(<-s.sess.NotificationsCh, DeepEquals, AddressedNotification{ |
4465 | + To: app2, |
4466 | + Notification: &n2, |
4467 | + }) |
4468 | c.Check(ac.ops, HasLen, 3) |
4469 | |
4470 | // second time they get ignored |
4471 | @@ -918,7 +952,7 @@ |
4472 | func (s *msgSuite) TestHandleNotificationsBadAckWrite(c *C) { |
4473 | s.sess.setShouldDelay() |
4474 | n1 := protocol.Notification{ |
4475 | - AppId: "app1", |
4476 | + AppId: "com.example.app1_app1", |
4477 | MsgId: "a", |
4478 | Payload: json.RawMessage(`{"m": 1}`), |
4479 | } |
4480 | @@ -941,7 +975,7 @@ |
4481 | s.sess.setShouldDelay() |
4482 | s.sess.SeenState = &brokenSeenState{} |
4483 | n1 := protocol.Notification{ |
4484 | - AppId: "app1", |
4485 | + AppId: "com.example.app1_app1", |
4486 | MsgId: "a", |
4487 | Payload: json.RawMessage(`{"m": 1}`), |
4488 | } |
4489 | |
4490 | === modified file 'debian/changelog' |
4491 | --- debian/changelog 2014-07-07 14:26:27 +0000 |
4492 | +++ debian/changelog 2014-07-21 14:14:04 +0000 |
4493 | @@ -1,3 +1,55 @@ |
4494 | +ubuntu-push (0.49-0ubuntu1) UNRELEASED; urgency=medium |
4495 | + |
4496 | + [Samuele Pedroni] |
4497 | + * Check in the api whether an app has pushed too many notifications. |
4498 | + * Return payload of most recent notification in too many pending |
4499 | + notifications API error. |
4500 | + * Introduce clear_pending flag to clean everything pending for an app. |
4501 | + * Refactor and cleanup. |
4502 | + * Introduce replace_tag support in store and api, with acceptance test. |
4503 | + * Teach a couple of trick to cmd/acceptanceclient: exit on run timeout, |
4504 | + wait for event matching given regexp pattern. |
4505 | + * Limit unicast data payload to 2K. |
4506 | + * Payload should be json (fixes message needing to be base64-encoded in |
4507 | + helper reply) |
4508 | + * Implement limited mboxes |
4509 | + * Refactor and cleanup of things done in haste by Chipaca. |
4510 | + |
4511 | + [Richard Huddie] |
4512 | + * autopilot test framework and basic coverage of broadcast notifications. |
4513 | + |
4514 | + [Guillermo Gonzalez] |
4515 | + * Add scripts to simplify setup/run of the autopilot tests in the |
4516 | + device/emulator and include basic unicast tests. |
4517 | + * Add autopilot test for notification using the emblem counter. |
4518 | + * Adds scenarios to the autopilot tests for legacy and click (without |
4519 | + version) applications. |
4520 | + * Broadcast via the helpers route. |
4521 | + * Basic support for actions (only default action) in the persistent |
4522 | + notifications. |
4523 | + * Change PostBroadcast to send the broadcast message to the software |
4524 | + updates helper. |
4525 | + |
4526 | + [John R. Lenton] |
4527 | + * Detangle client and postal. |
4528 | + * Introduce PostalService interface, and change the client tests to use |
4529 | + that as much as reasonable. |
4530 | + * Async invocation of helpers. |
4531 | + * Give click.Click knowledge of helpers. |
4532 | + * Write ual-based helper launcher. |
4533 | + * Switch to the ual-based helper launcher unless the environment |
4534 | + variable UBUNTU_PUSH_USE_TRIVIAL_HELPER is set. |
4535 | + * Threw together an implementation of helpers for legacy applications. |
4536 | + * Hacked up an initial software updates helper, to be handed off to the |
4537 | + appropriate team shortly. |
4538 | + |
4539 | + [Roberto Alsina] |
4540 | + * Wrap the (dbus) WindowStack API and add endpoint to the Postal service |
4541 | + to support inhibition of notifications for focused apps. |
4542 | + * Inhibit notifications for focused apps |
4543 | + |
4544 | + -- John R. Lenton <john.lenton@canonical.com> Thu, 17 Jul 2014 13:26:48 +0100 |
4545 | + |
4546 | ubuntu-push (0.43+14.10.20140707-0ubuntu1) utopic; urgency=medium |
4547 | |
4548 | [ Samuele Pedroni ] |
4549 | |
4550 | === modified file 'debian/control' |
4551 | --- debian/control 2014-07-04 09:17:49 +0000 |
4552 | +++ debian/control 2014-07-21 14:14:04 +0000 |
4553 | @@ -22,6 +22,7 @@ |
4554 | libnih-dbus-dev, |
4555 | libclick-0.4-dev, |
4556 | cmake, |
4557 | + python3.4, |
4558 | Standards-Version: 3.9.5 |
4559 | Homepage: http://launchpad.net/ubuntu-push |
4560 | Vcs-Bzr: lp:ubuntu-push |
4561 | @@ -49,3 +50,23 @@ |
4562 | The Ubuntu Push Notifications client-side daemon library. |
4563 | . |
4564 | This package contains the source. |
4565 | + |
4566 | +Package: ubuntu-push-dev-server |
4567 | +Architecture: any |
4568 | +Multi-Arch: foreign |
4569 | +Depends: ${misc:Depends}, |
4570 | + ${shlibs:Depends}, |
4571 | +Built-Using: ${misc:Built-Using} |
4572 | +Description: Ubuntu Push Notifications development & testing server |
4573 | + The Ubuntu Push Notifications dev & testing server. |
4574 | + |
4575 | +Package: ubuntu-push-autopilot |
4576 | +Architecture: any |
4577 | +Multi-Arch: foreign |
4578 | +Depends: unity8-autopilot, |
4579 | + unity-scope-click, |
4580 | + ubuntu-push-client, |
4581 | +Recommends: ubuntu-push-dev-server, |
4582 | +Built-Using: ${misc:Built-Using} |
4583 | +Description: Ubuntu Push Notifications autopilot tests |
4584 | + The Ubuntu Push Notifications autopilot tests. |
4585 | |
4586 | === added file 'debian/exec-tool' |
4587 | --- debian/exec-tool 1970-01-01 00:00:00 +0000 |
4588 | +++ debian/exec-tool 2014-07-21 14:14:04 +0000 |
4589 | @@ -0,0 +1,2 @@ |
4590 | +#!/bin/sh |
4591 | +initctl set-env "APP_EXEC=$APP_URIS" |
4592 | |
4593 | === added file 'debian/push-helper.hook' |
4594 | --- debian/push-helper.hook 1970-01-01 00:00:00 +0000 |
4595 | +++ debian/push-helper.hook 2014-07-21 14:14:04 +0000 |
4596 | @@ -0,0 +1,3 @@ |
4597 | +Pattern: ${home}/.local/share/push-helpers/${short-id}.json |
4598 | +User-Level: yes |
4599 | +Hook-Name: push-helper |
4600 | |
4601 | === modified file 'debian/rules' |
4602 | --- debian/rules 2014-06-23 12:46:28 +0000 |
4603 | +++ debian/rules 2014-07-21 14:14:04 +0000 |
4604 | @@ -12,10 +12,10 @@ |
4605 | # (should go away once we ship go 1.3) |
4606 | override_dh_auto_test: |
4607 | cd $$( find ./ -type d -regex '\./[^/]*/src/launchpad.net' -printf "%h\n" | head -n1) && \ |
4608 | - env GOPATH=$$(cd ..; pwd) go test $$(env GOPATH=$$(cd ..; pwd) go list $(DH_GOPKG)/... | grep -v acceptance | grep -v http13client ) |
4609 | + env GOPATH=$$(cd ..; pwd) go test -v $$(env GOPATH=$$(cd ..; pwd) go list $(DH_GOPKG)/... | grep -v acceptance | grep -v http13client ) |
4610 | |
4611 | override_dh_install: |
4612 | - dh_install -Xusr/bin/cmd -Xusr/bin/dev --fail-missing |
4613 | + dh_install -Xusr/bin/cmd --fail-missing |
4614 | |
4615 | %: |
4616 | dh $@ --buildsystem=golang --with=golang |
4617 | |
4618 | === added file 'debian/ubuntu-push-autopilot.install' |
4619 | --- debian/ubuntu-push-autopilot.install 1970-01-01 00:00:00 +0000 |
4620 | +++ debian/ubuntu-push-autopilot.install 2014-07-21 14:14:04 +0000 |
4621 | @@ -0,0 +1,1 @@ |
4622 | +tests/autopilot/push_notifications/ /usr/lib/python3/dist-packages |
4623 | |
4624 | === modified file 'debian/ubuntu-push-client.install' |
4625 | --- debian/ubuntu-push-client.install 2014-04-30 18:10:31 +0000 |
4626 | +++ debian/ubuntu-push-client.install 2014-07-21 14:14:04 +0000 |
4627 | @@ -1,5 +1,8 @@ |
4628 | #!/usr/bin/dh-exec |
4629 | debian/config.json /etc/xdg/ubuntu-push-client |
4630 | debian/ubuntu-push-client.conf /usr/share/upstart/sessions |
4631 | +debian/exec-tool /usr/lib/${DEB_HOST_MULTIARCH}/ubuntu-app-launch/push-helper |
4632 | +debian/push-helper.hook /usr/share/click/hooks |
4633 | signing-helper/signing-helper /usr/lib/ubuntu-push-client |
4634 | usr/bin/ubuntu-push => /usr/lib/ubuntu-push-client/ubuntu-push-client |
4635 | +scripts/software-updates-helper.py => /usr/lib/ubuntu-push-client/legacy-helpers/ubuntu-system-settings |
4636 | |
4637 | === added file 'debian/ubuntu-push-dev-server.install' |
4638 | --- debian/ubuntu-push-dev-server.install 1970-01-01 00:00:00 +0000 |
4639 | +++ debian/ubuntu-push-dev-server.install 2014-07-21 14:14:04 +0000 |
4640 | @@ -0,0 +1,2 @@ |
4641 | +#!/usr/bin/dh-exec |
4642 | +usr/bin/dev => /usr/bin/ubuntu-push-dev-server |
4643 | |
4644 | === added directory 'launch_helper/cual' |
4645 | === added file 'launch_helper/cual/cual.go' |
4646 | --- launch_helper/cual/cual.go 1970-01-01 00:00:00 +0000 |
4647 | +++ launch_helper/cual/cual.go 2014-07-21 14:14:04 +0000 |
4648 | @@ -0,0 +1,97 @@ |
4649 | +/* |
4650 | + Copyright 2013-2014 Canonical Ltd. |
4651 | + |
4652 | + This program is free software: you can redistribute it and/or modify it |
4653 | + under the terms of the GNU General Public License version 3, as published |
4654 | + by the Free Software Foundation. |
4655 | + |
4656 | + This program is distributed in the hope that it will be useful, but |
4657 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
4658 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
4659 | + PURPOSE. See the GNU General Public License for more details. |
4660 | + |
4661 | + You should have received a copy of the GNU General Public License along |
4662 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
4663 | +*/ |
4664 | + |
4665 | +package cual |
4666 | + |
4667 | +/* |
4668 | +#cgo pkg-config: ubuntu-app-launch-2 |
4669 | +#include <glib.h> |
4670 | + |
4671 | +gboolean add_observer (gpointer); |
4672 | +gboolean remove_observer (gpointer); |
4673 | +char* launch(gchar* app_id, gchar* exec, gchar* f1, gchar* f2, gpointer p); |
4674 | +gboolean stop(gchar* app_id, gchar* iid); |
4675 | +*/ |
4676 | +import "C" |
4677 | +import ( |
4678 | + "errors" |
4679 | + "unsafe" |
4680 | + |
4681 | + "launchpad.net/ubuntu-push/click" |
4682 | + "launchpad.net/ubuntu-push/logger" |
4683 | +) |
4684 | + |
4685 | +func gstring(s string) *C.gchar { |
4686 | + return (*C.gchar)(C.CString(s)) |
4687 | +} |
4688 | + |
4689 | +type helperState struct { |
4690 | + log logger.Logger |
4691 | + done func(string) |
4692 | +} |
4693 | + |
4694 | +//export helperDone |
4695 | +func helperDone(gp unsafe.Pointer, ciid *C.char) { |
4696 | + hs := (*helperState)(gp) |
4697 | + iid := C.GoString(ciid) |
4698 | + hs.done(iid) |
4699 | +} |
4700 | + |
4701 | +var ( |
4702 | + ErrCantObserve = errors.New("can't add observer") |
4703 | + ErrCantUnobserve = errors.New("can't remove observer") |
4704 | + ErrCantLaunch = errors.New("can't launch helper") |
4705 | + ErrCantStop = errors.New("can't stop helper") |
4706 | +) |
4707 | + |
4708 | +func New(log logger.Logger) *helperState { |
4709 | + return &helperState{log: log} |
4710 | +} |
4711 | + |
4712 | +func (hs *helperState) InstallObserver(done func(string)) error { |
4713 | + hs.done = done |
4714 | + if C.add_observer(C.gpointer(hs)) != C.TRUE { |
4715 | + return ErrCantObserve |
4716 | + } |
4717 | + return nil |
4718 | +} |
4719 | + |
4720 | +func (hs *helperState) RemoveObserver() error { |
4721 | + if C.remove_observer(C.gpointer(hs)) != C.TRUE { |
4722 | + return ErrCantUnobserve |
4723 | + } |
4724 | + return nil |
4725 | +} |
4726 | + |
4727 | +func (hs *helperState) HelperInfo(app *click.AppId) (string, string) { |
4728 | + return app.Helper() |
4729 | +} |
4730 | + |
4731 | +func (hs *helperState) Launch(appId, exec, f1, f2 string) (string, error) { |
4732 | + // launch(...) takes over ownership of things passed in |
4733 | + iid := C.GoString(C.launch(gstring(appId), gstring(exec), gstring(f1), gstring(f2), C.gpointer(hs))) |
4734 | + if iid == "" { |
4735 | + return "", ErrCantLaunch |
4736 | + } |
4737 | + return iid, nil |
4738 | +} |
4739 | + |
4740 | +func (hs *helperState) Stop(appId, instanceId string) error { |
4741 | + if C.stop(gstring(appId), gstring(instanceId)) != C.TRUE { |
4742 | + return ErrCantStop |
4743 | + } |
4744 | + return nil |
4745 | +} |
4746 | |
4747 | === added file 'launch_helper/cual/cual_c.go' |
4748 | --- launch_helper/cual/cual_c.go 1970-01-01 00:00:00 +0000 |
4749 | +++ launch_helper/cual/cual_c.go 2014-07-21 14:14:04 +0000 |
4750 | @@ -0,0 +1,57 @@ |
4751 | +/* |
4752 | + Copyright 2013-2014 Canonical Ltd. |
4753 | + |
4754 | + This program is free software: you can redistribute it and/or modify it |
4755 | + under the terms of the GNU General Public License version 3, as published |
4756 | + by the Free Software Foundation. |
4757 | + |
4758 | + This program is distributed in the hope that it will be useful, but |
4759 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
4760 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
4761 | + PURPOSE. See the GNU General Public License for more details. |
4762 | + |
4763 | + You should have received a copy of the GNU General Public License along |
4764 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
4765 | +*/ |
4766 | + |
4767 | +package cual |
4768 | + |
4769 | +// this is a .go to work around limitations in dh-golang |
4770 | + |
4771 | +/* |
4772 | +#include <ubuntu-app-launch.h> |
4773 | +#include <glib.h> |
4774 | + |
4775 | +#define HELPER_ERROR g_quark_from_static_string ("cgo-ual-helper-error-quark") |
4776 | + |
4777 | +static void observer_of_stop (const gchar * app_id, const gchar * instance_id, const gchar * helper_type, gpointer user_data) { |
4778 | + helperDone (user_data, instance_id); |
4779 | +} |
4780 | + |
4781 | +char* launch(gchar* app_id, gchar* exec, gchar* f1, gchar* f2, gpointer p) { |
4782 | + const gchar* uris[4] = {exec, f1, f2, NULL}; |
4783 | + gchar* iid = ubuntu_app_launch_start_multiple_helper ("push-helper", app_id, uris); |
4784 | + g_free (app_id); |
4785 | + g_free (exec); |
4786 | + g_free (f1); |
4787 | + g_free (f2); |
4788 | + return iid; |
4789 | +} |
4790 | + |
4791 | +gboolean add_observer(gpointer p) { |
4792 | + return ubuntu_app_launch_observer_add_helper_stop(observer_of_stop, "push-helper", p); |
4793 | +} |
4794 | + |
4795 | +gboolean remove_observer(gpointer p) { |
4796 | + return ubuntu_app_launch_observer_delete_helper_stop(observer_of_stop, "push-helper", p); |
4797 | +} |
4798 | + |
4799 | +gboolean stop(gchar* app_id, gchar* iid) { |
4800 | + gboolean res; |
4801 | + res = ubuntu_app_launch_stop_multiple_helper ("push-helper", app_id, iid); |
4802 | + g_free (app_id); |
4803 | + g_free (iid); |
4804 | + return res; |
4805 | +} |
4806 | +*/ |
4807 | +import "C" |
4808 | |
4809 | === modified file 'launch_helper/helper.go' |
4810 | --- launch_helper/helper.go 2014-06-30 17:00:54 +0000 |
4811 | +++ launch_helper/helper.go 2014-07-21 14:14:04 +0000 |
4812 | @@ -24,28 +24,41 @@ |
4813 | "launchpad.net/ubuntu-push/logger" |
4814 | ) |
4815 | |
4816 | -type HelperLauncher interface { |
4817 | - Run(appId string, message []byte) *HelperOutput |
4818 | -} |
4819 | - |
4820 | type trivialHelperLauncher struct { |
4821 | - log logger.Logger |
4822 | -} |
4823 | - |
4824 | -// a trivial HelperLauncher that doesn't launch anything at all |
4825 | -func NewTrivialHelperLauncher(log logger.Logger) HelperLauncher { |
4826 | - return &trivialHelperLauncher{log} |
4827 | -} |
4828 | - |
4829 | -func (triv *trivialHelperLauncher) Run(appId string, message []byte) *HelperOutput { |
4830 | - out := new(HelperOutput) |
4831 | - err := json.Unmarshal(message, out) |
4832 | - if err == nil { |
4833 | - return out |
4834 | - } |
4835 | - triv.log.Debugf("failed to parse HelperOutput from message, leaving it alone: %v", err) |
4836 | - out.Message = message |
4837 | - out.Notification = nil |
4838 | - |
4839 | - return out |
4840 | + log logger.Logger |
4841 | + chOut chan *HelperResult |
4842 | + chIn chan *HelperInput |
4843 | +} |
4844 | + |
4845 | +// a trivial HelperPool that doesn't launch anything at all |
4846 | +func NewTrivialHelperPool(log logger.Logger) HelperPool { |
4847 | + return &trivialHelperLauncher{log: log} |
4848 | +} |
4849 | + |
4850 | +func (triv *trivialHelperLauncher) Start() chan *HelperResult { |
4851 | + triv.chOut = make(chan *HelperResult) |
4852 | + triv.chIn = make(chan *HelperInput, InputBufferSize) |
4853 | + |
4854 | + go func() { |
4855 | + for i := range triv.chIn { |
4856 | + res := &HelperResult{Input: i} |
4857 | + err := json.Unmarshal(i.Payload, &res.HelperOutput) |
4858 | + if err != nil { |
4859 | + triv.log.Debugf("failed to parse HelperOutput from message, leaving it alone: %v", err) |
4860 | + res.Message = i.Payload |
4861 | + res.Notification = nil |
4862 | + } |
4863 | + triv.chOut <- res |
4864 | + } |
4865 | + }() |
4866 | + |
4867 | + return triv.chOut |
4868 | +} |
4869 | + |
4870 | +func (triv *trivialHelperLauncher) Stop() { |
4871 | + close(triv.chIn) |
4872 | +} |
4873 | + |
4874 | +func (triv *trivialHelperLauncher) Run(kind string, input *HelperInput) { |
4875 | + triv.chIn <- input |
4876 | } |
4877 | |
4878 | === modified file 'launch_helper/helper_output.go' |
4879 | --- launch_helper/helper_output.go 2014-07-06 13:53:42 +0000 |
4880 | +++ launch_helper/helper_output.go 2014-07-21 14:14:04 +0000 |
4881 | @@ -16,6 +16,12 @@ |
4882 | |
4883 | package launch_helper |
4884 | |
4885 | +import ( |
4886 | + "encoding/json" |
4887 | + |
4888 | + "launchpad.net/ubuntu-push/click" |
4889 | +) |
4890 | + |
4891 | // a Card is the usual “visual” presentation of a notification, used |
4892 | // for bubbles and the notification centre (neé messaging menu) |
4893 | type Card struct { |
4894 | @@ -52,6 +58,20 @@ |
4895 | |
4896 | // HelperOutput is the expected output of a helper |
4897 | type HelperOutput struct { |
4898 | - Message []byte `json:"message"` // what to put in the post office's queue |
4899 | - Notification *Notification `json:"notification"` // what to present to the user |
4900 | + Message json.RawMessage `json:"message,omitempty"` // what to put in the post office's queue |
4901 | + Notification *Notification `json:"notification,omitempty"` // what to present to the user |
4902 | +} |
4903 | + |
4904 | +// HelperResult is the result of a helper run for a particular app id |
4905 | +type HelperResult struct { |
4906 | + HelperOutput |
4907 | + Input *HelperInput |
4908 | +} |
4909 | + |
4910 | +// HelperInput is what's passed in to a helper for it to work |
4911 | +type HelperInput struct { |
4912 | + kind string |
4913 | + App *click.AppId |
4914 | + NotificationId string |
4915 | + Payload json.RawMessage |
4916 | } |
4917 | |
4918 | === modified file 'launch_helper/helper_test.go' |
4919 | --- launch_helper/helper_test.go 2014-06-30 17:00:54 +0000 |
4920 | +++ launch_helper/helper_test.go 2014-07-21 14:14:04 +0000 |
4921 | @@ -17,10 +17,14 @@ |
4922 | package launch_helper |
4923 | |
4924 | import ( |
4925 | + "encoding/json" |
4926 | "testing" |
4927 | + "time" |
4928 | |
4929 | . "launchpad.net/gocheck" |
4930 | |
4931 | + "launchpad.net/ubuntu-push/click" |
4932 | + clickhelp "launchpad.net/ubuntu-push/click/testing" |
4933 | helpers "launchpad.net/ubuntu-push/testing" |
4934 | ) |
4935 | |
4936 | @@ -28,30 +32,60 @@ |
4937 | |
4938 | type runnerSuite struct { |
4939 | testlog *helpers.TestLogger |
4940 | + app *click.AppId |
4941 | } |
4942 | |
4943 | var _ = Suite(&runnerSuite{}) |
4944 | |
4945 | func (s *runnerSuite) SetUpTest(c *C) { |
4946 | s.testlog = helpers.NewTestLogger(c, "error") |
4947 | + s.app = clickhelp.MustParseAppId("com.example.test_test-app_0") |
4948 | } |
4949 | |
4950 | -func (s *runnerSuite) TestTrivialRunnerWorks(c *C) { |
4951 | +func (s *runnerSuite) TestTrivialPoolWorks(c *C) { |
4952 | notif := &Notification{Sound: "42"} |
4953 | |
4954 | - triv := NewTrivialHelperLauncher(s.testlog) |
4955 | - // []byte is sent as a base64-encoded string |
4956 | - out := triv.Run("foo", []byte(`{"message": "aGVsbG8=", "notification": {"sound": "42"}}`)) |
4957 | + triv := NewTrivialHelperPool(s.testlog) |
4958 | + ch := triv.Start() |
4959 | + in := &HelperInput{App: s.app, Payload: []byte(`{"message": {"m":42}, "notification": {"sound": "42"}}`)} |
4960 | + triv.Run("klick", in) |
4961 | + out := <-ch |
4962 | c.Assert(out, NotNil) |
4963 | - c.Check(out.Message, DeepEquals, []byte("hello")) |
4964 | + c.Check(out.Message, DeepEquals, json.RawMessage(`{"m":42}`)) |
4965 | c.Check(out.Notification, DeepEquals, notif) |
4966 | -} |
4967 | - |
4968 | -func (s *runnerSuite) TestTrivialRunnerWorksOnBadInput(c *C) { |
4969 | - triv := NewTrivialHelperLauncher(s.testlog) |
4970 | + c.Check(out.Input, DeepEquals, in) |
4971 | +} |
4972 | + |
4973 | +func (s *runnerSuite) TestTrivialPoolWorksOnBadInput(c *C) { |
4974 | + triv := NewTrivialHelperPool(s.testlog) |
4975 | + ch := triv.Start() |
4976 | + msg := []byte(`{card: 3}`) |
4977 | + in := &HelperInput{App: s.app, Payload: msg} |
4978 | + triv.Run("klick", in) |
4979 | + out := <-ch |
4980 | + c.Assert(out, NotNil) |
4981 | + c.Check(out.Notification, IsNil) |
4982 | + c.Check(out.Message, DeepEquals, json.RawMessage(msg)) |
4983 | + c.Check(out.Input, DeepEquals, in) |
4984 | +} |
4985 | + |
4986 | +func (s *runnerSuite) TestTrivialPoolDoesNotBlockEasily(c *C) { |
4987 | + triv := NewTrivialHelperPool(s.testlog) |
4988 | + triv.Start() |
4989 | msg := []byte(`this is a not your grandmother's json message`) |
4990 | - out := triv.Run("foo", msg) |
4991 | - c.Assert(out, NotNil) |
4992 | - c.Check(out.Notification, IsNil) |
4993 | - c.Check(out.Message, DeepEquals, msg) |
4994 | + in := &HelperInput{App: s.app, Payload: msg} |
4995 | + flagCh := make(chan bool) |
4996 | + go func() { |
4997 | + // stuff several in there |
4998 | + triv.Run("klick", in) |
4999 | + triv.Run("klick", in) |
5000 | + triv.Run("klick", in) |