Merge lp:~chipaca/ubuntu-push/the-push-automatic into lp:ubuntu-push

Proposed by John Lenton
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
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/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

Description of the change

Merged from automatic.

To post a comment you must log in.
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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", &notif)
66+ ec.Present(ecs.app, "nid", &notif)
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", &notif), Equals, true)
168+ c.Check(ec.Present(hs.app, "nid", &notif), 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", &notif), Equals, true)
177+ c.Check(ec.Present(hs.app, "nid", &notif), 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", &notif), Equals, true)
186+ c.Check(ec.Present(hs.app, "nid", &notif), 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", &notif), Equals, true)
195+ c.Check(ec.Present(hs.app, "nid", &notif), 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 <- &notifications.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 := &notifs[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)
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches