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: 107
Proposed branch: lp:~chipaca/ubuntu-push/the-push-automatic
Merge into: lp:ubuntu-push
Diff against target: 3302 lines (+1773/-357)
35 files modified
PACKAGE_DEPS (+1/-0)
bus/connectivity/connectivity.go (+1/-1)
bus/connectivity/connectivity_test.go (+1/-0)
bus/emblemcounter/emblemcounter.go (+69/-0)
bus/emblemcounter/emblemcounter_test.go (+79/-0)
bus/endpoint.go (+11/-0)
bus/haptic/haptic.go (+70/-0)
bus/haptic/haptic_test.go (+111/-0)
bus/notifications/app_helper/app_helper_c.go (+18/-13)
bus/testing/testing_endpoint.go (+15/-0)
bus/testing/testing_endpoint_test.go (+11/-0)
click/cclick/cclick.go (+76/-0)
click/click.go (+86/-0)
click/click_test.go (+86/-0)
client/client.go (+114/-31)
client/client_test.go (+227/-32)
client/service/common.go (+28/-6)
client/service/common_test.go (+60/-0)
client/service/postal.go (+55/-38)
client/service/postal_test.go (+111/-52)
client/service/service.go (+78/-78)
client/service/service_test.go (+181/-96)
client/session/session.go (+11/-0)
client/session/session_test.go (+61/-3)
debian/changelog (+15/-0)
debian/config.json (+1/-1)
debian/control (+1/-0)
launch_helper/helper_output.go (+2/-2)
messaging/messaging.go (+1/-0)
messaging/messaging_test.go (+4/-4)
server/session/session_test.go (+1/-0)
sounds/sounds.go (+91/-0)
sounds/sounds_test.go (+85/-0)
testing/helpers.go (+10/-0)
whoopsie/identifier/identifier.go (+1/-0)
To merge this branch: bzr merge lp:~chipaca/ubuntu-push/the-push-automatic
Reviewer Review Type Date Requested Status
John Lenton (community) Approve
Review via email: mp+225715@code.launchpad.net

Description of the change

Merging automatic:

[ Samuele Pedroni ]
  * Logic to support unregistering tokens lazily for uninstalled apps
  * Minimal wrapping of libclick to check if a package is installed for a user

[ John Lenton ]
  * Finalize dbus api
  * First version of emblem counters.

To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) :
review: Approve
107. By PS Jenkins bot

* Logic to support unregistering tokens lazily for uninstalled apps
* Minimal wrapping of libclick to check if a package is installed for a user
* Refactor and cleanup of cleanup/service
* Finalized DBus API (hopefully)
* Support emblem counter notifications
* Support haptic (vibration) notifications
* Support sound notifications

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'PACKAGE_DEPS'
--- PACKAGE_DEPS 2014-06-24 14:19:31 +0000
+++ PACKAGE_DEPS 2014-07-07 14:23:18 +0000
@@ -10,3 +10,4 @@
10libwhoopsie-dev10libwhoopsie-dev
11libmessaging-menu-dev11libmessaging-menu-dev
12libubuntu-app-launch2-dev12libubuntu-app-launch2-dev
13libclick-0.4-dev
1314
=== modified file 'bus/connectivity/connectivity.go'
--- bus/connectivity/connectivity.go 2014-04-28 01:56:21 +0000
+++ bus/connectivity/connectivity.go 2014-07-07 14:23:18 +0000
@@ -129,7 +129,7 @@
129 case v, ok := <-cs.networkStateCh:129 case v, ok := <-cs.networkStateCh:
130 if !ok {130 if !ok {
131 // tear it all down and start over131 // tear it all down and start over
132 return false, errors.New("Got not-OK from StateChanged watch")132 return false, errors.New("got not-OK from StateChanged watch")
133 }133 }
134 cs.webgetCh = nil134 cs.webgetCh = nil
135 cs.currentState = v135 cs.currentState = v
136136
=== modified file 'bus/connectivity/connectivity_test.go'
--- bus/connectivity/connectivity_test.go 2014-07-01 11:55:30 +0000
+++ bus/connectivity/connectivity_test.go 2014-07-07 14:23:18 +0000
@@ -167,6 +167,7 @@
167func (*racyEndpoint) GrabName(bool) <-chan error { return nil }167func (*racyEndpoint) GrabName(bool) <-chan error { return nil }
168func (*racyEndpoint) WatchMethod(bus.DispatchMap, string, ...interface{}) {}168func (*racyEndpoint) WatchMethod(bus.DispatchMap, string, ...interface{}) {}
169func (*racyEndpoint) Signal(member string, suffix string, args []interface{}) error { return nil }169func (*racyEndpoint) Signal(member string, suffix string, args []interface{}) error { return nil }
170func (*racyEndpoint) SetProperty(string, string, interface{}) error { return nil }
170171
171var _ bus.Endpoint = (*racyEndpoint)(nil)172var _ bus.Endpoint = (*racyEndpoint)(nil)
172173
173174
=== added directory 'bus/emblemcounter'
=== added file 'bus/emblemcounter/emblemcounter.go'
--- bus/emblemcounter/emblemcounter.go 1970-01-01 00:00:00 +0000
+++ bus/emblemcounter/emblemcounter.go 2014-07-07 14:23:18 +0000
@@ -0,0 +1,69 @@
1/*
2 Copyright 2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17// Package emblemcounter can present notifications as a counter on an
18// emblem on an item in the launcher.
19package emblemcounter
20
21import (
22 "launchpad.net/go-dbus/v1"
23
24 "launchpad.net/ubuntu-push/bus"
25 "launchpad.net/ubuntu-push/click"
26 "launchpad.net/ubuntu-push/launch_helper"
27 "launchpad.net/ubuntu-push/logger"
28 "launchpad.net/ubuntu-push/nih"
29)
30
31// emblemcounter works by setting properties on a well-known dbus name.
32var BusAddress = bus.Address{
33 Interface: "com.canonical.Unity.Launcher.Item",
34 Path: "/com/canonical/Unity/Launcher",
35 Name: "com.canonical.Unity.Launcher",
36}
37
38// EmblemCounter is a little tool that fiddles with the unity launcher
39// to put emblems with counters on launcher icons.
40type EmblemCounter struct {
41 bus bus.Endpoint
42 log logger.Logger
43}
44
45// Build an EmblemCounter using the given bus and log.
46func New(endp bus.Endpoint, log logger.Logger) *EmblemCounter {
47 return &EmblemCounter{bus: endp, log: log}
48}
49
50// Look for an EmblemCounter section in a Notification and, if
51// present, presents it to the user.
52func (ctr *EmblemCounter) Present(appId string, notificationId string, notification *launch_helper.Notification) {
53 if notification == nil || notification.EmblemCounter == nil {
54 ctr.log.Debugf("no notification or no EmblemCounter in the notification; doing nothing: %#v", notification)
55 return
56 }
57 parsed, err := click.ParseAppId(appId)
58 if err != nil {
59 ctr.log.Debugf("no appId in %#v", appId)
60 return
61 }
62 ec := notification.EmblemCounter
63 ctr.log.Debugf("setting emblem counter for %s to %d (visible: %t)", appId, ec.Count, ec.Visible)
64
65 quoted := string(nih.Quote([]byte(parsed.Application)))
66
67 ctr.bus.SetProperty("count", "/"+quoted, dbus.Variant{ec.Count})
68 ctr.bus.SetProperty("countVisible", "/"+quoted, dbus.Variant{ec.Visible})
69}
070
=== added file 'bus/emblemcounter/emblemcounter_test.go'
--- bus/emblemcounter/emblemcounter_test.go 1970-01-01 00:00:00 +0000
+++ bus/emblemcounter/emblemcounter_test.go 2014-07-07 14:23:18 +0000
@@ -0,0 +1,79 @@
1/*
2 Copyright 2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17package emblemcounter
18
19import (
20 "testing"
21
22 "launchpad.net/go-dbus/v1"
23 . "launchpad.net/gocheck"
24
25 testibus "launchpad.net/ubuntu-push/bus/testing"
26 "launchpad.net/ubuntu-push/launch_helper"
27 helpers "launchpad.net/ubuntu-push/testing"
28 "launchpad.net/ubuntu-push/testing/condition"
29)
30
31func TestEmblemCounter(t *testing.T) { TestingT(t) }
32
33type ecSuite struct {
34 log *helpers.TestLogger
35}
36
37var _ = Suite(&ecSuite{})
38
39func (ecs *ecSuite) SetUpTest(c *C) {
40 ecs.log = helpers.NewTestLogger(c, "debug")
41}
42
43// checks that Present() actually calls SetProperty on the launcher
44func (ecs *ecSuite) TestPresentPresents(c *C) {
45 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
46
47 ec := New(endp, ecs.log)
48 notif := launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{Count: 42, Visible: true}}
49 ec.Present("com.example.test_test-app", "nid", &notif)
50 callArgs := testibus.GetCallArgs(endp)
51 c.Assert(callArgs, HasLen, 2)
52 c.Check(callArgs[0].Member, Equals, "::SetProperty")
53 c.Check(callArgs[1].Member, Equals, "::SetProperty")
54 c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", "/test_2dapp", dbus.Variant{Value: int32(42)}})
55 c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", "/test_2dapp", dbus.Variant{Value: true}})
56}
57
58// check that Present() doesn't call SetProperty if no EmblemCounter in the Notification
59func (ecs *ecSuite) TestSkipIfMissing(c *C) {
60 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
61 ec := New(endp, ecs.log)
62
63 // nothing happens if nil Notification
64 ec.Present("com.example.test_test-app", "nid", nil)
65 c.Assert(testibus.GetCallArgs(endp), HasLen, 0)
66
67 // nothing happens if no EmblemCounter in Notification
68 ec.Present("com.example.test_test-app", "nid", &launch_helper.Notification{})
69 c.Assert(testibus.GetCallArgs(endp), HasLen, 0)
70
71 // but an empty EmblemCounter is acted on
72 ec.Present("com.example.test_test-app", "nid", &launch_helper.Notification{EmblemCounter: &launch_helper.EmblemCounter{}})
73 callArgs := testibus.GetCallArgs(endp)
74 c.Assert(callArgs, HasLen, 2)
75 c.Check(callArgs[0].Member, Equals, "::SetProperty")
76 c.Check(callArgs[1].Member, Equals, "::SetProperty")
77 c.Check(callArgs[0].Args, DeepEquals, []interface{}{"count", "/test_2dapp", dbus.Variant{Value: int32(0)}})
78 c.Check(callArgs[1].Args, DeepEquals, []interface{}{"countVisible", "/test_2dapp", dbus.Variant{Value: false}})
79}
080
=== modified file 'bus/endpoint.go'
--- bus/endpoint.go 2014-07-01 11:55:30 +0000
+++ bus/endpoint.go 2014-07-07 14:23:18 +0000
@@ -42,6 +42,7 @@
42 Signal(string, string, []interface{}) error42 Signal(string, string, []interface{}) error
43 Call(member string, args []interface{}, rvs ...interface{}) error43 Call(member string, args []interface{}, rvs ...interface{}) error
44 GetProperty(property string) (interface{}, error)44 GetProperty(property string) (interface{}, error)
45 SetProperty(property string, suffix string, value interface{}) error
45 Dial() error46 Dial() error
46 Close()47 Close()
47 String() string48 String() string
@@ -178,6 +179,16 @@
178 return variant.Value, nil179 return variant.Value, nil
179}180}
180181
182// SetProperty calls org.freedesktop.DBus.Properties's Set method
183//
184// XXX: untested
185func (endp *endpoint) SetProperty(property string, suffix string, value interface{}) error {
186 // can't use the pre-existing ObjectProxy for this one
187 proxy := endp.bus.Object(endp.addr.Name, dbus.ObjectPath(endp.addr.Path+suffix))
188 _, err := proxy.Call("org.freedesktop.DBus.Properties", "Set", endp.addr.Interface, property, value)
189 return err
190}
191
181// Close the connection to dbus.192// Close the connection to dbus.
182//193//
183// XXX: untested194// XXX: untested
184195
=== added directory 'bus/haptic'
=== added file 'bus/haptic/haptic.go'
--- bus/haptic/haptic.go 1970-01-01 00:00:00 +0000
+++ bus/haptic/haptic.go 2014-07-07 14:23:18 +0000
@@ -0,0 +1,70 @@
1/*
2 Copyright 2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17// Package haptic can present notifications as a vibration pattern
18// using the usensord/haptic interface
19package haptic
20
21import (
22 "launchpad.net/ubuntu-push/bus"
23 "launchpad.net/ubuntu-push/launch_helper"
24 "launchpad.net/ubuntu-push/logger"
25)
26
27// usensord/haptic lives on a well-knwon bus.Address
28var BusAddress bus.Address = bus.Address{
29 Interface: "com.canonical.usensord.haptic",
30 Path: "/com/canonical/usensord/haptic",
31 Name: "com.canonical.usensord",
32}
33
34// Haptic encapsulates info needed to call out to usensord/haptic
35type Haptic struct {
36 bus bus.Endpoint
37 log logger.Logger
38}
39
40// New returns a new Haptic that'll use the provided bus.Endpoint
41func New(endp bus.Endpoint, log logger.Logger) *Haptic {
42 return &Haptic{endp, log}
43}
44
45// Present presents the notification via a vibrate pattern
46func (haptic *Haptic) Present(_, _ string, notification *launch_helper.Notification) bool {
47 if notification == nil || notification.Vibrate == nil {
48 haptic.log.Debugf("no notification or no Vibrate in the notification; doing nothing: %#v", notification)
49 return false
50 }
51 pattern := notification.Vibrate.Pattern
52 repeat := notification.Vibrate.Repeat
53 if repeat == 0 {
54 repeat = 1
55 }
56 if notification.Vibrate.Duration != 0 {
57 pattern = []uint32{notification.Vibrate.Duration}
58 }
59 if len(pattern) == 0 {
60 haptic.log.Debugf("not enough information in the notification's Vibrate section to create a pattern")
61 return false
62 }
63 haptic.log.Debugf("vibrating %d times to the tune of %v", repeat, pattern)
64 err := haptic.bus.Call("VibratePattern", bus.Args(pattern, repeat))
65 if err != nil {
66 haptic.log.Debugf("VibratePattern call returned %v", err)
67 return false
68 }
69 return true
70}
071
=== added file 'bus/haptic/haptic_test.go'
--- bus/haptic/haptic_test.go 1970-01-01 00:00:00 +0000
+++ bus/haptic/haptic_test.go 2014-07-07 14:23:18 +0000
@@ -0,0 +1,111 @@
1/*
2 Copyright 2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17package haptic
18
19import (
20 "testing"
21
22 . "launchpad.net/gocheck"
23
24 testibus "launchpad.net/ubuntu-push/bus/testing"
25 "launchpad.net/ubuntu-push/launch_helper"
26 helpers "launchpad.net/ubuntu-push/testing"
27 "launchpad.net/ubuntu-push/testing/condition"
28)
29
30func TestHaptic(t *testing.T) { TestingT(t) }
31
32type hapticSuite struct {
33 log *helpers.TestLogger
34}
35
36var _ = Suite(&hapticSuite{})
37
38func (hs *hapticSuite) SetUpTest(c *C) {
39 hs.log = helpers.NewTestLogger(c, "debug")
40}
41
42// checks that Present() actually calls VibratePattern
43func (hs *hapticSuite) TestPresentPresents(c *C) {
44 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
45
46 ec := New(endp, hs.log)
47 notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2}}
48 c.Check(ec.Present("com.example.test_test-app", "nid", &notif), Equals, true)
49 callArgs := testibus.GetCallArgs(endp)
50 c.Assert(callArgs, HasLen, 1)
51 c.Check(callArgs[0].Member, Equals, "VibratePattern")
52 c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200, 100}, uint32(2)})
53}
54
55// check that Present() defaults Repeat to 1
56func (hs *hapticSuite) TestPresentDefaultsRepeatTo1(c *C) {
57 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
58
59 ec := New(endp, hs.log)
60 // note: no Repeat:
61 notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Pattern: []uint32{200, 100}}}
62 c.Check(ec.Present("com.example.test_test-app", "nid", &notif), Equals, true)
63 callArgs := testibus.GetCallArgs(endp)
64 c.Assert(callArgs, HasLen, 1)
65 c.Check(callArgs[0].Member, Equals, "VibratePattern")
66 // note: Repeat of 1:
67 c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200, 100}, uint32(1)})
68}
69
70// check that Present() makes a Pattern of [Duration] if Duration is given
71func (hs *hapticSuite) TestPresentBuildsPatternWithDuration(c *C) {
72 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
73
74 ec := New(endp, hs.log)
75 // note: no Repeat, no Pattern, just Duration:
76 notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Duration: 200}}
77 c.Check(ec.Present("com.example.test_test-app", "nid", &notif), Equals, true)
78 callArgs := testibus.GetCallArgs(endp)
79 c.Assert(callArgs, HasLen, 1)
80 c.Check(callArgs[0].Member, Equals, "VibratePattern")
81 // note: Pattern of [Duration], Repeat of 1:
82 c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200}, uint32(1)})
83}
84
85// check that Present() ignores Pattern and makes a Pattern of [Duration] if Duration is given
86func (hs *hapticSuite) TestPresentOverrides(c *C) {
87 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
88
89 ec := New(endp, hs.log)
90 // note: Duration given, as well as Pattern; Repeat given as 0:
91 notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Duration: 200, Pattern: []uint32{500}, Repeat: 0}}
92 c.Check(ec.Present("com.example.test_test-app", "nid", &notif), Equals, true)
93 callArgs := testibus.GetCallArgs(endp)
94 c.Assert(callArgs, HasLen, 1)
95 c.Check(callArgs[0].Member, Equals, "VibratePattern")
96 // note: Pattern of [Duration], Repeat of 1:
97 c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200}, uint32(1)})
98}
99
100// check that Present() doesn't call VibratePattern if things are not right
101func (hs *hapticSuite) TestSkipIfMissing(c *C) {
102 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
103
104 ec := New(endp, hs.log)
105 // no notification at all
106 c.Check(ec.Present("", "", nil), Equals, false)
107 // no Vibration in the notificaton
108 c.Check(ec.Present("", "", &launch_helper.Notification{}), Equals, false)
109 // empty Vibration
110 c.Check(ec.Present("", "", &launch_helper.Notification{Vibrate: &launch_helper.Vibration{}}), Equals, false)
111}
0112
=== modified file 'bus/notifications/app_helper/app_helper_c.go'
--- bus/notifications/app_helper/app_helper_c.go 2014-06-26 17:03:34 +0000
+++ bus/notifications/app_helper/app_helper_c.go 2014-07-07 14:23:18 +0000
@@ -19,23 +19,28 @@
1919
20/*20/*
21#cgo pkg-config: gio-unix-2.021#cgo pkg-config: gio-unix-2.0
22#cgo pkg-config: gio-2.0
23#include <stdlib.h>
24#include <glib.h>22#include <glib.h>
25#include <gio/gdesktopappinfo.h>23#include <gio/gdesktopappinfo.h>
24
25gchar* app_icon_filename_from_id (gchar* app_id) {
26 gchar* filename = NULL;
27 GAppInfo* app_info = (GAppInfo*)g_desktop_app_info_new (app_id);
28 if (app_info != NULL) {
29 GIcon* icon = g_app_info_get_icon (app_info);
30 if (icon != NULL) {
31 filename = g_icon_to_string (icon);
32 // g_app_info_get_icon has "transfer none"
33 }
34 g_object_unref (app_info);
35 }
36 g_free (app_id);
37 return filename;
38}
26*/39*/
27import "C"40import "C"
28import "unsafe"
2941
30func AppIconFromId(appId string) string {42func AppIconFromId(appId string) string {
31 _id := C.CString(appId)43 name := C.app_icon_filename_from_id((*C.gchar)(C.CString(appId)))
32 defer C.free(unsafe.Pointer(_id))44 defer C.g_free((C.gpointer)(name))
33 _app_info := C.g_desktop_app_info_new(_id)45 return C.GoString((*C.char)(name))
34 defer C.g_app_info_delete(_app_info)
35 _app_icon := C.g_app_info_get_icon(_app_info)
36 defer C.g_object_unref((C.gpointer)(_app_icon))
37 _icon_string := C.g_icon_to_string(_app_icon)
38 defer C.free(unsafe.Pointer(_icon_string))
39 name := C.GoString((*C.char)(_icon_string))
40 return name
41}46}
4247
=== modified file 'bus/testing/testing_endpoint.go'
--- bus/testing/testing_endpoint.go 2014-07-01 11:55:30 +0000
+++ bus/testing/testing_endpoint.go 2014-07-07 14:23:18 +0000
@@ -150,6 +150,21 @@
150 return res, err150 return res, err
151}151}
152152
153// See Endpoint's SetProperty. This one does nothing beyond
154// registering being called.
155func (tc *testingEndpoint) SetProperty(property string, suffix string, value interface{}) error {
156 tc.callArgsLck.Lock()
157 defer tc.callArgsLck.Unlock()
158
159 args := callArgs{
160 Member: "::SetProperty",
161 Args: []interface{}{property, suffix, value},
162 }
163 tc.callArgs = append(tc.callArgs, args)
164
165 return nil
166}
167
153// See Endpoint's Dial. This one will check its dialCondition to168// See Endpoint's Dial. This one will check its dialCondition to
154// decide whether to return an error or not.169// decide whether to return an error or not.
155func (endp *testingEndpoint) Dial() error {170func (endp *testingEndpoint) Dial() error {
156171
=== modified file 'bus/testing/testing_endpoint_test.go'
--- bus/testing/testing_endpoint_test.go 2014-07-01 11:55:30 +0000
+++ bus/testing/testing_endpoint_test.go 2014-07-07 14:23:18 +0000
@@ -238,3 +238,14 @@
238 Args: []interface{}{foomp, []interface{}(nil)},238 Args: []interface{}{foomp, []interface{}(nil)},
239 }})239 }})
240}240}
241
242// Test that SetProperty updates callArgs
243func (s *TestingEndpointSuite) TestSetPropertyUpdatesCallArgs(c *C) {
244 endp := NewTestingEndpoint(nil, condition.Work(true))
245 endp.SetProperty("prop", "suffix", "value")
246 c.Check(GetCallArgs(endp), DeepEquals, []callArgs{
247 {
248 Member: "::SetProperty",
249 Args: []interface{}{"prop", "suffix", "value"},
250 }})
251}
241252
=== added directory 'click'
=== added directory 'click/cclick'
=== added file 'click/cclick/cclick.go'
--- click/cclick/cclick.go 1970-01-01 00:00:00 +0000
+++ click/cclick/cclick.go 2014-07-07 14:23:18 +0000
@@ -0,0 +1,76 @@
1/*
2 Copyright 2013-2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17// Package cclick has the internal cgo wrapping libclick for package click.
18package cclick
19
20/*
21#cgo pkg-config: click-0.4
22#cgo pkg-config: glib-2.0 gobject-2.0
23
24#include <click-0.4/click.h>
25*/
26import "C"
27
28import (
29 "fmt"
30 "runtime"
31)
32
33type CClickUser struct {
34 cref *C.ClickUser
35}
36
37func gchar(s string) *C.gchar {
38 return (*C.gchar)(C.CString(s))
39}
40
41func gfree(s *C.gchar) {
42 C.g_free((C.gpointer)(s))
43}
44
45func (ccu *CClickUser) CInit(holder interface{}) error {
46 var gerr *C.GError
47 cref := C.click_user_new_for_user(nil, nil, &gerr)
48 defer C.g_clear_error(&gerr)
49 if gerr != nil {
50 return fmt.Errorf("faild to make ClickUser: %s", C.GoString((*C.char)(gerr.message)))
51 }
52 ccu.cref = cref
53 runtime.SetFinalizer(holder, func(interface{}) {
54 C.g_object_unref((C.gpointer)(cref))
55 })
56 return nil
57}
58
59func (ccu *CClickUser) CGetVersion(pkgName string) string {
60 pkgname := gchar(pkgName)
61 defer gfree(pkgname)
62 var gerr *C.GError
63 defer C.g_clear_error(&gerr)
64 ver := C.click_user_get_version(ccu.cref, pkgname, &gerr)
65 if gerr != nil {
66 return ""
67 }
68 defer gfree(ver)
69 return C.GoString((*C.char)(ver))
70}
71
72func (ccu *CClickUser) CHasPackageName(pkgName string) bool {
73 pkgname := gchar(pkgName)
74 defer gfree(pkgname)
75 return C.click_user_has_package_name(ccu.cref, pkgname) == C.TRUE
76}
077
=== added file 'click/click.go'
--- click/click.go 1970-01-01 00:00:00 +0000
+++ click/click.go 2014-07-07 14:23:18 +0000
@@ -0,0 +1,86 @@
1/*
2 Copyright 2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17// Package click exposes some utilities related to click packages and
18// wraps libclick to check if packages are installed.
19package click
20
21import (
22 "errors"
23 "regexp"
24 "sync"
25
26 "launchpad.net/ubuntu-push/click/cclick"
27)
28
29// AppId holds a parsed application id.
30type AppId struct {
31 Package string
32 Application string
33 Version string
34}
35
36// from https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId
37// except the version is made optional
38var rx = regexp.MustCompile(`^([a-z0-9][a-z0-9+.-]+)_([a-zA-Z0-9+.-]+)(?:_([0-9][a-zA-Z0-9.+:~-]*))?$`)
39
40var (
41 ErrInvalidAppId = errors.New("invalid application id")
42)
43
44func ParseAppId(id string) (*AppId, error) {
45 m := rx.FindStringSubmatch(id)
46 if len(m) == 0 {
47 return nil, ErrInvalidAppId
48 }
49 return &AppId{Package: m[1], Application: m[2], Version: m[3]}, nil
50}
51
52func AppInPackage(appId, pkgname string) bool {
53 id, _ := ParseAppId(appId)
54 return id != nil && id.Package == pkgname
55}
56
57// ClickUser exposes the click package registry for the user.
58type ClickUser struct {
59 ccu cclick.CClickUser
60 lock sync.Mutex
61}
62
63// User makes a new ClickUser object for the current user.
64func User() (*ClickUser, error) {
65 cu := new(ClickUser)
66 err := cu.ccu.CInit(cu)
67 if err != nil {
68 return nil, err
69 }
70 return cu, nil
71}
72
73// HasPackage checks if the appId is installed for user.
74func (cu *ClickUser) HasPackage(appId string) bool {
75 cu.lock.Lock()
76 defer cu.lock.Unlock()
77 id, err := ParseAppId(appId)
78 if err != nil {
79 return false
80 }
81 if id.Version != "" {
82 return cu.ccu.CGetVersion(id.Package) == id.Version
83 } else {
84 return cu.ccu.CHasPackageName(id.Package)
85 }
86}
087
=== added file 'click/click_test.go'
--- click/click_test.go 1970-01-01 00:00:00 +0000
+++ click/click_test.go 2014-07-07 14:23:18 +0000
@@ -0,0 +1,86 @@
1/*
2 Copyright 2013-2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17package click
18
19import (
20 "testing"
21
22 . "launchpad.net/gocheck"
23)
24
25func TestClick(t *testing.T) { TestingT(t) }
26
27type clickSuite struct{}
28
29var _ = Suite(&clickSuite{})
30
31func (cs *clickSuite) TestParseAppId(c *C) {
32 id, err := ParseAppId("com.ubuntu.clock_clock")
33 c.Assert(err, IsNil)
34 c.Check(id.Package, Equals, "com.ubuntu.clock")
35 c.Check(id.Application, Equals, "clock")
36 c.Check(id.Version, Equals, "")
37
38 id, err = ParseAppId("com.ubuntu.clock_clock_10")
39 c.Assert(err, IsNil)
40 c.Check(id.Package, Equals, "com.ubuntu.clock")
41 c.Check(id.Application, Equals, "clock")
42 c.Check(id.Version, Equals, "10")
43
44 for _, s := range []string{"com.ubuntu.clock_clock_10_4", "com.ubuntu.clock", ""} {
45 id, err = ParseAppId(s)
46 c.Check(id, IsNil)
47 c.Check(err, Equals, ErrInvalidAppId)
48 }
49}
50
51func (cs *clickSuite) TestInPackage(c *C) {
52 c.Check(AppInPackage("com.ubuntu.clock_clock", "com.ubuntu.clock"), Equals, true)
53 c.Check(AppInPackage("com.ubuntu.clock_clock_10", "com.ubuntu.clock"), Equals, true)
54 c.Check(AppInPackage("com.ubuntu.clock", "com.ubuntu.clock"), Equals, false)
55 c.Check(AppInPackage("bananas", "fruit"), Equals, false)
56}
57
58func (s *clickSuite) TestUser(c *C) {
59 u, err := User()
60 c.Assert(err, IsNil)
61 c.Assert(u, NotNil)
62}
63
64func (s *clickSuite) TestHasPackageNegative(c *C) {
65 u, err := User()
66 c.Assert(err, IsNil)
67 c.Check(u.HasPackage("com.foo.bar"), Equals, false)
68 c.Check(u.HasPackage("com.foo.bar_baz"), Equals, false)
69}
70
71func (s *clickSuite) TestHasPackageVersionNegative(c *C) {
72 u, err := User()
73 c.Assert(err, IsNil)
74 c.Check(u.HasPackage("com.ubuntu.clock_clock_1000.0"), Equals, false)
75}
76
77func (s *clickSuite) TestHasPackageClock(c *C) {
78 u, err := User()
79 c.Assert(err, IsNil)
80 ver := u.ccu.CGetVersion("com.ubuntu.clock")
81 if ver == "" {
82 c.Skip("no com.ubuntu.clock pkg installed")
83 }
84 c.Check(u.HasPackage("com.ubuntu.clock_clock"), Equals, true)
85 c.Check(u.HasPackage("com.ubuntu.clock_clock_"+ver), Equals, true)
86}
087
=== modified file 'client/client.go'
--- client/client.go 2014-07-01 11:55:30 +0000
+++ client/client.go 2014-07-07 14:23:18 +0000
@@ -26,16 +26,20 @@
26 "errors"26 "errors"
27 "fmt"27 "fmt"
28 "io/ioutil"28 "io/ioutil"
29 "net/url"
29 "os"30 "os"
30 "os/exec"31 "os/exec"
31 "strings"32 "strings"
3233
33 "launchpad.net/ubuntu-push/bus"34 "launchpad.net/ubuntu-push/bus"
34 "launchpad.net/ubuntu-push/bus/connectivity"35 "launchpad.net/ubuntu-push/bus/connectivity"
36 "launchpad.net/ubuntu-push/bus/emblemcounter"
37 "launchpad.net/ubuntu-push/bus/haptic"
35 "launchpad.net/ubuntu-push/bus/networkmanager"38 "launchpad.net/ubuntu-push/bus/networkmanager"
36 "launchpad.net/ubuntu-push/bus/notifications"39 "launchpad.net/ubuntu-push/bus/notifications"
37 "launchpad.net/ubuntu-push/bus/systemimage"40 "launchpad.net/ubuntu-push/bus/systemimage"
38 "launchpad.net/ubuntu-push/bus/urldispatcher"41 "launchpad.net/ubuntu-push/bus/urldispatcher"
42 "launchpad.net/ubuntu-push/click"
39 "launchpad.net/ubuntu-push/client/service"43 "launchpad.net/ubuntu-push/client/service"
40 "launchpad.net/ubuntu-push/client/session"44 "launchpad.net/ubuntu-push/client/session"
41 "launchpad.net/ubuntu-push/client/session/seenstate"45 "launchpad.net/ubuntu-push/client/session/seenstate"
@@ -68,6 +72,14 @@
68 LogLevel logger.ConfigLogLevel `json:"log_level"`72 LogLevel logger.ConfigLogLevel `json:"log_level"`
69}73}
7074
75// PushService is the interface we use of service.PushService.
76type PushService interface {
77 // Start starts the service.
78 Start() error
79 // Unregister unregisters the token for appId.
80 Unregister(appId string) error
81}
82
71// PushClient is the Ubuntu Push Notifications client-side daemon.83// PushClient is the Ubuntu Push Notifications client-side daemon.
72type PushClient struct {84type PushClient struct {
73 leveldbPath string85 leveldbPath string
@@ -80,6 +92,8 @@
80 notificationsEndp bus.Endpoint92 notificationsEndp bus.Endpoint
81 urlDispatcherEndp bus.Endpoint93 urlDispatcherEndp bus.Endpoint
82 connectivityEndp bus.Endpoint94 connectivityEndp bus.Endpoint
95 emblemcounterEndp bus.Endpoint
96 hapticEndp bus.Endpoint
83 systemImageEndp bus.Endpoint97 systemImageEndp bus.Endpoint
84 systemImageInfo *systemimage.InfoResult98 systemImageInfo *systemimage.InfoResult
85 connCh chan bool99 connCh chan bool
@@ -88,9 +102,13 @@
88 session *session.ClientSession102 session *session.ClientSession
89 sessionConnectedCh chan uint32103 sessionConnectedCh chan uint32
90 pushServiceEndpoint bus.Endpoint104 pushServiceEndpoint bus.Endpoint
91 pushService *service.PushService105 pushService PushService
92 postalServiceEndpoint bus.Endpoint106 postalServiceEndpoint bus.Endpoint
93 postalService *service.PostalService107 postalService *service.PostalService
108 unregisterCh chan string
109 trackAddressees map[string]bool
110 clickUser *click.ClickUser
111 hasPackage func(string) bool
94}112}
95113
96// Creates a new Ubuntu Push Notifications client-side daemon that will use114// Creates a new Ubuntu Push Notifications client-side daemon that will use
@@ -122,14 +140,26 @@
122 // later, we'll be specifying more logging options in the config file140 // later, we'll be specifying more logging options in the config file
123 client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel.Level())141 client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel.Level())
124142
143 clickUser, err := click.User()
144 if err != nil {
145 return fmt.Errorf("libclick: %v", err)
146 }
147 client.clickUser = clickUser
148 // overridden for testing
149 client.hasPackage = clickUser.HasPackage
150
151 client.unregisterCh = make(chan string, 10)
152
125 // overridden for testing153 // overridden for testing
126 client.idder = identifier.New()154 client.idder = identifier.New()
127 client.urlDispatcherEndp = bus.SessionBus.Endpoint(urldispatcher.BusAddress, client.log)155 client.urlDispatcherEndp = bus.SessionBus.Endpoint(urldispatcher.BusAddress, client.log)
128 client.connectivityEndp = bus.SystemBus.Endpoint(networkmanager.BusAddress, client.log)156 client.connectivityEndp = bus.SystemBus.Endpoint(networkmanager.BusAddress, client.log)
129 client.systemImageEndp = bus.SystemBus.Endpoint(systemimage.BusAddress, client.log)157 client.systemImageEndp = bus.SystemBus.Endpoint(systemimage.BusAddress, client.log)
130 if client.notificationsEndp == nil {158 client.notificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, client.log)
131 client.notificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, client.log)159 client.emblemcounterEndp = bus.SessionBus.Endpoint(emblemcounter.BusAddress, client.log)
132 }160 client.hapticEndp = bus.SessionBus.Endpoint(haptic.BusAddress, client.log)
161 client.postalServiceEndpoint = bus.SessionBus.Endpoint(service.PostalServiceBusAddress, client.log)
162 client.pushServiceEndpoint = bus.SessionBus.Endpoint(service.PushServiceBusAddress, client.log)
133163
134 client.connCh = make(chan bool, 1)164 client.connCh = make(chan bool, 1)
135 client.sessionConnectedCh = make(chan uint32, 1)165 client.sessionConnectedCh = make(chan uint32, 1)
@@ -156,11 +186,25 @@
156 ExchangeTimeout: client.config.ExchangeTimeout.TimeDuration(),186 ExchangeTimeout: client.config.ExchangeTimeout.TimeDuration(),
157 HostsCachingExpiryTime: client.config.HostsCachingExpiryTime.TimeDuration(),187 HostsCachingExpiryTime: client.config.HostsCachingExpiryTime.TimeDuration(),
158 ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(),188 ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(),
159 PEM: client.pem,189 PEM: client.pem,
160 Info: info,190 Info: info,
161 AuthGetter: client.getAuthorization,191 AuthGetter: client.getAuthorization,
162 AuthURL: client.config.SessionURL,192 AuthURL: client.config.SessionURL,
163 }193 AddresseeChecker: client,
194 }
195}
196
197// derivePushServiceSetup derives the service setup from the client configuration bits.
198func (client *PushClient) derivePushServiceSetup() (*service.PushServiceSetup, error) {
199 setup := new(service.PushServiceSetup)
200 purl, err := url.Parse(client.config.RegistrationURL)
201 if err != nil {
202 return nil, fmt.Errorf("cannot parse registration url: %v", err)
203 }
204 setup.RegURL = purl
205 setup.DeviceId = client.deviceId
206 setup.AuthGetter = client.getAuthorization
207 return setup, nil
164}208}
165209
166// getAuthorization gets the authorization blob to send to the server210// getAuthorization gets the authorization blob to send to the server
@@ -249,6 +293,42 @@
249 }293 }
250}294}
251295
296// StartAddresseeBatch starts a batch of checks for addressees.
297func (client *PushClient) StartAddresseeBatch() {
298 client.trackAddressees = make(map[string]bool, 10)
299}
300
301// CheckForAddressee check for the addressee presence.
302func (client *PushClient) CheckForAddressee(notif *protocol.Notification) bool {
303 appId := notif.AppId
304 present, ok := client.trackAddressees[appId]
305 if ok {
306 return present
307 }
308 present = client.hasPackage(appId)
309 client.trackAddressees[appId] = present
310 if !present {
311 client.unregisterCh <- appId
312 }
313 return present
314}
315
316// handleUnregister deals with tokens of uninstalled apps
317func (client *PushClient) handleUnregister(appId string) {
318 if !client.hasPackage(appId) {
319 // xxx small chance of race here, in case the app gets
320 // reinstalled and registers itself before we finish
321 // the unregister; we need click and app launching
322 // collaboration to do better. we redo the hasPackage
323 // check here just before to keep the race window as
324 // small as possible
325 err := client.pushService.Unregister(appId)
326 if err != nil {
327 client.log.Errorf("unregistering %s: %s", appId, err)
328 }
329 }
330}
331
252// handleConnState deals with connectivity events332// handleConnState deals with connectivity events
253func (client *PushClient) handleConnState(hasConnectivity bool) {333func (client *PushClient) handleConnState(hasConnectivity bool) {
254 if client.hasConnectivity == hasConnectivity {334 if client.hasConnectivity == hasConnectivity {
@@ -320,9 +400,7 @@
320 if !client.filterBroadcastNotification(msg) {400 if !client.filterBroadcastNotification(msg) {
321 return nil401 return nil
322 }402 }
323 not_id, err := client.postalService.SendNotification(service.ACTION_ID_BROADCAST,403 not_id, err := client.postalService.InjectBroadcast()
324 "update_manager_icon", "There's an updated system image.",
325 "Tap to open the system updater.")
326 if err != nil {404 if err != nil {
327 client.log.Errorf("showing notification: %s", err)405 client.log.Errorf("showing notification: %s", err)
328 return err406 return err
@@ -333,20 +411,30 @@
333411
334// handleUnicastNotification deals with receiving a unicast notification412// handleUnicastNotification deals with receiving a unicast notification
335func (client *PushClient) handleUnicastNotification(msg *protocol.Notification) error {413func (client *PushClient) handleUnicastNotification(msg *protocol.Notification) error {
414 appId, err := click.ParseAppId(msg.AppId)
415 if err != nil {
416 client.log.Debugf("notification %#v for invalid app id %#v.", msg.MsgId, msg.AppId)
417 return errors.New("invalid app id in notification")
418 }
336 client.log.Debugf("sending notification %#v for %#v.", msg.MsgId, msg.AppId)419 client.log.Debugf("sending notification %#v for %#v.", msg.MsgId, msg.AppId)
337 return client.postalService.Inject(msg.AppId, msg.MsgId, string(msg.Payload))420 return client.postalService.Inject(appId.Package, msg.AppId, msg.MsgId, string(msg.Payload))
338}421}
339422
340// handleClick deals with the user clicking a notification423// handleClick deals with the user clicking a notification
341func (client *PushClient) handleClick(action_id string) error {424func (client *PushClient) handleClick(actionId string) error {
342 // “The string is a stark data structure and everywhere it is passed425 // “The string is a stark data structure and everywhere it is passed
343 // there is much duplication of process. It is a perfect vehicle for426 // there is much duplication of process. It is a perfect vehicle for
344 // hiding information.”427 // hiding information.”
345 //428 //
346 // From ACM's SIGPLAN publication, (September, 1982), Article429 // From ACM's SIGPLAN publication, (September, 1982), Article
347 // "Epigrams in Programming", by Alan J. Perlis of Yale University.430 // "Epigrams in Programming", by Alan J. Perlis of Yale University.
348 url := strings.TrimPrefix(action_id, service.ACTION_ID_SNOWFLAKE)431 url := actionId
349 if len(url) == len(action_id) || len(url) == 0 {432 // XXX: branch for the broadcast notifications
433 if strings.HasPrefix(actionId, service.ACTION_ID_PREFIX) {
434 parts := strings.Split(actionId, "::")
435 url = parts[1]
436 }
437 if len(url) == len(actionId) || len(url) == 0 {
350 // it didn't start with the prefix438 // it didn't start with the prefix
351 return nil439 return nil
352 }440 }
@@ -356,7 +444,7 @@
356}444}
357445
358// doLoop connects events with their handlers446// doLoop connects events with their handlers
359func (client *PushClient) doLoop(connhandler func(bool), clickhandler func(string) error, bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(*protocol.Notification) error, errhandler func(error)) {447func (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)) {
360 for {448 for {
361 select {449 select {
362 case state := <-client.connCh:450 case state := <-client.connCh:
@@ -371,6 +459,8 @@
371 errhandler(err)459 errhandler(err)
372 case count := <-client.sessionConnectedCh:460 case count := <-client.sessionConnectedCh:
373 client.log.Debugf("Session connected after %d attempts", count)461 client.log.Debugf("Session connected after %d attempts", count)
462 case appId := <-client.unregisterCh:
463 unregisterhandler(appId)
374 }464 }
375 }465 }
376}466}
@@ -392,21 +482,17 @@
392 client.handleClick,482 client.handleClick,
393 client.handleBroadcastNotification,483 client.handleBroadcastNotification,
394 client.handleUnicastNotification,484 client.handleUnicastNotification,
395 client.handleErr)485 client.handleErr,
486 client.handleUnregister)
396}487}
397488
398func (client *PushClient) startService() error {489func (client *PushClient) startService() error {
399 if client.pushServiceEndpoint == nil {490 setup, err := client.derivePushServiceSetup()
400 client.pushServiceEndpoint = bus.SessionBus.Endpoint(service.PushServiceBusAddress, client.log)491 if err != nil {
401 }492 return err
402 if client.postalServiceEndpoint == nil {
403 client.postalServiceEndpoint = bus.SessionBus.Endpoint(service.PostalServiceBusAddress, client.log)
404 }493 }
405494
406 client.pushService = service.NewPushService(client.pushServiceEndpoint, client.log)495 client.pushService = service.NewPushService(client.pushServiceEndpoint, setup, client.log)
407 client.pushService.SetRegistrationURL(client.config.RegistrationURL)
408 client.pushService.SetAuthGetter(client.getAuthorization)
409 client.pushService.SetDeviceId(client.deviceId)
410 if err := client.pushService.Start(); err != nil {496 if err := client.pushService.Start(); err != nil {
411 return err497 return err
412 }498 }
@@ -414,10 +500,7 @@
414}500}
415501
416func (client *PushClient) setupPostalService() error {502func (client *PushClient) setupPostalService() error {
417 if client.notificationsEndp == nil {503 client.postalService = service.NewPostalService(client.postalServiceEndpoint, client.notificationsEndp, client.emblemcounterEndp, client.hapticEndp, client.log)
418 client.notificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, client.log)
419 }
420 client.postalService = service.NewPostalService(client.postalServiceEndpoint, client.notificationsEndp, client.log)
421 return nil504 return nil
422}505}
423506
424507
=== modified file 'client/client_test.go'
--- client/client_test.go 2014-07-01 11:55:30 +0000
+++ client/client_test.go 2014-07-07 14:23:18 +0000
@@ -187,14 +187,26 @@
187187
188func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) {188func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) {
189 cli := NewPushClient(cs.configPath, cs.leveldbPath)189 cli := NewPushClient(cs.configPath, cs.leveldbPath)
190
191 // keep these in the same order as in the client struct, for sanity
190 c.Check(cli.notificationsEndp, IsNil)192 c.Check(cli.notificationsEndp, IsNil)
191 c.Check(cli.urlDispatcherEndp, IsNil)193 c.Check(cli.urlDispatcherEndp, IsNil)
192 c.Check(cli.connectivityEndp, IsNil)194 c.Check(cli.connectivityEndp, IsNil)
195 c.Check(cli.emblemcounterEndp, IsNil)
196 c.Check(cli.hapticEndp, IsNil)
197 c.Check(cli.systemImageEndp, IsNil)
198 c.Check(cli.pushServiceEndpoint, IsNil)
199 c.Check(cli.postalServiceEndpoint, IsNil)
193 err := cli.configure()200 err := cli.configure()
194 c.Assert(err, IsNil)201 c.Assert(err, IsNil)
195 c.Assert(cli.notificationsEndp, NotNil)202 c.Check(cli.notificationsEndp, NotNil)
196 c.Assert(cli.urlDispatcherEndp, NotNil)203 c.Check(cli.urlDispatcherEndp, NotNil)
197 c.Assert(cli.connectivityEndp, NotNil)204 c.Check(cli.connectivityEndp, NotNil)
205 c.Check(cli.emblemcounterEndp, NotNil)
206 c.Check(cli.hapticEndp, NotNil)
207 c.Check(cli.systemImageEndp, NotNil)
208 c.Check(cli.pushServiceEndpoint, NotNil)
209 c.Check(cli.postalServiceEndpoint, NotNil)
198}210}
199211
200func (cs *clientSuite) TestConfigureSetsUpConnCh(c *C) {212func (cs *clientSuite) TestConfigureSetsUpConnCh(c *C) {
@@ -205,6 +217,15 @@
205 c.Assert(cli.connCh, NotNil)217 c.Assert(cli.connCh, NotNil)
206}218}
207219
220func (cs *clientSuite) TestConfigureSetsUpAddresseeChecks(c *C) {
221 cli := NewPushClient(cs.configPath, cs.leveldbPath)
222 c.Check(cli.unregisterCh, IsNil)
223 err := cli.configure()
224 c.Assert(err, IsNil)
225 c.Assert(cli.unregisterCh, NotNil)
226 c.Assert(cli.hasPackage("com.bar.baz_foo"), Equals, false)
227}
228
208func (cs *clientSuite) TestConfigureBailsOnBadFilename(c *C) {229func (cs *clientSuite) TestConfigureBailsOnBadFilename(c *C) {
209 cli := NewPushClient("/does/not/exist", cs.leveldbPath)230 cli := NewPushClient("/does/not/exist", cs.leveldbPath)
210 err := cli.configure()231 err := cli.configure()
@@ -255,6 +276,35 @@
255}276}
256277
257/*****************************************************************278/*****************************************************************
279 addresses checking tests
280******************************************************************/
281
282func (cs *clientSuite) TestCheckForAddressee(c *C) {
283 cli := NewPushClient(cs.configPath, cs.leveldbPath)
284 cli.unregisterCh = make(chan string, 5)
285 cli.StartAddresseeBatch()
286 calls := 0
287 cli.hasPackage = func(appId string) bool {
288 calls++
289 if appId == "app1" {
290 return false
291 }
292 return true
293 }
294 c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: "app1"}), Equals, false)
295 c.Check(calls, Equals, 1)
296 c.Assert(cli.unregisterCh, HasLen, 1)
297 c.Check(<-cli.unregisterCh, Equals, "app1")
298 c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: "app2"}), Equals, true)
299 c.Check(calls, Equals, 2)
300 c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: "app1"}), Equals, false)
301 c.Check(calls, Equals, 2)
302 c.Check(cli.CheckForAddressee(&protocol.Notification{AppId: "app2"}), Equals, true)
303 c.Check(calls, Equals, 2)
304 c.Check(cli.unregisterCh, HasLen, 0)
305}
306
307/*****************************************************************
258 deriveSessionConfig tests308 deriveSessionConfig tests
259******************************************************************/309******************************************************************/
260310
@@ -273,10 +323,11 @@
273 ExchangeTimeout: 10 * time.Millisecond,323 ExchangeTimeout: 10 * time.Millisecond,
274 HostsCachingExpiryTime: 1 * time.Hour,324 HostsCachingExpiryTime: 1 * time.Hour,
275 ExpectAllRepairedTime: 30 * time.Minute,325 ExpectAllRepairedTime: 30 * time.Minute,
276 PEM: cli.pem,326 PEM: cli.pem,
277 Info: info,327 Info: info,
278 AuthGetter: func(string) string { return "" },328 AuthGetter: func(string) string { return "" },
279 AuthURL: "xyzzy://",329 AuthURL: "xyzzy://",
330 AddresseeChecker: cli,
280 }331 }
281 // sanity check that we are looking at all fields332 // sanity check that we are looking at all fields
282 vExpected := reflect.ValueOf(expected)333 vExpected := reflect.ValueOf(expected)
@@ -289,7 +340,7 @@
289 // finally compare340 // finally compare
290 conf := cli.deriveSessionConfig(info)341 conf := cli.deriveSessionConfig(info)
291 // compare authGetter by string342 // compare authGetter by string
292 c.Check(fmt.Sprintf("%v", conf.AuthGetter), Equals, fmt.Sprintf("%v", cli.getAuthorization))343 c.Check(fmt.Sprintf("%#v", conf.AuthGetter), Equals, fmt.Sprintf("%#v", cli.getAuthorization))
293 // and set it to nil344 // and set it to nil
294 conf.AuthGetter = nil345 conf.AuthGetter = nil
295 expected.AuthGetter = nil346 expected.AuthGetter = nil
@@ -297,6 +348,51 @@
297}348}
298349
299/*****************************************************************350/*****************************************************************
351 derivePushServiceSetup tests
352******************************************************************/
353
354func (cs *clientSuite) TestDerivePushServiceSetup(c *C) {
355 cs.writeTestConfig(map[string]interface{}{})
356 cli := NewPushClient(cs.configPath, cs.leveldbPath)
357 err := cli.configure()
358 c.Assert(err, IsNil)
359 cli.deviceId = "zoo"
360 expected := &service.PushServiceSetup{
361 DeviceId: "zoo",
362 AuthGetter: func(string) string { return "" },
363 RegURL: helpers.ParseURL("reg://"),
364 }
365 // sanity check that we are looking at all fields
366 vExpected := reflect.ValueOf(expected).Elem()
367 nf := vExpected.NumField()
368 for i := 0; i < nf; i++ {
369 fv := vExpected.Field(i)
370 // field isn't empty/zero
371 c.Assert(fv.Interface(), Not(DeepEquals), reflect.Zero(fv.Type()).Interface(), Commentf("forgot about: %s", vExpected.Type().Field(i).Name))
372 }
373 // finally compare
374 setup, err := cli.derivePushServiceSetup()
375 c.Assert(err, IsNil)
376 // compare authGetter by string
377 c.Check(fmt.Sprintf("%#v", setup.AuthGetter), Equals, fmt.Sprintf("%#v", cli.getAuthorization))
378 // and set it to nil
379 setup.AuthGetter = nil
380 expected.AuthGetter = nil
381 c.Check(setup, DeepEquals, expected)
382}
383
384func (cs *clientSuite) TestDerivePushServiceSetupError(c *C) {
385 cs.writeTestConfig(map[string]interface{}{
386 "registration_url": "%gh",
387 })
388 cli := NewPushClient(cs.configPath, cs.leveldbPath)
389 err := cli.configure()
390 c.Assert(err, IsNil)
391 _, err = cli.derivePushServiceSetup()
392 c.Check(err, ErrorMatches, "cannot parse registration url:.*")
393}
394
395/*****************************************************************
300 startService tests396 startService tests
301******************************************************************/397******************************************************************/
302398
@@ -305,7 +401,8 @@
305 "auth_helper": helpers.ScriptAbsPath("dummyauth.sh"),401 "auth_helper": helpers.ScriptAbsPath("dummyauth.sh"),
306 })402 })
307 cli := NewPushClient(cs.configPath, cs.leveldbPath)403 cli := NewPushClient(cs.configPath, cs.leveldbPath)
308 cli.configure()404 err := cli.configure()
405 c.Assert(err, IsNil)
309 cli.log = cs.log406 cli.log = cs.log
310 cli.deviceId = "fake-id"407 cli.deviceId = "fake-id"
311 cli.pushServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil)408 cli.pushServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil)
@@ -313,16 +410,26 @@
313 c.Check(cli.pushService, IsNil)410 c.Check(cli.pushService, IsNil)
314 c.Check(cli.startService(), IsNil)411 c.Check(cli.startService(), IsNil)
315 c.Assert(cli.pushService, NotNil)412 c.Assert(cli.pushService, NotNil)
316 c.Check(cli.pushService.IsRunning(), Equals, true)413 pushService := cli.pushService.(*service.PushService)
317 c.Check(cli.pushService.GetDeviceId(), Equals, "fake-id")414 c.Check(pushService.IsRunning(), Equals, true)
318 c.Check(cli.pushService.GetRegistrationAuthorization(), Equals, "hello reg://")
319 c.Assert(cli.setupPostalService(), IsNil)415 c.Assert(cli.setupPostalService(), IsNil)
320 c.Assert(cli.startPostalService(), IsNil)416 c.Assert(cli.startPostalService(), IsNil)
321 c.Check(cli.postalService.IsRunning(), Equals, true)417 c.Check(cli.postalService.IsRunning(), Equals, true)
322 cli.pushService.Stop()418 pushService.Stop()
323 cli.postalService.Stop()419 cli.postalService.Stop()
324}420}
325421
422func (cs *clientSuite) TestStartServiceSetupError(c *C) {
423 cs.writeTestConfig(map[string]interface{}{
424 "registration_url": "%gh",
425 })
426 cli := NewPushClient(cs.configPath, cs.leveldbPath)
427 err := cli.configure()
428 c.Assert(err, IsNil)
429 err = cli.startService()
430 c.Check(err, ErrorMatches, "cannot parse registration url:.*")
431}
432
326func (cs *clientSuite) TestStartServiceErrorsOnNilLog(c *C) {433func (cs *clientSuite) TestStartServiceErrorsOnNilLog(c *C) {
327 cli := NewPushClient(cs.configPath, cs.leveldbPath)434 cli := NewPushClient(cs.configPath, cs.leveldbPath)
328 c.Check(cli.log, IsNil)435 c.Check(cli.log, IsNil)
@@ -404,6 +511,10 @@
404 siCond := condition.Fail2Work(2)511 siCond := condition.Fail2Work(2)
405 siEndp := testibus.NewMultiValuedTestingEndpoint(siCond, condition.Work(true), []interface{}{int32(101), "mako", "daily", "Unknown", map[string]string{}})512 siEndp := testibus.NewMultiValuedTestingEndpoint(siCond, condition.Work(true), []interface{}{int32(101), "mako", "daily", "Unknown", map[string]string{}})
406 testibus.SetWatchTicker(cEndp, make(chan bool))513 testibus.SetWatchTicker(cEndp, make(chan bool))
514 ecCond := condition.Fail2Work(13)
515 ecEndp := testibus.NewTestingEndpoint(ecCond, condition.Work(true))
516 haCond := condition.Fail2Work(2)
517 haEndp := testibus.NewTestingEndpoint(haCond, condition.Work(true))
407 // ok, create the thing518 // ok, create the thing
408 cli := NewPushClient(cs.configPath, cs.leveldbPath)519 cli := NewPushClient(cs.configPath, cs.leveldbPath)
409 cli.log = cs.log520 cli.log = cs.log
@@ -418,6 +529,8 @@
418 cli.notificationsEndp = nEndp529 cli.notificationsEndp = nEndp
419 cli.urlDispatcherEndp = uEndp530 cli.urlDispatcherEndp = uEndp
420 cli.connectivityEndp = cEndp531 cli.connectivityEndp = cEndp
532 cli.emblemcounterEndp = ecEndp
533 cli.hapticEndp = haEndp
421 cli.systemImageEndp = siEndp534 cli.systemImageEndp = siEndp
422535
423 c.Assert(cli.takeTheBus(), IsNil)536 c.Assert(cli.takeTheBus(), IsNil)
@@ -434,6 +547,10 @@
434 c.Check(cCond.OK(), Equals, true)547 c.Check(cCond.OK(), Equals, true)
435 // the systemimage endpoint retried until connected548 // the systemimage endpoint retried until connected
436 c.Check(siCond.OK(), Equals, true)549 c.Check(siCond.OK(), Equals, true)
550 // the emblemcounter endpoint retried until connected
551 c.Check(ecCond.OK(), Equals, true)
552 // the haptic endpoint retried until connected
553 c.Check(haCond.OK(), Equals, true)
437}554}
438555
439// takeTheBus can, in fact, fail556// takeTheBus can, in fact, fail
@@ -449,6 +566,8 @@
449 cli.notificationsEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false))566 cli.notificationsEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false))
450 cli.urlDispatcherEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false))567 cli.urlDispatcherEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false))
451 cli.connectivityEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false))568 cli.connectivityEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false))
569 cli.emblemcounterEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false))
570 cli.hapticEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false))
452 cli.systemImageEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false))571 cli.systemImageEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false))
453572
454 c.Check(cli.takeTheBus(), NotNil)573 c.Check(cli.takeTheBus(), NotNil)
@@ -654,7 +773,7 @@
654 args := testibus.GetCallArgs(endp)773 args := testibus.GetCallArgs(endp)
655 c.Assert(args, HasLen, 1)774 c.Assert(args, HasLen, 1)
656 c.Check(args[0].Member, Equals, "Notify")775 c.Check(args[0].Member, Equals, "Notify")
657 c.Check(cs.log.Captured(), Matches, `.* got notification id \d+\s*`)776 c.Check(cs.log.Captured(), Matches, `(?s).* got notification id \d+\s*`)
658}777}
659778
660func (cs *clientSuite) TestHandleBroadcastNotificationNothingToDo(c *C) {779func (cs *clientSuite) TestHandleBroadcastNotificationNothingToDo(c *C) {
@@ -686,18 +805,13 @@
686******************************************************************/805******************************************************************/
687806
688var payload = `{"message": "aGVsbG8=", "notification": {"card": {"icon": "icon-value", "summary": "summary-value", "body": "body-value", "actions": []}}}`807var payload = `{"message": "aGVsbG8=", "notification": {"card": {"icon": "icon-value", "summary": "summary-value", "body": "body-value", "actions": []}}}`
689var notif = &protocol.Notification{AppId: "hello", Payload: []byte(payload), MsgId: "42"}808var notif = &protocol.Notification{AppId: "com.example.test_hello", Payload: []byte(payload), MsgId: "42"}
690809
691func (cs *clientSuite) TestHandleUcastNotification(c *C) {810func (cs *clientSuite) TestHandleUcastNotification(c *C) {
692 cli := NewPushClient(cs.configPath, cs.leveldbPath)811 cli := NewPushClient(cs.configPath, cs.leveldbPath)
693 svcEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1))
694 postEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1))812 postEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1))
695 cli.log = cs.log813 cli.log = cs.log
696 cli.pushServiceEndpoint = svcEndp
697 cli.postalServiceEndpoint = postEndp814 cli.postalServiceEndpoint = postEndp
698 notsEndp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
699 cli.notificationsEndp = notsEndp
700 c.Assert(cli.startService(), IsNil)
701 c.Assert(cli.setupPostalService(), IsNil)815 c.Assert(cli.setupPostalService(), IsNil)
702 c.Assert(cli.startPostalService(), IsNil)816 c.Assert(cli.startPostalService(), IsNil)
703 c.Check(cli.handleUnicastNotification(notif), IsNil)817 c.Check(cli.handleUnicastNotification(notif), IsNil)
@@ -705,13 +819,22 @@
705 args := testibus.GetCallArgs(postEndp)819 args := testibus.GetCallArgs(postEndp)
706 c.Assert(len(args), Not(Equals), 0)820 c.Assert(len(args), Not(Equals), 0)
707 c.Check(args[len(args)-1].Member, Equals, "::Signal")821 c.Check(args[len(args)-1].Member, Equals, "::Signal")
708 c.Check(cs.log.Captured(), Matches, `(?m).*sending notification "42" for "hello".*`)822 c.Check(cs.log.Captured(), Matches, `(?m).*sending notification "42" for "com.example.test_hello".*`)
823}
824
825func (cs *clientSuite) TestHandleUcastFailsOnBadAppId(c *C) {
826 notif := &protocol.Notification{AppId: "bad-app-id", MsgId: "-1"}
827 cli := NewPushClient(cs.configPath, cs.leveldbPath)
828 cli.log = cs.log
829 c.Check(cli.handleUnicastNotification(notif), ErrorMatches, "invalid app id in notification")
709}830}
710831
711/*****************************************************************832/*****************************************************************
712 handleClick tests833 handleClick tests
713******************************************************************/834******************************************************************/
714835
836var ACTION_ID_BROADCAST = service.ACTION_ID_PREFIX + service.SystemUpdateUrl + service.ACTION_ID_SUFFIX
837
715func (cs *clientSuite) TestHandleClick(c *C) {838func (cs *clientSuite) TestHandleClick(c *C) {
716 cli := NewPushClient(cs.configPath, cs.leveldbPath)839 cli := NewPushClient(cs.configPath, cs.leveldbPath)
717 cli.log = cs.log840 cli.log = cs.log
@@ -723,14 +846,14 @@
723 args := testibus.GetCallArgs(endp)846 args := testibus.GetCallArgs(endp)
724 c.Assert(args, HasLen, 0)847 c.Assert(args, HasLen, 0)
725 // check we worked with the right action id848 // check we worked with the right action id
726 c.Check(cli.handleClick(service.ACTION_ID_BROADCAST), IsNil)849 c.Check(cli.handleClick(ACTION_ID_BROADCAST), IsNil)
727 // check we sent the notification850 // check we sent the notification
728 args = testibus.GetCallArgs(endp)851 args = testibus.GetCallArgs(endp)
729 c.Assert(args, HasLen, 1)852 c.Assert(args, HasLen, 1)
730 c.Check(args[0].Member, Equals, "DispatchURL")853 c.Check(args[0].Member, Equals, "DispatchURL")
731 c.Check(args[0].Args, DeepEquals, []interface{}{service.SystemUpdateUrl})854 c.Check(args[0].Args, DeepEquals, []interface{}{service.SystemUpdateUrl})
732 // check we worked with the right action id855 // check we worked with the right action id
733 c.Check(cli.handleClick(service.ACTION_ID_SNOWFLAKE+"foo"), IsNil)856 c.Check(cli.handleClick(service.ACTION_ID_PREFIX+"foo"), IsNil)
734 // check we sent the notification857 // check we sent the notification
735 args = testibus.GetCallArgs(endp)858 args = testibus.GetCallArgs(endp)
736 c.Assert(args, HasLen, 2)859 c.Assert(args, HasLen, 2)
@@ -739,6 +862,64 @@
739}862}
740863
741/*****************************************************************864/*****************************************************************
865 handleUnregister tests
866******************************************************************/
867
868type testPushService struct {
869 err error
870 unregistered string
871}
872
873func (ps *testPushService) Start() error {
874 return nil
875}
876
877func (ps *testPushService) Unregister(appId string) error {
878 ps.unregistered = appId
879 return ps.err
880}
881
882func (cs *clientSuite) TestHandleUnregister(c *C) {
883 cli := NewPushClient(cs.configPath, cs.leveldbPath)
884 cli.log = cs.log
885 cli.hasPackage = func(appId string) bool {
886 c.Check(appId, Equals, "app1")
887 return false
888 }
889 ps := &testPushService{}
890 cli.pushService = ps
891 cli.handleUnregister("app1")
892 c.Assert(ps.unregistered, Equals, "app1")
893 c.Check(cs.log.Captured(), Equals, "")
894}
895
896func (cs *clientSuite) TestHandleUnregisterNop(c *C) {
897 cli := NewPushClient(cs.configPath, cs.leveldbPath)
898 cli.log = cs.log
899 cli.hasPackage = func(appId string) bool {
900 c.Check(appId, Equals, "app1")
901 return true
902 }
903 ps := &testPushService{}
904 cli.pushService = ps
905 cli.handleUnregister("app1")
906 c.Assert(ps.unregistered, Equals, "")
907}
908
909func (cs *clientSuite) TestHandleUnregisterError(c *C) {
910 cli := NewPushClient(cs.configPath, cs.leveldbPath)
911 cli.log = cs.log
912 cli.hasPackage = func(appId string) bool {
913 return false
914 }
915 fail := errors.New("BAD")
916 ps := &testPushService{err: fail}
917 cli.pushService = ps
918 cli.handleUnregister("app1")
919 c.Check(cs.log.Captured(), Matches, "ERROR unregistering app1: BAD\n")
920}
921
922/*****************************************************************
742 doLoop tests923 doLoop tests
743******************************************************************/924******************************************************************/
744925
@@ -747,6 +928,7 @@
747var nopBcast = func(*session.BroadcastNotification) error { return nil }928var nopBcast = func(*session.BroadcastNotification) error { return nil }
748var nopUcast = func(*protocol.Notification) error { return nil }929var nopUcast = func(*protocol.Notification) error { return nil }
749var nopError = func(error) {}930var nopError = func(error) {}
931var nopUnregister = func(string) {}
750932
751func (cs *clientSuite) TestDoLoopConn(c *C) {933func (cs *clientSuite) TestDoLoopConn(c *C) {
752 cli := NewPushClient(cs.configPath, cs.leveldbPath)934 cli := NewPushClient(cs.configPath, cs.leveldbPath)
@@ -757,7 +939,7 @@
757 c.Assert(cli.initSession(), IsNil)939 c.Assert(cli.initSession(), IsNil)
758940
759 ch := make(chan bool, 1)941 ch := make(chan bool, 1)
760 go cli.doLoop(func(bool) { ch <- true }, nopClick, nopBcast, nopUcast, nopError)942 go cli.doLoop(func(bool) { ch <- true }, nopClick, nopBcast, nopUcast, nopError, nopUnregister)
761 c.Check(takeNextBool(ch), Equals, true)943 c.Check(takeNextBool(ch), Equals, true)
762}944}
763945
@@ -771,7 +953,7 @@
771 cli.actionsCh = aCh953 cli.actionsCh = aCh
772954
773 ch := make(chan bool, 1)955 ch := make(chan bool, 1)
774 go cli.doLoop(nopConn, func(_ string) error { ch <- true; return nil }, nopBcast, nopUcast, nopError)956 go cli.doLoop(nopConn, func(_ string) error { ch <- true; return nil }, nopBcast, nopUcast, nopError, nopUnregister)
775 c.Check(takeNextBool(ch), Equals, true)957 c.Check(takeNextBool(ch), Equals, true)
776}958}
777959
@@ -784,7 +966,7 @@
784 cli.session.BroadcastCh <- &session.BroadcastNotification{}966 cli.session.BroadcastCh <- &session.BroadcastNotification{}
785967
786 ch := make(chan bool, 1)968 ch := make(chan bool, 1)
787 go cli.doLoop(nopConn, nopClick, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopError)969 go cli.doLoop(nopConn, nopClick, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopError, nopUnregister)
788 c.Check(takeNextBool(ch), Equals, true)970 c.Check(takeNextBool(ch), Equals, true)
789}971}
790972
@@ -797,7 +979,7 @@
797 cli.session.NotificationsCh <- &protocol.Notification{}979 cli.session.NotificationsCh <- &protocol.Notification{}
798980
799 ch := make(chan bool, 1)981 ch := make(chan bool, 1)
800 go cli.doLoop(nopConn, nopClick, nopBcast, func(*protocol.Notification) error { ch <- true; return nil }, nopError)982 go cli.doLoop(nopConn, nopClick, nopBcast, func(*protocol.Notification) error { ch <- true; return nil }, nopError, nopUnregister)
801 c.Check(takeNextBool(ch), Equals, true)983 c.Check(takeNextBool(ch), Equals, true)
802}984}
803985
@@ -810,7 +992,20 @@
810 cli.session.ErrCh <- nil992 cli.session.ErrCh <- nil
811993
812 ch := make(chan bool, 1)994 ch := make(chan bool, 1)
813 go cli.doLoop(nopConn, nopClick, nopBcast, nopUcast, func(error) { ch <- true })995 go cli.doLoop(nopConn, nopClick, nopBcast, nopUcast, func(error) { ch <- true }, nopUnregister)
996 c.Check(takeNextBool(ch), Equals, true)
997}
998
999func (cs *clientSuite) TestDoLoopUnregister(c *C) {
1000 cli := NewPushClient(cs.configPath, cs.leveldbPath)
1001 cli.log = cs.log
1002 cli.systemImageInfo = siInfoRes
1003 c.Assert(cli.initSession(), IsNil)
1004 cli.unregisterCh = make(chan string, 1)
1005 cli.unregisterCh <- "app1"
1006
1007 ch := make(chan bool, 1)
1008 go cli.doLoop(nopConn, nopClick, nopBcast, nopUcast, nopError, func(appId string) { c.Check(appId, Equals, "app1"); ch <- true })
814 c.Check(takeNextBool(ch), Equals, true)1009 c.Check(takeNextBool(ch), Equals, true)
815}1010}
8161011
@@ -879,7 +1074,7 @@
879 c.Check(cs.log.Captured(), Matches, "(?ms).*Session connected after 42 attempts$")1074 c.Check(cs.log.Captured(), Matches, "(?ms).*Session connected after 42 attempts$")
8801075
881 // * actionsCh to the click handler/url dispatcher1076 // * actionsCh to the click handler/url dispatcher
882 aCh <- notifications.RawActionReply{ActionId: service.ACTION_ID_BROADCAST}1077 aCh <- notifications.RawActionReply{ActionId: ACTION_ID_BROADCAST}
883 tick()1078 tick()
884 uargs := testibus.GetCallArgs(cli.urlDispatcherEndp)1079 uargs := testibus.GetCallArgs(cli.urlDispatcherEndp)
885 c.Assert(uargs, HasLen, 1)1080 c.Assert(uargs, HasLen, 1)
@@ -943,7 +1138,7 @@
943 // so we start,1138 // so we start,
944 err := cli.Start()1139 err := cli.Start()
945 // and it works1140 // and it works
946 c.Check(err, IsNil)1141 c.Assert(err, IsNil)
9471142
948 // and now everthing is better! We have a config,1143 // and now everthing is better! We have a config,
949 c.Check(string(cli.config.Addr), Equals, ":0")1144 c.Check(string(cli.config.Addr), Equals, ":0")
@@ -956,8 +1151,8 @@
956 // and a service,1151 // and a service,
957 c.Check(cli.pushService, NotNil)1152 c.Check(cli.pushService, NotNil)
958 // and everthying us just peachy!1153 // and everthying us just peachy!
959 cli.pushService.Stop() // cleanup1154 cli.pushService.(*service.PushService).Stop() // cleanup
960 cli.postalService.Stop() // cleanup1155 cli.postalService.Stop() // cleanup
961}1156}
9621157
963func (cs *clientSuite) TestStartCanFail(c *C) {1158func (cs *clientSuite) TestStartCanFail(c *C) {
9641159
=== modified file 'client/service/common.go'
--- client/service/common.go 2014-07-01 11:55:30 +0000
+++ client/service/common.go 2014-07-07 14:23:18 +0000
@@ -20,10 +20,13 @@
2020
21import (21import (
22 "errors"22 "errors"
23 "strings"
23 "sync"24 "sync"
2425
25 "launchpad.net/ubuntu-push/bus"26 "launchpad.net/ubuntu-push/bus"
27 "launchpad.net/ubuntu-push/click"
26 "launchpad.net/ubuntu-push/logger"28 "launchpad.net/ubuntu-push/logger"
29 "launchpad.net/ubuntu-push/nih"
27)30)
2831
29type DBusService struct {32type DBusService struct {
@@ -43,10 +46,11 @@
43)46)
4447
45var (48var (
46 NotConfigured = errors.New("not configured")49 ErrNotConfigured = errors.New("not configured")
47 AlreadyStarted = errors.New("already started")50 ErrAlreadyStarted = errors.New("already started")
48 BadArgCount = errors.New("Wrong number of arguments")51 ErrBadArgCount = errors.New("wrong number of arguments")
49 BadArgType = errors.New("Bad argument type")52 ErrBadArgType = errors.New("bad argument type")
53 ErrBadAppId = errors.New("package must be prefix of app id")
50)54)
5155
52// IsRunning() returns whether the service's state is StateRunning56// IsRunning() returns whether the service's state is StateRunning
@@ -61,10 +65,10 @@
61 svc.lock.Lock()65 svc.lock.Lock()
62 defer svc.lock.Unlock()66 defer svc.lock.Unlock()
63 if svc.state != StateUnknown {67 if svc.state != StateUnknown {
64 return AlreadyStarted68 return ErrAlreadyStarted
65 }69 }
66 if svc.Log == nil || svc.Bus == nil {70 if svc.Log == nil || svc.Bus == nil {
67 return NotConfigured71 return ErrNotConfigured
68 }72 }
69 err := svc.Bus.Dial()73 err := svc.Bus.Dial()
70 if err != nil {74 if err != nil {
@@ -97,3 +101,21 @@
97 }101 }
98 svc.state = StateFinished102 svc.state = StateFinished
99}103}
104
105// grabDBusPackageAndAppId() extracts the appId from a dbus-provided
106// []interface{}, and checks it against the package in the last
107// element of the dbus path.
108func grabDBusPackageAndAppId(path string, args []interface{}, numExtra int) (pkgname string, appId string, err error) {
109 if len(args) != 1+numExtra {
110 return "", "", ErrBadArgCount
111 }
112 appId, ok := args[0].(string)
113 if !ok {
114 return "", "", ErrBadArgType
115 }
116 pkgname = string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:])))
117 if !click.AppInPackage(appId, pkgname) {
118 return "", "", ErrBadAppId
119 }
120 return
121}
100122
=== added file 'client/service/common_test.go'
--- client/service/common_test.go 1970-01-01 00:00:00 +0000
+++ client/service/common_test.go 2014-07-07 14:23:18 +0000
@@ -0,0 +1,60 @@
1/*
2 Copyright 2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17package service
18
19import (
20 . "launchpad.net/gocheck"
21)
22
23type commonSuite struct{}
24
25var _ = Suite(&commonSuite{})
26
27func (cs *commonSuite) TestGrabDBusPackageAndAppIdWorks(c *C) {
28 aDBusPath := "/com/ubuntu/Postal/com_2eexample_2etest"
29 aPackage := "com.example.test"
30 anAppId := aPackage + "_test"
31 pkg, app, err := grabDBusPackageAndAppId(aDBusPath, []interface{}{anAppId}, 0)
32 c.Check(err, IsNil)
33 c.Check(pkg, Equals, aPackage)
34 c.Check(app, Equals, anAppId)
35}
36
37func (cs *commonSuite) TestGrabDBusPackageAndAppIdFails(c *C) {
38 aDBusPath := "/com/ubuntu/Postal/com_2eexample_2etest"
39 aPackage := "com.example.test"
40 anAppId := aPackage + "_test"
41
42 for i, s := range []struct {
43 path string
44 args []interface{}
45 numExtra int
46 errt error
47 }{
48 {aDBusPath, []interface{}{}, 0, ErrBadArgCount},
49 {aDBusPath, []interface{}{anAppId}, 1, ErrBadArgCount},
50 {aDBusPath, []interface{}{anAppId, anAppId}, 0, ErrBadArgCount},
51 {aDBusPath, []interface{}{1}, 0, ErrBadArgType},
52 {aDBusPath, []interface{}{aPackage}, 0, ErrBadAppId},
53 } {
54 comment := Commentf("iteration #%d", i)
55 pkg, app, err := grabDBusPackageAndAppId(s.path, s.args, s.numExtra)
56 c.Check(err, Equals, s.errt, comment)
57 c.Check(pkg, Equals, "", comment)
58 c.Check(app, Equals, "", comment)
59 }
60}
061
=== modified file 'client/service/postal.go'
--- client/service/postal.go 2014-07-01 11:55:30 +0000
+++ client/service/postal.go 2014-07-07 14:23:18 +0000
@@ -17,17 +17,19 @@
17package service17package service
1818
19import (19import (
20 "strings"20 "sync"
2121
22 "code.google.com/p/go-uuid/uuid"22 "code.google.com/p/go-uuid/uuid"
23 "launchpad.net/go-dbus/v1"
2423
25 "launchpad.net/ubuntu-push/bus"24 "launchpad.net/ubuntu-push/bus"
25 "launchpad.net/ubuntu-push/bus/emblemcounter"
26 "launchpad.net/ubuntu-push/bus/haptic"
26 "launchpad.net/ubuntu-push/bus/notifications"27 "launchpad.net/ubuntu-push/bus/notifications"
27 "launchpad.net/ubuntu-push/launch_helper"28 "launchpad.net/ubuntu-push/launch_helper"
28 "launchpad.net/ubuntu-push/logger"29 "launchpad.net/ubuntu-push/logger"
29 "launchpad.net/ubuntu-push/messaging"30 "launchpad.net/ubuntu-push/messaging"
30 "launchpad.net/ubuntu-push/nih"31 "launchpad.net/ubuntu-push/nih"
32 "launchpad.net/ubuntu-push/sounds"
31 "launchpad.net/ubuntu-push/util"33 "launchpad.net/ubuntu-push/util"
32)34)
3335
@@ -38,6 +40,8 @@
38 msgHandler func(string, string, *launch_helper.HelperOutput) error40 msgHandler func(string, string, *launch_helper.HelperOutput) error
39 HelperLauncher launch_helper.HelperLauncher41 HelperLauncher launch_helper.HelperLauncher
40 messagingMenu *messaging.MessagingMenu42 messagingMenu *messaging.MessagingMenu
43 emblemcounterEndp bus.Endpoint
44 hapticEndp bus.Endpoint
41 notificationsEndp bus.Endpoint45 notificationsEndp bus.Endpoint
42}46}
4347
@@ -50,19 +54,21 @@
50)54)
5155
52var (56var (
53 SystemUpdateUrl = "settings:///system/system-update"57 SystemUpdateUrl = "settings:///system/system-update"
54 ACTION_ID_SNOWFLAKE = "::ubuntu-push-client::"58 ACTION_ID_PREFIX = "ubuntu-push-client::"
55 ACTION_ID_BROADCAST = ACTION_ID_SNOWFLAKE + SystemUpdateUrl59 ACTION_ID_SUFFIX = "::0"
56)60)
5761
58// NewPostalService() builds a new service and returns it.62// NewPostalService() builds a new service and returns it.
59func NewPostalService(busEndp bus.Endpoint, notificationsEndp bus.Endpoint, log logger.Logger) *PostalService {63func NewPostalService(busEndp bus.Endpoint, notificationsEndp bus.Endpoint, emblemcounterEndp bus.Endpoint, hapticEndp bus.Endpoint, log logger.Logger) *PostalService {
60 var svc = &PostalService{}64 var svc = &PostalService{}
61 svc.Log = log65 svc.Log = log
62 svc.Bus = busEndp66 svc.Bus = busEndp
63 svc.messagingMenu = messaging.New(log)67 svc.messagingMenu = messaging.New(log)
64 svc.HelperLauncher = launch_helper.NewTrivialHelperLauncher(log)68 svc.HelperLauncher = launch_helper.NewTrivialHelperLauncher(log)
65 svc.notificationsEndp = notificationsEndp69 svc.notificationsEndp = notificationsEndp
70 svc.emblemcounterEndp = emblemcounterEndp
71 svc.hapticEndp = hapticEndp
66 svc.msgHandler = svc.messageHandler72 svc.msgHandler = svc.messageHandler
67 return svc73 return svc
68}74}
@@ -84,24 +90,36 @@
84// Start() dials the bus, grab the name, and listens for method calls.90// Start() dials the bus, grab the name, and listens for method calls.
85func (svc *PostalService) Start() error {91func (svc *PostalService) Start() error {
86 return svc.DBusService.Start(bus.DispatchMap{92 return svc.DBusService.Start(bus.DispatchMap{
87 "Notifications": svc.notifications,93 "Messages": svc.notifications,
88 "Inject": svc.inject,94 "Post": svc.inject,
89 }, PostalServiceBusAddress)95 }, PostalServiceBusAddress)
90}96}
9197
92func (svc *PostalService) TakeTheBus() (<-chan notifications.RawActionReply, error) {98func (svc *PostalService) TakeTheBus() (<-chan notifications.RawActionReply, error) {
93 iniCh := make(chan uint32)99 var wg sync.WaitGroup
94 go func() { iniCh <- util.NewAutoRedialer(svc.notificationsEndp).Redial() }()100 endps := []bus.Endpoint{
95 <-iniCh101 svc.notificationsEndp,
102 svc.emblemcounterEndp,
103 svc.hapticEndp,
104 }
105 wg.Add(len(endps))
106 for _, endp := range endps {
107 go func(endp bus.Endpoint) {
108 util.NewAutoRedialer(endp).Redial()
109 wg.Done()
110 }(endp)
111 }
112 wg.Wait()
96 actionsCh, err := notifications.Raw(svc.notificationsEndp, svc.Log).WatchActions()113 actionsCh, err := notifications.Raw(svc.notificationsEndp, svc.Log).WatchActions()
114
97 return actionsCh, err115 return actionsCh, err
98}116}
99117
100func (svc *PostalService) notifications(path string, args, _ []interface{}) ([]interface{}, error) {118func (svc *PostalService) notifications(path string, args, _ []interface{}) ([]interface{}, error) {
101 if len(args) != 0 {119 _, appId, err := grabDBusPackageAndAppId(path, args, 0)
102 return nil, BadArgCount120 if err != nil {
121 return nil, err
103 }122 }
104 appname := string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:])))
105123
106 svc.lock.Lock()124 svc.lock.Lock()
107 defer svc.lock.Unlock()125 defer svc.lock.Unlock()
@@ -109,8 +127,8 @@
109 if svc.mbox == nil {127 if svc.mbox == nil {
110 return []interface{}{[]string(nil)}, nil128 return []interface{}{[]string(nil)}, nil
111 }129 }
112 msgs := svc.mbox[appname]130 msgs := svc.mbox[appId]
113 delete(svc.mbox, appname)131 delete(svc.mbox, appId)
114132
115 return []interface{}{msgs}, nil133 return []interface{}{msgs}, nil
116}134}
@@ -118,23 +136,23 @@
118var newNid = uuid.New136var newNid = uuid.New
119137
120func (svc *PostalService) inject(path string, args, _ []interface{}) ([]interface{}, error) {138func (svc *PostalService) inject(path string, args, _ []interface{}) ([]interface{}, error) {
121 if len(args) != 1 {139 pkg, appId, err := grabDBusPackageAndAppId(path, args, 1)
122 return nil, BadArgCount140 if err != nil {
141 return nil, err
123 }142 }
124 notif, ok := args[0].(string)143 notif, ok := args[1].(string)
125 if !ok {144 if !ok {
126 return nil, BadArgType145 return nil, ErrBadArgType
127 }146 }
128 appname := string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:])))
129147
130 nid := newNid()148 nid := newNid()
131149
132 return nil, svc.Inject(appname, nid, notif)150 return nil, svc.Inject(pkg, appId, nid, notif)
133}151}
134152
135// Inject() signals to an application over dbus that a notification153// Inject() signals to an application over dbus that a notification
136// has arrived.154// has arrived.
137func (svc *PostalService) Inject(appname string, nid string, notif string) error {155func (svc *PostalService) Inject(pkgname string, appname string, nid string, notif string) error {
138 svc.lock.Lock()156 svc.lock.Lock()
139 defer svc.lock.Unlock()157 defer svc.lock.Unlock()
140 if svc.mbox == nil {158 if svc.mbox == nil {
@@ -153,29 +171,28 @@
153 svc.DBusService.Log.Debugf("call to msgHandler successful")171 svc.DBusService.Log.Debugf("call to msgHandler successful")
154 }172 }
155173
156 return svc.Bus.Signal("Notification", "/"+string(nih.Quote([]byte(appname))), []interface{}{appname})174 return svc.Bus.Signal("Post", "/"+string(nih.Quote([]byte(pkgname))), []interface{}{appname})
157}175}
158176
159func (svc *PostalService) messageHandler(appname string, nid string, output *launch_helper.HelperOutput) error {177func (svc *PostalService) messageHandler(appname string, nid string, output *launch_helper.HelperOutput) error {
160 svc.messagingMenu.Present(appname, nid, output.Notification)178 svc.messagingMenu.Present(appname, nid, output.Notification)
161 nots := notifications.Raw(svc.notificationsEndp, svc.Log)179 nots := notifications.Raw(svc.notificationsEndp, svc.Log)
162 _, err := nots.Present(appname, nid, output.Notification)180 _, err := nots.Present(appname, nid, output.Notification)
181 emblemcounter.New(svc.emblemcounterEndp, svc.Log).Present(appname, nid, output.Notification)
182 haptic.New(svc.hapticEndp, svc.Log).Present(appname, nid, output.Notification)
183 sounds.New(svc.Log).Present(appname, nid, output.Notification)
163184
164 return err185 return err
165}186}
166187
167func (svc *PostalService) SendNotification(action_id, icon, summary, body string) (uint32, error) {188func (svc *PostalService) InjectBroadcast() (uint32, error) {
168 a := []string{action_id, "Switch to app"} // action value not visible on the phone189 // XXX: call a helper?
169 h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}}190 // XXX: Present force us to send the url as the notificationId
170 nots := notifications.Raw(svc.notificationsEndp, svc.Log)191 icon := "update_manager_icon"
171 return nots.Notify(192 summary := "There's an updated system image."
172 "ubuntu-push-client", // app name193 body := "Tap to open the system updater."
173 uint32(0), // id194 actions := []string{"Switch to app"} // action value not visible on the phone
174 icon, // icon195 card := &launch_helper.Card{Icon: icon, Summary: summary, Body: body, Actions: actions, Popup: true}
175 summary, // summary196 output := &launch_helper.HelperOutput{[]byte(""), &launch_helper.Notification{Card: card}}
176 body, // body197 return 0, svc.msgHandler("ubuntu-push-client", SystemUpdateUrl, output)
177 a, // actions
178 h, // hints
179 int32(10*1000), // timeout (ms)
180 )
181}198}
182199
=== modified file 'client/service/postal_test.go'
--- client/service/postal_test.go 2014-07-01 11:55:30 +0000
+++ client/service/postal_test.go 2014-07-07 14:23:18 +0000
@@ -18,6 +18,7 @@
1818
19import (19import (
20 "errors"20 "errors"
21 "sort"
2122
22 . "launchpad.net/gocheck"23 . "launchpad.net/gocheck"
2324
@@ -29,9 +30,11 @@
29)30)
3031
31type postalSuite struct {32type postalSuite struct {
32 log *helpers.TestLogger33 log *helpers.TestLogger
33 bus bus.Endpoint34 bus bus.Endpoint
34 notifBus bus.Endpoint35 notifBus bus.Endpoint
36 counterBus bus.Endpoint
37 hapticBus bus.Endpoint
35}38}
3639
37var _ = Suite(&postalSuite{})40var _ = Suite(&postalSuite{})
@@ -40,10 +43,12 @@
40 ss.log = helpers.NewTestLogger(c, "debug")43 ss.log = helpers.NewTestLogger(c, "debug")
41 ss.bus = testibus.NewTestingEndpoint(condition.Work(true), nil)44 ss.bus = testibus.NewTestingEndpoint(condition.Work(true), nil)
42 ss.notifBus = testibus.NewTestingEndpoint(condition.Work(true), nil)45 ss.notifBus = testibus.NewTestingEndpoint(condition.Work(true), nil)
46 ss.counterBus = testibus.NewTestingEndpoint(condition.Work(true), nil)
47 ss.hapticBus = testibus.NewTestingEndpoint(condition.Work(true), nil)
43}48}
4449
45func (ss *postalSuite) TestStart(c *C) {50func (ss *postalSuite) TestStart(c *C) {
46 svc := NewPostalService(ss.bus, ss.notifBus, ss.log)51 svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log)
47 c.Check(svc.IsRunning(), Equals, false)52 c.Check(svc.IsRunning(), Equals, false)
48 c.Check(svc.Start(), IsNil)53 c.Check(svc.Start(), IsNil)
49 c.Check(svc.IsRunning(), Equals, true)54 c.Check(svc.IsRunning(), Equals, true)
@@ -51,45 +56,45 @@
51}56}
5257
53func (ss *postalSuite) TestStartTwice(c *C) {58func (ss *postalSuite) TestStartTwice(c *C) {
54 svc := NewPostalService(ss.bus, ss.notifBus, ss.log)59 svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log)
55 c.Check(svc.Start(), IsNil)60 c.Check(svc.Start(), IsNil)
56 c.Check(svc.Start(), Equals, AlreadyStarted)61 c.Check(svc.Start(), Equals, ErrAlreadyStarted)
57 svc.Stop()62 svc.Stop()
58}63}
5964
60func (ss *postalSuite) TestStartNoLog(c *C) {65func (ss *postalSuite) TestStartNoLog(c *C) {
61 svc := NewPostalService(ss.bus, ss.notifBus, nil)66 svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, nil)
62 c.Check(svc.Start(), Equals, NotConfigured)67 c.Check(svc.Start(), Equals, ErrNotConfigured)
63}68}
6469
65func (ss *postalSuite) TestStartNoBus(c *C) {70func (ss *postalSuite) TestStartNoBus(c *C) {
66 svc := NewPostalService(nil, ss.notifBus, ss.log)71 svc := NewPostalService(nil, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log)
67 c.Check(svc.Start(), Equals, NotConfigured)72 c.Check(svc.Start(), Equals, ErrNotConfigured)
68}73}
6974
70func (ss *postalSuite) TestTakeTheBustFail(c *C) {75func (ss *postalSuite) TestTakeTheBustFail(c *C) {
71 nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(false), []interface{}{uint32(1), "hello"})76 nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(false), []interface{}{uint32(1), "hello"})
72 svc := NewPostalService(ss.bus, nEndp, ss.log)77 svc := NewPostalService(ss.bus, nEndp, ss.counterBus, ss.hapticBus, ss.log)
73 _, err := svc.TakeTheBus()78 _, err := svc.TakeTheBus()
74 c.Check(err, NotNil)79 c.Check(err, NotNil)
75}80}
7681
77func (ss *postalSuite) TestTakeTheBustOk(c *C) {82func (ss *postalSuite) TestTakeTheBustOk(c *C) {
78 nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(true), []interface{}{uint32(1), "hello"})83 nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(true), []interface{}{uint32(1), "hello"})
79 svc := NewPostalService(ss.bus, nEndp, ss.log)84 svc := NewPostalService(ss.bus, nEndp, ss.counterBus, ss.hapticBus, ss.log)
80 _, err := svc.TakeTheBus()85 _, err := svc.TakeTheBus()
81 c.Check(err, IsNil)86 c.Check(err, IsNil)
82}87}
8388
84func (ss *postalSuite) TestStartFailsOnBusDialFailure(c *C) {89func (ss *postalSuite) TestStartFailsOnBusDialFailure(c *C) {
85 bus := testibus.NewTestingEndpoint(condition.Work(false), nil)90 bus := testibus.NewTestingEndpoint(condition.Work(false), nil)
86 svc := NewPostalService(bus, ss.notifBus, ss.log)91 svc := NewPostalService(bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log)
87 c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`)92 c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`)
88 svc.Stop()93 svc.Stop()
89}94}
9095
91func (ss *postalSuite) TestStartGrabsName(c *C) {96func (ss *postalSuite) TestStartGrabsName(c *C) {
92 svc := NewPostalService(ss.bus, ss.notifBus, ss.log)97 svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log)
93 c.Assert(svc.Start(), IsNil)98 c.Assert(svc.Start(), IsNil)
94 callArgs := testibus.GetCallArgs(ss.bus)99 callArgs := testibus.GetCallArgs(ss.bus)
95 defer svc.Stop()100 defer svc.Stop()
@@ -98,7 +103,7 @@
98}103}
99104
100func (ss *postalSuite) TestStopClosesBus(c *C) {105func (ss *postalSuite) TestStopClosesBus(c *C) {
101 svc := NewPostalService(ss.bus, ss.notifBus, ss.log)106 svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log)
102 c.Assert(svc.Start(), IsNil)107 c.Assert(svc.Start(), IsNil)
103 svc.Stop()108 svc.Stop()
104 callArgs := testibus.GetCallArgs(ss.bus)109 callArgs := testibus.GetCallArgs(ss.bus)
@@ -110,31 +115,31 @@
110// Injection tests115// Injection tests
111116
112func (ss *postalSuite) TestInjectWorks(c *C) {117func (ss *postalSuite) TestInjectWorks(c *C) {
113 svc := NewPostalService(ss.bus, ss.notifBus, ss.log)118 svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log)
114 svc.msgHandler = nil119 svc.msgHandler = nil
115 rvs, err := svc.inject("/hello", []interface{}{"world"}, nil)120 rvs, err := svc.inject(aPackageOnBus, []interface{}{anAppId, "world"}, nil)
116 c.Assert(err, IsNil)121 c.Assert(err, IsNil)
117 c.Check(rvs, IsNil)122 c.Check(rvs, IsNil)
118 rvs, err = svc.inject("/hello", []interface{}{"there"}, nil)123 rvs, err = svc.inject(aPackageOnBus, []interface{}{anAppId, "there"}, nil)
119 c.Assert(err, IsNil)124 c.Assert(err, IsNil)
120 c.Check(rvs, IsNil)125 c.Check(rvs, IsNil)
121 c.Assert(svc.mbox, HasLen, 1)126 c.Assert(svc.mbox, HasLen, 1)
122 c.Assert(svc.mbox["hello"], HasLen, 2)127 c.Assert(svc.mbox[anAppId], HasLen, 2)
123 c.Check(svc.mbox["hello"][0], Equals, "world")128 c.Check(svc.mbox[anAppId][0], Equals, "world")
124 c.Check(svc.mbox["hello"][1], Equals, "there")129 c.Check(svc.mbox[anAppId][1], Equals, "there")
125130
126 // and check it fired the right signal (twice)131 // and check it fired the right signal (twice)
127 callArgs := testibus.GetCallArgs(ss.bus)132 callArgs := testibus.GetCallArgs(ss.bus)
128 c.Assert(callArgs, HasLen, 2)133 c.Assert(callArgs, HasLen, 2)
129 c.Check(callArgs[0].Member, Equals, "::Signal")134 c.Check(callArgs[0].Member, Equals, "::Signal")
130 c.Check(callArgs[0].Args, DeepEquals, []interface{}{"Notification", "/hello", []interface{}{"hello"}})135 c.Check(callArgs[0].Args, DeepEquals, []interface{}{"Post", aPackageOnBus, []interface{}{anAppId}})
131 c.Check(callArgs[1], DeepEquals, callArgs[0])136 c.Check(callArgs[1], DeepEquals, callArgs[0])
132}137}
133138
134func (ss *postalSuite) TestInjectFailsIfInjectFails(c *C) {139func (ss *postalSuite) TestInjectFailsIfInjectFails(c *C) {
135 bus := testibus.NewTestingEndpoint(condition.Work(true),140 bus := testibus.NewTestingEndpoint(condition.Work(true),
136 condition.Work(false))141 condition.Work(false))
137 svc := NewPostalService(bus, ss.notifBus, ss.log)142 svc := NewPostalService(bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log)
138 svc.SetMessageHandler(func(string, string, *launch_helper.HelperOutput) error { return errors.New("fail") })143 svc.SetMessageHandler(func(string, string, *launch_helper.HelperOutput) error { return errors.New("fail") })
139 _, err := svc.inject("/hello", []interface{}{"xyzzy"}, nil)144 _, err := svc.inject("/hello", []interface{}{"xyzzy"}, nil)
140 c.Check(err, NotNil)145 c.Check(err, NotNil)
@@ -145,22 +150,57 @@
145 args []interface{}150 args []interface{}
146 errt error151 errt error
147 }{152 }{
148 {nil, BadArgCount},153 {nil, ErrBadArgCount},
149 {[]interface{}{}, BadArgCount},154 {[]interface{}{}, ErrBadArgCount},
150 {[]interface{}{1}, BadArgType},155 {[]interface{}{1}, ErrBadArgCount},
151 {[]interface{}{1, 2}, BadArgCount},156 {[]interface{}{anAppId, 1}, ErrBadArgType},
157 {[]interface{}{1, "hello"}, ErrBadArgType},
158 {[]interface{}{1, 2, 3}, ErrBadArgCount},
159 {[]interface{}{"bar", "hello"}, ErrBadAppId},
152 } {160 } {
153 reg, err := new(PostalService).inject("", s.args, nil)161 reg, err := new(PostalService).inject(aPackageOnBus, s.args, nil)
154 c.Check(reg, IsNil, Commentf("iteration #%d", i))162 c.Check(reg, IsNil, Commentf("iteration #%d", i))
155 c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))163 c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
156 }164 }
157}165}
158166
159//167//
168// Injection (Broadcast) tests
169
170func (ss *postalSuite) TestInjectBroadcast(c *C) {
171 bus := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
172 svc := NewPostalService(ss.bus, bus, ss.counterBus, ss.hapticBus, ss.log)
173 //svc.msgHandler = nil
174 rvs, err := svc.InjectBroadcast()
175 c.Assert(err, IsNil)
176 c.Check(rvs, Equals, uint32(0))
177 c.Assert(err, IsNil)
178 // and check it fired the right signal (twice)
179 callArgs := testibus.GetCallArgs(bus)
180 c.Assert(callArgs, HasLen, 1)
181 c.Check(callArgs[0].Member, Equals, "Notify")
182 c.Check(callArgs[0].Args[0:6], DeepEquals, []interface{}{"ubuntu-push-client", uint32(0), "update_manager_icon",
183 "There's an updated system image.", "Tap to open the system updater.",
184 []string{"ubuntu-push-client::settings:///system/system-update::0", "Switch to app"}})
185 // TODO: check the map in callArgs?
186 // c.Check(callArgs[0].Args[7]["x-canonical-secondary-icon"], NotNil)
187 // c.Check(callArgs[0].Args[7]["x-canonical-snap-decisions"], NotNil)
188}
189
190func (ss *postalSuite) TestInjectBroadcastFails(c *C) {
191 bus := testibus.NewTestingEndpoint(condition.Work(true),
192 condition.Work(false))
193 svc := NewPostalService(ss.bus, bus, ss.counterBus, ss.hapticBus, ss.log)
194 svc.SetMessageHandler(func(string, string, *launch_helper.HelperOutput) error { return errors.New("fail") })
195 _, err := svc.InjectBroadcast()
196 c.Check(err, NotNil)
197}
198
199//
160// Notifications tests200// Notifications tests
161func (ss *postalSuite) TestNotificationsWorks(c *C) {201func (ss *postalSuite) TestNotificationsWorks(c *C) {
162 svc := NewPostalService(ss.bus, ss.notifBus, ss.log)202 svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log)
163 nots, err := svc.notifications("/hello", nil, nil)203 nots, err := svc.notifications(aPackageOnBus, []interface{}{anAppId}, nil)
164 c.Assert(err, IsNil)204 c.Assert(err, IsNil)
165 c.Assert(nots, NotNil)205 c.Assert(nots, NotNil)
166 c.Assert(nots, HasLen, 1)206 c.Assert(nots, HasLen, 1)
@@ -168,8 +208,8 @@
168 if svc.mbox == nil {208 if svc.mbox == nil {
169 svc.mbox = make(map[string][]string)209 svc.mbox = make(map[string][]string)
170 }210 }
171 svc.mbox["hello"] = append(svc.mbox["hello"], "this", "thing")211 svc.mbox[anAppId] = append(svc.mbox[anAppId], "this", "thing")
172 nots, err = svc.notifications("/hello", nil, nil)212 nots, err = svc.notifications(aPackageOnBus, []interface{}{anAppId}, nil)
173 c.Assert(err, IsNil)213 c.Assert(err, IsNil)
174 c.Assert(nots, NotNil)214 c.Assert(nots, NotNil)
175 c.Assert(nots, HasLen, 1)215 c.Assert(nots, HasLen, 1)
@@ -177,9 +217,19 @@
177}217}
178218
179func (ss *postalSuite) TestNotificationsFailsIfBadArgs(c *C) {219func (ss *postalSuite) TestNotificationsFailsIfBadArgs(c *C) {
180 reg, err := new(PostalService).notifications("/foo", []interface{}{1}, nil)220 for i, s := range []struct {
181 c.Check(reg, IsNil)221 args []interface{}
182 c.Check(err, Equals, BadArgCount)222 errt error
223 }{
224 {nil, ErrBadArgCount},
225 {[]interface{}{}, ErrBadArgCount},
226 {[]interface{}{1}, ErrBadArgType},
227 {[]interface{}{"potato"}, ErrBadAppId},
228 } {
229 reg, err := new(PostalService).notifications(aPackageOnBus, s.args, nil)
230 c.Check(reg, IsNil, Commentf("iteration #%d", i))
231 c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
232 }
183}233}
184234
185func (ss *postalSuite) TestMessageHandlerPublicAPI(c *C) {235func (ss *postalSuite) TestMessageHandlerPublicAPI(c *C) {
@@ -198,35 +248,44 @@
198248
199func (ss *postalSuite) TestInjectCallsMessageHandler(c *C) {249func (ss *postalSuite) TestInjectCallsMessageHandler(c *C) {
200 var ext = &launch_helper.HelperOutput{}250 var ext = &launch_helper.HelperOutput{}
201 svc := NewPostalService(ss.bus, ss.notifBus, ss.log)251 svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log)
202 f := func(app string, nid string, s *launch_helper.HelperOutput) error { ext = s; return nil }252 f := func(app string, nid string, s *launch_helper.HelperOutput) error { ext = s; return nil }
203 svc.SetMessageHandler(f)253 svc.SetMessageHandler(f)
204 c.Check(svc.Inject("stuff", "thing", "{}"), IsNil)254 c.Check(svc.Inject("pkg", "app", "thing", "{}"), IsNil)
205 c.Check(ext, DeepEquals, &launch_helper.HelperOutput{})255 c.Check(ext, DeepEquals, &launch_helper.HelperOutput{})
206 err := errors.New("ouch")256 err := errors.New("ouch")
207 svc.SetMessageHandler(func(string, string, *launch_helper.HelperOutput) error { return err })257 svc.SetMessageHandler(func(string, string, *launch_helper.HelperOutput) error { return err })
208 c.Check(svc.Inject("stuff", "", "{}"), Equals, err)258 c.Check(svc.Inject("pkg", "app", "", "{}"), Equals, err)
209}259}
210260
211func (ss *postalSuite) TestMessageHandler(c *C) {261func (ss *postalSuite) TestMessageHandlerPresents(c *C) {
212 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))262 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
213 svc := NewPostalService(ss.bus, endp, ss.log)263 svc := NewPostalService(endp, endp, endp, endp, ss.log)
214 card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true}264 // Persist is false so we just check the log
215 output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card}}265 card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true, Persist: false}
216 err := svc.messageHandler("xyzzy", "", output)266 vib := &launch_helper.Vibration{Duration: 500}
267 emb := &launch_helper.EmblemCounter{Count: 2, Visible: true}
268 output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card, EmblemCounter: emb, Vibrate: vib}}
269 err := svc.messageHandler("com.example.test_test", "", output)
217 c.Assert(err, IsNil)270 c.Assert(err, IsNil)
218 args := testibus.GetCallArgs(endp)271 args := testibus.GetCallArgs(endp)
219 c.Assert(args, HasLen, 1)272 c.Assert(args, HasLen, 4)
220 c.Check(args[0].Member, Equals, "Notify")273 mm := make([]string, len(args))
221 c.Check(args[0].Args[0], Equals, "xyzzy")274 for i, m := range args {
222 c.Check(args[0].Args[2], Equals, "icon-value")275 mm[i] = m.Member
223 c.Check(args[0].Args[3], Equals, "summary-value")276 }
224 c.Check(args[0].Args[4], Equals, "body-value")277 sort.Strings(mm)
278 // check the Present() methods were called.
279 // For dbus-backed presenters, just check the right dbus methods are called
280 c.Check(mm, DeepEquals, []string{"::SetProperty", "::SetProperty", "Notify", "VibratePattern"})
281 // For the other ones, check the logs
282 c.Check(ss.log.Captured(), Matches, `(?sm).* no persistable card:.*`)
283 c.Check(ss.log.Captured(), Matches, `(?sm).* no Sound in the notification.*`)
225}284}
226285
227func (ss *postalSuite) TestMessageHandlerReportsFailedNotifies(c *C) {286func (ss *postalSuite) TestMessageHandlerReportsFailedNotifies(c *C) {
228 endp := testibus.NewTestingEndpoint(nil, condition.Work(false))287 endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
229 svc := NewPostalService(ss.bus, endp, ss.log)288 svc := NewPostalService(ss.bus, endp, ss.counterBus, ss.hapticBus, ss.log)
230 card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true}289 card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true}
231 notif := &launch_helper.Notification{Card: card}290 notif := &launch_helper.Notification{Card: card}
232 output := &launch_helper.HelperOutput{Notification: notif}291 output := &launch_helper.HelperOutput{Notification: notif}
@@ -235,7 +294,7 @@
235}294}
236295
237func (ss *postalSuite) TestMessageHandlerReportsButIgnoresUnmarshalErrors(c *C) {296func (ss *postalSuite) TestMessageHandlerReportsButIgnoresUnmarshalErrors(c *C) {
238 svc := NewPostalService(ss.bus, ss.notifBus, ss.log)297 svc := NewPostalService(ss.bus, ss.notifBus, ss.counterBus, ss.hapticBus, ss.log)
239 output := &launch_helper.HelperOutput{[]byte(`broken`), nil}298 output := &launch_helper.HelperOutput{[]byte(`broken`), nil}
240 err := svc.messageHandler("", "", output)299 err := svc.messageHandler("", "", output)
241 c.Check(err, IsNil)300 c.Check(err, IsNil)
@@ -244,7 +303,7 @@
244303
245func (ss *postalSuite) TestMessageHandlerReportsButIgnoresNilNotifies(c *C) {304func (ss *postalSuite) TestMessageHandlerReportsButIgnoresNilNotifies(c *C) {
246 endp := testibus.NewTestingEndpoint(nil, condition.Work(false))305 endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
247 svc := NewPostalService(ss.bus, endp, ss.log)306 svc := NewPostalService(ss.bus, endp, ss.counterBus, ss.hapticBus, ss.log)
248 output := &launch_helper.HelperOutput{[]byte(`{}`), nil}307 output := &launch_helper.HelperOutput{[]byte(`{}`), nil}
249 err := svc.messageHandler("", "", output)308 err := svc.messageHandler("", "", output)
250 c.Assert(err, IsNil)309 c.Assert(err, IsNil)
251310
=== modified file 'client/service/service.go'
--- client/service/service.go 2014-07-01 11:55:30 +0000
+++ client/service/service.go 2014-07-07 14:23:18 +0000
@@ -23,8 +23,8 @@
23 "fmt"23 "fmt"
24 "io/ioutil"24 "io/ioutil"
25 "net/http"25 "net/http"
26 "net/url"
26 "os"27 "os"
27 "strings"
2828
29 "launchpad.net/ubuntu-push/bus"29 "launchpad.net/ubuntu-push/bus"
30 http13 "launchpad.net/ubuntu-push/http13client"30 http13 "launchpad.net/ubuntu-push/http13client"
@@ -32,10 +32,17 @@
32 "launchpad.net/ubuntu-push/nih"32 "launchpad.net/ubuntu-push/nih"
33)33)
3434
35// PushServiceSetup encapsulates the params for setting up a PushService.
36type PushServiceSetup struct {
37 RegURL *url.URL
38 DeviceId string
39 AuthGetter func(string) string
40}
41
35// PushService is the dbus api42// PushService is the dbus api
36type PushService struct {43type PushService struct {
37 DBusService44 DBusService
38 regURL string45 regURL *url.URL
39 deviceId string46 deviceId string
40 authGetter func(string) string47 authGetter func(string) string
41 httpCli http13.Client48 httpCli http13.Client
@@ -50,72 +57,42 @@
50)57)
5158
52// NewPushService() builds a new service and returns it.59// NewPushService() builds a new service and returns it.
53func NewPushService(bus bus.Endpoint, log logger.Logger) *PushService {60func NewPushService(bus bus.Endpoint, setup *PushServiceSetup, log logger.Logger) *PushService {
54 var svc = &PushService{}61 var svc = &PushService{}
55 svc.Log = log62 svc.Log = log
56 svc.Bus = bus63 svc.Bus = bus
64 svc.regURL = setup.RegURL
65 svc.deviceId = setup.DeviceId
66 svc.authGetter = setup.AuthGetter
57 return svc67 return svc
58}68}
5969
60// SetRegistrationURL() sets the registration url for the service70// getAuthorization() returns the URL and the authorization header for
61func (svc *PushService) SetRegistrationURL(url string) {71// POSTing to the registration HTTP endpoint for op
62 svc.lock.Lock()72func (svc *PushService) getAuthorization(op string) (string, string) {
63 defer svc.lock.Unlock()73 if svc.authGetter == nil || svc.regURL == nil {
64 svc.regURL = url74 return "", ""
65}75 }
6676 purl, err := svc.regURL.Parse(op)
67// SetAuthGetter() sets the authorization getter for the service77 if err != nil {
68func (svc *PushService) SetAuthGetter(authGetter func(string) string) {78 panic("op to getAuthorization was invalid")
69 svc.lock.Lock()79 }
70 defer svc.lock.Unlock()80 url := purl.String()
71 svc.authGetter = authGetter81 return url, svc.authGetter(url)
72}
73
74// getRegistrationAuthorization() returns the authorization header for
75// POSTing to the registration HTTP endpoint
76//
77// (this is for calling with the lock held)
78func (svc *PushService) getRegistrationAuthorization() string {
79 if svc.authGetter != nil && svc.regURL != "" {
80 return svc.authGetter(svc.regURL)
81 } else {
82 return ""
83 }
84}
85
86// GetRegistrationAuthorization() returns the authorization header for
87// POSTing to the registration HTTP endpoint
88func (svc *PushService) GetRegistrationAuthorization() string {
89 svc.lock.RLock()
90 defer svc.lock.RUnlock()
91 return svc.getRegistrationAuthorization()
92}
93
94// SetDeviceId() sets the device id
95func (svc *PushService) SetDeviceId(deviceId string) {
96 svc.lock.Lock()
97 defer svc.lock.Unlock()
98 svc.deviceId = deviceId
99}
100
101// GetDeviceId() returns the device id
102func (svc *PushService) GetDeviceId() string {
103 svc.lock.RLock()
104 defer svc.lock.RUnlock()
105 return svc.deviceId
106}82}
10783
108func (svc *PushService) Start() error {84func (svc *PushService) Start() error {
109 return svc.DBusService.Start(bus.DispatchMap{85 return svc.DBusService.Start(bus.DispatchMap{
110 "Register": svc.register,86 "Register": svc.register,
87 "Unregister": svc.unregister,
111 }, PushServiceBusAddress)88 }, PushServiceBusAddress)
112}89}
11390
114var (91var (
115 BadServer = errors.New("Bad server")92 ErrBadServer = errors.New("bad server")
116 BadRequest = errors.New("Bad request")93 ErrBadRequest = errors.New("bad request")
117 BadToken = errors.New("Bad token")94 ErrBadToken = errors.New("bad token")
118 BadAuth = errors.New("Bad auth")95 ErrBadAuth = errors.New("bad auth")
119)96)
12097
121type registrationRequest struct {98type registrationRequest struct {
@@ -130,31 +107,20 @@
130 Message string `json:"message"` //107 Message string `json:"message"` //
131}108}
132109
133func (svc *PushService) register(path string, args, _ []interface{}) ([]interface{}, error) {110func (svc *PushService) manageReg(op, appId string) (*registrationReply, error) {
134 svc.lock.RLock()111 req_body, err := json.Marshal(registrationRequest{svc.deviceId, appId})
135 defer svc.lock.RUnlock()
136 if len(args) != 0 {
137 return nil, BadArgCount
138 }
139 raw_appname := path[strings.LastIndex(path, "/")+1:]
140 appname := string(nih.Unquote([]byte(raw_appname)))
141
142 rv := os.Getenv("PUSH_REG_" + raw_appname)
143 if rv != "" {
144 return []interface{}{rv}, nil
145 }
146
147 req_body, err := json.Marshal(registrationRequest{svc.deviceId, appname})
148 if err != nil {112 if err != nil {
149 return nil, fmt.Errorf("unable to marshal register request body: %v", err)113 return nil, fmt.Errorf("unable to marshal register request body: %v", err)
150 }114 }
151 req, err := http13.NewRequest("POST", svc.regURL, bytes.NewReader(req_body))115
152 if err != nil {116 url, auth := svc.getAuthorization(op)
153 return nil, fmt.Errorf("unable to build register request: %v", err)
154 }
155 auth := svc.getRegistrationAuthorization()
156 if auth == "" {117 if auth == "" {
157 return nil, BadAuth118 return nil, ErrBadAuth
119 }
120
121 req, err := http13.NewRequest("POST", url, bytes.NewReader(req_body))
122 if err != nil {
123 panic(fmt.Errorf("unable to build register request: %v", err))
158 }124 }
159 req.Header.Add("Authorization", auth)125 req.Header.Add("Authorization", auth)
160 req.Header.Add("Content-Type", "application/json")126 req.Header.Add("Content-Type", "application/json")
@@ -169,9 +135,9 @@
169 switch {135 switch {
170 case resp.StatusCode >= http.StatusInternalServerError:136 case resp.StatusCode >= http.StatusInternalServerError:
171 // XXX retry on 503137 // XXX retry on 503
172 return nil, BadServer138 return nil, ErrBadServer
173 default:139 default:
174 return nil, BadRequest140 return nil, ErrBadRequest
175 }141 }
176 }142 }
177 // errors below here Can't Happen (tm).143 // errors below here Can't Happen (tm).
@@ -188,10 +154,44 @@
188 return nil, fmt.Errorf("unable to unmarshal register response: %v", err)154 return nil, fmt.Errorf("unable to unmarshal register response: %v", err)
189 }155 }
190156
157 return &reply, nil
158}
159
160func (svc *PushService) register(path string, args, _ []interface{}) ([]interface{}, error) {
161 _, appId, err := grabDBusPackageAndAppId(path, args, 0)
162 if err != nil {
163 return nil, err
164 }
165
166 rawAppId := string(nih.Quote([]byte(appId)))
167 rv := os.Getenv("PUSH_REG_" + rawAppId)
168 if rv != "" {
169 return []interface{}{rv}, nil
170 }
171
172 reply, err := svc.manageReg("/register", appId)
173 if err != nil {
174 return nil, err
175 }
176
191 if !reply.Ok || reply.Token == "" {177 if !reply.Ok || reply.Token == "" {
192 svc.Log.Errorf("Unexpected response: %#v", reply)178 svc.Log.Errorf("Unexpected response: %#v", reply)
193 return nil, BadToken179 return nil, ErrBadToken
194 }180 }
195181
196 return []interface{}{reply.Token}, nil182 return []interface{}{reply.Token}, nil
197}183}
184
185func (svc *PushService) unregister(path string, args, _ []interface{}) ([]interface{}, error) {
186 _, appId, err := grabDBusPackageAndAppId(path, args, 0)
187 if err != nil {
188 return nil, err
189 }
190
191 return nil, svc.Unregister(appId)
192}
193
194func (svc *PushService) Unregister(appId string) error {
195 _, err := svc.manageReg("/unregister", appId)
196 return err
197}
198198
=== modified file 'client/service/service_test.go'
--- client/service/service_test.go 2014-06-20 12:33:03 +0000
+++ client/service/service_test.go 2014-07-07 14:23:18 +0000
@@ -29,6 +29,7 @@
29 "launchpad.net/ubuntu-push/bus"29 "launchpad.net/ubuntu-push/bus"
30 testibus "launchpad.net/ubuntu-push/bus/testing"30 testibus "launchpad.net/ubuntu-push/bus/testing"
31 "launchpad.net/ubuntu-push/logger"31 "launchpad.net/ubuntu-push/logger"
32 "launchpad.net/ubuntu-push/nih"
32 helpers "launchpad.net/ubuntu-push/testing"33 helpers "launchpad.net/ubuntu-push/testing"
33 "launchpad.net/ubuntu-push/testing/condition"34 "launchpad.net/ubuntu-push/testing/condition"
34)35)
@@ -42,13 +43,36 @@
4243
43var _ = Suite(&serviceSuite{})44var _ = Suite(&serviceSuite{})
4445
46var (
47 aPackage = "com.example.test"
48 anAppId = aPackage + "_test-number-one"
49 aPackageOnBus = "/" + string(nih.Quote([]byte(aPackage)))
50)
51
45func (ss *serviceSuite) SetUpTest(c *C) {52func (ss *serviceSuite) SetUpTest(c *C) {
46 ss.log = helpers.NewTestLogger(c, "debug")53 ss.log = helpers.NewTestLogger(c, "debug")
47 ss.bus = testibus.NewTestingEndpoint(condition.Work(true), nil)54 ss.bus = testibus.NewTestingEndpoint(condition.Work(true), nil)
48}55}
4956
57var testSetup = &PushServiceSetup{}
58
59func (ss *serviceSuite) TestBuild(c *C) {
60 setup := &PushServiceSetup{
61 RegURL: helpers.ParseURL("http://reg"),
62 DeviceId: "FOO",
63 AuthGetter: func(s string) string {
64 return ""
65 },
66 }
67 svc := NewPushService(ss.bus, setup, ss.log)
68 c.Check(svc.Bus, Equals, ss.bus)
69 c.Check(svc.regURL, DeepEquals, helpers.ParseURL("http://reg"))
70 c.Check(fmt.Sprintf("%#v", svc.authGetter), Equals, fmt.Sprintf("%#v", setup.AuthGetter))
71 // ...
72}
73
50func (ss *serviceSuite) TestStart(c *C) {74func (ss *serviceSuite) TestStart(c *C) {
51 svc := NewPushService(ss.bus, ss.log)75 svc := NewPushService(ss.bus, testSetup, ss.log)
52 c.Check(svc.IsRunning(), Equals, false)76 c.Check(svc.IsRunning(), Equals, false)
53 c.Check(svc.Start(), IsNil)77 c.Check(svc.Start(), IsNil)
54 c.Check(svc.IsRunning(), Equals, true)78 c.Check(svc.IsRunning(), Equals, true)
@@ -56,31 +80,31 @@
56}80}
5781
58func (ss *serviceSuite) TestStartTwice(c *C) {82func (ss *serviceSuite) TestStartTwice(c *C) {
59 svc := NewPushService(ss.bus, ss.log)83 svc := NewPushService(ss.bus, testSetup, ss.log)
60 c.Check(svc.Start(), IsNil)84 c.Check(svc.Start(), IsNil)
61 c.Check(svc.Start(), Equals, AlreadyStarted)85 c.Check(svc.Start(), Equals, ErrAlreadyStarted)
62 svc.Stop()86 svc.Stop()
63}87}
6488
65func (ss *serviceSuite) TestStartNoLog(c *C) {89func (ss *serviceSuite) TestStartNoLog(c *C) {
66 svc := NewPushService(ss.bus, nil)90 svc := NewPushService(ss.bus, testSetup, nil)
67 c.Check(svc.Start(), Equals, NotConfigured)91 c.Check(svc.Start(), Equals, ErrNotConfigured)
68}92}
6993
70func (ss *serviceSuite) TestStartNoBus(c *C) {94func (ss *serviceSuite) TestStartNoBus(c *C) {
71 svc := NewPushService(nil, ss.log)95 svc := NewPushService(nil, testSetup, ss.log)
72 c.Check(svc.Start(), Equals, NotConfigured)96 c.Check(svc.Start(), Equals, ErrNotConfigured)
73}97}
7498
75func (ss *serviceSuite) TestStartFailsOnBusDialFailure(c *C) {99func (ss *serviceSuite) TestStartFailsOnBusDialFailure(c *C) {
76 bus := testibus.NewTestingEndpoint(condition.Work(false), nil)100 bus := testibus.NewTestingEndpoint(condition.Work(false), nil)
77 svc := NewPushService(bus, ss.log)101 svc := NewPushService(bus, testSetup, ss.log)
78 c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`)102 c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`)
79 svc.Stop()103 svc.Stop()
80}104}
81105
82func (ss *serviceSuite) TestStartGrabsName(c *C) {106func (ss *serviceSuite) TestStartGrabsName(c *C) {
83 svc := NewPushService(ss.bus, ss.log)107 svc := NewPushService(ss.bus, testSetup, ss.log)
84 c.Assert(svc.Start(), IsNil)108 c.Assert(svc.Start(), IsNil)
85 callArgs := testibus.GetCallArgs(ss.bus)109 callArgs := testibus.GetCallArgs(ss.bus)
86 defer svc.Stop()110 defer svc.Stop()
@@ -89,7 +113,7 @@
89}113}
90114
91func (ss *serviceSuite) TestStopClosesBus(c *C) {115func (ss *serviceSuite) TestStopClosesBus(c *C) {
92 svc := NewPushService(ss.bus, ss.log)116 svc := NewPushService(ss.bus, testSetup, ss.log)
93 c.Assert(svc.Start(), IsNil)117 c.Assert(svc.Start(), IsNil)
94 svc.Stop()118 svc.Stop()
95 callArgs := testibus.GetCallArgs(ss.bus)119 callArgs := testibus.GetCallArgs(ss.bus)
@@ -99,41 +123,48 @@
99123
100// registration tests124// registration tests
101125
102func (ss *serviceSuite) TestSetRegURLWorks(c *C) {
103 svc := NewPushService(ss.bus, ss.log)
104 c.Check(svc.regURL, Equals, "")
105 svc.SetRegistrationURL("xyzzy://")
106 c.Check(svc.regURL, Equals, "xyzzy://")
107}
108
109func (ss *serviceSuite) TestSetAuthGetterWorks(c *C) {
110 svc := NewPushService(ss.bus, ss.log)
111 c.Check(svc.authGetter, IsNil)
112 f := func(string) string { return "" }
113 svc.SetAuthGetter(f)
114 c.Check(fmt.Sprintf("%#v", svc.authGetter), Equals, fmt.Sprintf("%#v", f))
115}
116
117func (ss *serviceSuite) TestGetRegAuthWorks(c *C) {126func (ss *serviceSuite) TestGetRegAuthWorks(c *C) {
118 svc := NewPushService(ss.bus, ss.log)
119 svc.SetRegistrationURL("xyzzy://")
120 ch := make(chan string, 1)127 ch := make(chan string, 1)
121 f := func(s string) string { ch <- s; return "Auth " + s }128 setup := &PushServiceSetup{
122 svc.SetAuthGetter(f)129 RegURL: helpers.ParseURL("http://foo"),
123 c.Check(svc.getRegistrationAuthorization(), Equals, "Auth xyzzy://")130 AuthGetter: func(s string) string {
131 ch <- s
132 return "Auth " + s
133 },
134 }
135 svc := NewPushService(ss.bus, setup, ss.log)
136 url, auth := svc.getAuthorization("/op")
137 c.Check(auth, Equals, "Auth http://foo/op")
124 c.Assert(len(ch), Equals, 1)138 c.Assert(len(ch), Equals, 1)
125 c.Check(<-ch, Equals, "xyzzy://")139 c.Check(<-ch, Equals, "http://foo/op")
140 c.Check(url, Equals, "http://foo/op")
126}141}
127142
128func (ss *serviceSuite) TestGetRegAuthDoesNotPanic(c *C) {143func (ss *serviceSuite) TestGetRegAuthDoesNotPanic(c *C) {
129 svc := NewPushService(ss.bus, ss.log)144 svc := NewPushService(ss.bus, testSetup, ss.log)
130 c.Check(svc.getRegistrationAuthorization(), Equals, "")145 _, auth := svc.getAuthorization("/op")
146 c.Check(auth, Equals, "")
131}147}
132148
133func (ss *serviceSuite) TestRegistrationFailsIfBadArgs(c *C) {149func (ss *serviceSuite) TestRegistrationAndUnregistrationFailIfBadArgs(c *C) {
134 reg, err := new(PushService).register("", []interface{}{1}, nil)150 for i, s := range []struct {
135 c.Check(reg, IsNil)151 args []interface{}
136 c.Check(err, Equals, BadArgCount)152 errt error
153 }{
154 {nil, ErrBadArgCount},
155 {[]interface{}{}, ErrBadArgCount},
156 {[]interface{}{1}, ErrBadArgType},
157 {[]interface{}{"foo"}, ErrBadAppId},
158 {[]interface{}{"foo", "bar"}, ErrBadArgCount},
159 } {
160 reg, err := new(PushService).register("/bar", s.args, nil)
161 c.Check(reg, IsNil, Commentf("iteration #%d", i))
162 c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
163
164 reg, err = new(PushService).unregister("/bar", s.args, nil)
165 c.Check(reg, IsNil, Commentf("iteration #%d", i))
166 c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
167 }
137}168}
138169
139func (ss *serviceSuite) TestRegistrationWorks(c *C) {170func (ss *serviceSuite) TestRegistrationWorks(c *C) {
@@ -143,19 +174,20 @@
143 c.Assert(e, IsNil)174 c.Assert(e, IsNil)
144 req := registrationRequest{}175 req := registrationRequest{}
145 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)176 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
146 c.Check(req, DeepEquals, registrationRequest{"fake-device-id", "an-app-id"})177 c.Check(req, DeepEquals, registrationRequest{"fake-device-id", anAppId})
147178 c.Check(r.URL.Path, Equals, "/register")
148 w.Header().Set("Content-Type", "application/json")179 w.Header().Set("Content-Type", "application/json")
149 fmt.Fprintln(w, `{"ok":true,"token":"blob-of-bytes"}`)180 fmt.Fprintln(w, `{"ok":true,"token":"blob-of-bytes"}`)
150 }))181 }))
151 defer ts.Close()182 defer ts.Close()
152183 setup := &PushServiceSetup{
153 svc := NewPushService(ss.bus, ss.log)184 DeviceId: "fake-device-id",
154 svc.SetAuthGetter(func(string) string { return "tok" })185 RegURL: helpers.ParseURL(ts.URL),
155 svc.SetRegistrationURL(ts.URL)186 AuthGetter: func(string) string { return "tok" },
156 svc.SetDeviceId("fake-device-id")187 }
188 svc := NewPushService(ss.bus, setup, ss.log)
157 // this'll check (un)quoting, too189 // this'll check (un)quoting, too
158 reg, err := svc.register("/an_2dapp_2did", nil, nil)190 reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil)
159 c.Assert(err, IsNil)191 c.Assert(err, IsNil)
160 c.Assert(reg, HasLen, 1)192 c.Assert(reg, HasLen, 1)
161 regs, ok := reg[0].(string)193 regs, ok := reg[0].(string)
@@ -164,10 +196,11 @@
164}196}
165197
166func (ss *serviceSuite) TestRegistrationOverrideWorks(c *C) {198func (ss *serviceSuite) TestRegistrationOverrideWorks(c *C) {
167 os.Setenv("PUSH_REG_stuff", "42")199 envar := "PUSH_REG_" + string(nih.Quote([]byte(anAppId)))
168 defer os.Setenv("PUSH_REG_stuff", "")200 os.Setenv(envar, "42")
201 defer os.Setenv(envar, "")
169202
170 reg, err := new(PushService).register("/stuff", nil, nil)203 reg, err := new(PushService).register(aPackageOnBus, []interface{}{anAppId}, nil)
171 c.Assert(reg, HasLen, 1)204 c.Assert(reg, HasLen, 1)
172 regs, ok := reg[0].(string)205 regs, ok := reg[0].(string)
173 c.Check(ok, Equals, true)206 c.Check(ok, Equals, true)
@@ -175,103 +208,155 @@
175 c.Check(err, IsNil)208 c.Check(err, IsNil)
176}209}
177210
178func (ss *serviceSuite) TestRegistrationFailsOnBadReqURL(c *C) {211func (ss *serviceSuite) TestManageRegFailsOnBadAuth(c *C) {
179 svc := NewPushService(ss.bus, ss.log)
180 svc.SetRegistrationURL("%gh")
181 reg, err := svc.register("thing", nil, nil)
182 c.Check(reg, IsNil)
183 c.Check(err, ErrorMatches, "unable to build register request: .*")
184}
185
186func (ss *serviceSuite) TestRegistrationFailsOnBadAuth(c *C) {
187 svc := NewPushService(ss.bus, ss.log)
188 // ... no auth added212 // ... no auth added
189 reg, err := svc.register("thing", nil, nil)213 svc := NewPushService(ss.bus, testSetup, ss.log)
214 reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil)
190 c.Check(reg, IsNil)215 c.Check(reg, IsNil)
191 c.Check(err, Equals, BadAuth)216 c.Check(err, Equals, ErrBadAuth)
192}217}
193218
194func (ss *serviceSuite) TestRegistrationFailsOnNoServer(c *C) {219func (ss *serviceSuite) TestManageRegFailsOnNoServer(c *C) {
195 svc := NewPushService(ss.bus, ss.log)220 setup := &PushServiceSetup{
196 svc.SetRegistrationURL("xyzzy://")221 DeviceId: "fake-device-id",
197 svc.SetAuthGetter(func(string) string { return "tok" })222 RegURL: helpers.ParseURL("xyzzy://"),
198 reg, err := svc.register("thing", nil, nil)223 AuthGetter: func(string) string { return "tok" },
224 }
225 svc := NewPushService(ss.bus, setup, ss.log)
226 reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil)
199 c.Check(reg, IsNil)227 c.Check(reg, IsNil)
200 c.Check(err, ErrorMatches, "unable to request registration: .*")228 c.Check(err, ErrorMatches, "unable to request registration: .*")
201}229}
202230
203func (ss *serviceSuite) TestRegistrationFailsOn40x(c *C) {231func (ss *serviceSuite) TestManageRegFailsOn40x(c *C) {
204 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {232 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
205 http.Error(w, "I'm a teapot", 418)233 http.Error(w, "I'm a teapot", 418)
206 }))234 }))
207 defer ts.Close()235 defer ts.Close()
208236 setup := &PushServiceSetup{
209 svc := NewPushService(ss.bus, ss.log)237 DeviceId: "fake-device-id",
210 svc.SetAuthGetter(func(string) string { return "tok" })238 RegURL: helpers.ParseURL(ts.URL),
211 svc.SetRegistrationURL(ts.URL)239 AuthGetter: func(string) string { return "tok" },
212 reg, err := svc.register("/thing", nil, nil)240 }
213 c.Check(err, Equals, BadRequest)241 svc := NewPushService(ss.bus, setup, ss.log)
242 reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil)
243 c.Check(err, Equals, ErrBadRequest)
214 c.Check(reg, IsNil)244 c.Check(reg, IsNil)
215}245}
216246
217func (ss *serviceSuite) TestRegistrationFailsOn50x(c *C) {247func (ss *serviceSuite) TestManageRegFailsOn50x(c *C) {
218 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {248 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
219 http.Error(w, "Not implemented", 501)249 http.Error(w, "Not implemented", 501)
220 }))250 }))
221 defer ts.Close()251 defer ts.Close()
222252 setup := &PushServiceSetup{
223 svc := NewPushService(ss.bus, ss.log)253 DeviceId: "fake-device-id",
224 svc.SetAuthGetter(func(string) string { return "tok" })254 RegURL: helpers.ParseURL(ts.URL),
225 svc.SetRegistrationURL(ts.URL)255 AuthGetter: func(string) string { return "tok" },
226 reg, err := svc.register("/thing", nil, nil)256 }
227 c.Check(err, Equals, BadServer)257 svc := NewPushService(ss.bus, setup, ss.log)
258 reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil)
259 c.Check(err, Equals, ErrBadServer)
228 c.Check(reg, IsNil)260 c.Check(reg, IsNil)
229}261}
230262
231func (ss *serviceSuite) TestRegistrationFailsOnBadJSON(c *C) {263func (ss *serviceSuite) TestManageRegFailsOnBadJSON(c *C) {
232 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {264 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
233 buf := make([]byte, 256)265 buf := make([]byte, 256)
234 n, e := r.Body.Read(buf)266 n, e := r.Body.Read(buf)
235 c.Assert(e, IsNil)267 c.Assert(e, IsNil)
236 req := registrationRequest{}268 req := registrationRequest{}
237 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)269 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
238 c.Check(req, DeepEquals, registrationRequest{"fake-device-id", "an-app-id"})270 c.Check(req, DeepEquals, registrationRequest{"fake-device-id", anAppId})
239271
240 w.Header().Set("Content-Type", "application/json")272 w.Header().Set("Content-Type", "application/json")
241 fmt.Fprintln(w, `{`)273 fmt.Fprintln(w, `{`)
242 }))274 }))
243 defer ts.Close()275 defer ts.Close()
244276 setup := &PushServiceSetup{
245 svc := NewPushService(ss.bus, ss.log)277 DeviceId: "fake-device-id",
246 svc.SetAuthGetter(func(string) string { return "tok" })278 RegURL: helpers.ParseURL(ts.URL),
247 svc.SetRegistrationURL(ts.URL)279 AuthGetter: func(string) string { return "tok" },
248 svc.SetDeviceId("fake-device-id")280 }
281 svc := NewPushService(ss.bus, setup, ss.log)
249 // this'll check (un)quoting, too282 // this'll check (un)quoting, too
250 reg, err := svc.register("/an_2dapp_2did", nil, nil)283 reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil)
251 c.Check(reg, IsNil)284 c.Check(reg, IsNil)
252 c.Check(err, ErrorMatches, "unable to unmarshal register response: .*")285 c.Check(err, ErrorMatches, "unable to unmarshal register response: .*")
253}286}
254287
255func (ss *serviceSuite) TestRegistrationFailsOnBadJSONDocument(c *C) {288func (ss *serviceSuite) TestManageRegFailsOnBadJSONDocument(c *C) {
256 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {289 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
257 buf := make([]byte, 256)290 buf := make([]byte, 256)
258 n, e := r.Body.Read(buf)291 n, e := r.Body.Read(buf)
259 c.Assert(e, IsNil)292 c.Assert(e, IsNil)
260 req := registrationRequest{}293 req := registrationRequest{}
261 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)294 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
262 c.Check(req, DeepEquals, registrationRequest{"fake-device-id", "an-app-id"})295 c.Check(req, DeepEquals, registrationRequest{"fake-device-id", anAppId})
263296
264 w.Header().Set("Content-Type", "application/json")297 w.Header().Set("Content-Type", "application/json")
265 fmt.Fprintln(w, `{"bananas": "very yes"}`)298 fmt.Fprintln(w, `{"bananas": "very yes"}`)
266 }))299 }))
267 defer ts.Close()300 defer ts.Close()
268301 setup := &PushServiceSetup{
269 svc := NewPushService(ss.bus, ss.log)302 DeviceId: "fake-device-id",
270 svc.SetAuthGetter(func(string) string { return "tok" })303 RegURL: helpers.ParseURL(ts.URL),
271 svc.SetRegistrationURL(ts.URL)304 AuthGetter: func(string) string { return "tok" },
272 svc.SetDeviceId("fake-device-id")305 }
306 svc := NewPushService(ss.bus, setup, ss.log)
273 // this'll check (un)quoting, too307 // this'll check (un)quoting, too
274 reg, err := svc.register("/an_2dapp_2did", nil, nil)308 reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil)
275 c.Check(reg, IsNil)309 c.Check(reg, IsNil)
276 c.Check(err, Equals, BadToken)310 c.Check(err, Equals, ErrBadToken)
311}
312
313func (ss *serviceSuite) TestDBusUnregisterWorks(c *C) {
314 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
315 buf := make([]byte, 256)
316 n, e := r.Body.Read(buf)
317 c.Assert(e, IsNil)
318 req := registrationRequest{}
319 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
320 c.Check(req, DeepEquals, registrationRequest{"fake-device-id", anAppId})
321 c.Check(r.URL.Path, Equals, "/unregister")
322 w.Header().Set("Content-Type", "application/json")
323 fmt.Fprintln(w, `{"ok":true,"token":"blob-of-bytes"}`)
324 }))
325 defer ts.Close()
326 setup := &PushServiceSetup{
327 DeviceId: "fake-device-id",
328 RegURL: helpers.ParseURL(ts.URL),
329 AuthGetter: func(string) string { return "tok" },
330 }
331 svc := NewPushService(ss.bus, setup, ss.log)
332 // this'll check (un)quoting, too
333 reg, err := svc.unregister(aPackageOnBus, []interface{}{anAppId}, nil)
334 c.Assert(err, IsNil)
335 c.Assert(reg, HasLen, 0)
336}
337
338func (ss *serviceSuite) TestUnregistrationWorks(c *C) {
339 invoked := make(chan bool, 1)
340 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
341 buf := make([]byte, 256)
342 n, e := r.Body.Read(buf)
343 c.Assert(e, IsNil)
344 req := registrationRequest{}
345 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
346 c.Check(req, DeepEquals, registrationRequest{"fake-device-id", anAppId})
347 c.Check(r.URL.Path, Equals, "/unregister")
348 invoked <- true
349 w.Header().Set("Content-Type", "application/json")
350 fmt.Fprintln(w, `{"ok":true}`)
351 }))
352 defer ts.Close()
353 setup := &PushServiceSetup{
354 DeviceId: "fake-device-id",
355 RegURL: helpers.ParseURL(ts.URL),
356 AuthGetter: func(string) string { return "tok" },
357 }
358 svc := NewPushService(ss.bus, setup, ss.log)
359 err := svc.Unregister(anAppId)
360 c.Assert(err, IsNil)
361 c.Check(invoked, HasLen, 1)
277}362}
278363
=== modified file 'client/session/session.go'
--- client/session/session.go 2014-06-13 12:19:26 +0000
+++ client/session/session.go 2014-07-07 14:23:18 +0000
@@ -78,6 +78,12 @@
78 Get() (*gethosts.Host, error)78 Get() (*gethosts.Host, error)
79}79}
8080
81// AddresseeChecking can check if a notification can be delivered.
82type AddresseeChecking interface {
83 StartAddresseeBatch()
84 CheckForAddressee(*protocol.Notification) bool
85}
86
81// ClientSessionConfig groups the client session configuration.87// ClientSessionConfig groups the client session configuration.
82type ClientSessionConfig struct {88type ClientSessionConfig struct {
83 ConnectTimeout time.Duration89 ConnectTimeout time.Duration
@@ -88,6 +94,7 @@
88 Info map[string]interface{}94 Info map[string]interface{}
89 AuthGetter func(string) string95 AuthGetter func(string) string
90 AuthURL string96 AuthURL string
97 AddresseeChecker AddresseeChecking
91}98}
9299
93// ClientSession holds a client<->server session and its configuration.100// ClientSession holds a client<->server session and its configuration.
@@ -426,8 +433,12 @@
426 return err433 return err
427 }434 }
428 sess.clearShouldDelay()435 sess.clearShouldDelay()
436 sess.AddresseeChecker.StartAddresseeBatch()
429 for i := range notifs {437 for i := range notifs {
430 notif := &notifs[i]438 notif := &notifs[i]
439 if !sess.AddresseeChecker.CheckForAddressee(notif) {
440 continue
441 }
431 sess.Log.Debugf("unicast app:%v msg:%s payload:%s",442 sess.Log.Debugf("unicast app:%v msg:%s payload:%s",
432 notif.AppId, notif.MsgId, notif.Payload)443 notif.AppId, notif.MsgId, notif.Payload)
433 sess.Log.Debugf("sending ucast over")444 sess.Log.Debugf("sending ucast over")
434445
=== modified file 'client/session/session_test.go'
--- client/session/session_test.go 2014-06-13 12:19:26 +0000
+++ client/session/session_test.go 2014-07-07 14:23:18 +0000
@@ -798,7 +798,23 @@
798 handleNotifications() tests798 handleNotifications() tests
799****************************************************************/799****************************************************************/
800800
801type testAddresseeChecking struct {
802 ops chan string
803 missing string
804}
805
806func (ac *testAddresseeChecking) StartAddresseeBatch() {
807 ac.ops <- "start"
808}
809
810func (ac *testAddresseeChecking) CheckForAddressee(notif *protocol.Notification) bool {
811 ac.ops <- notif.AppId
812 return notif.AppId != ac.missing
813}
814
801func (s *msgSuite) TestHandleNotificationsWorks(c *C) {815func (s *msgSuite) TestHandleNotificationsWorks(c *C) {
816 ac := &testAddresseeChecking{ops: make(chan string, 10)}
817 s.sess.AddresseeChecker = ac
802 s.sess.setShouldDelay()818 s.sess.setShouldDelay()
803 n1 := protocol.Notification{819 n1 := protocol.Notification{
804 AppId: "app1",820 AppId: "app1",
@@ -820,12 +836,52 @@
820 s.upCh <- nil // ack ok836 s.upCh <- nil // ack ok
821 c.Check(<-s.errCh, Equals, nil)837 c.Check(<-s.errCh, Equals, nil)
822 c.Check(s.sess.ShouldDelay(), Equals, false)838 c.Check(s.sess.ShouldDelay(), Equals, false)
823 c.Assert(len(s.sess.NotificationsCh), Equals, 2)839 c.Assert(s.sess.NotificationsCh, HasLen, 2)
824 c.Check(<-s.sess.NotificationsCh, DeepEquals, &n1)840 c.Check(<-s.sess.NotificationsCh, DeepEquals, &n1)
825 c.Check(<-s.sess.NotificationsCh, DeepEquals, &n2)841 c.Check(<-s.sess.NotificationsCh, DeepEquals, &n2)
842 c.Check(ac.ops, HasLen, 3)
843 c.Check(<-ac.ops, Equals, "start")
844 c.Check(<-ac.ops, Equals, "app1")
845 c.Check(<-ac.ops, Equals, "app2")
846}
847
848func (s *msgSuite) TestHandleNotificationsAddresseeCheck(c *C) {
849 ac := &testAddresseeChecking{
850 ops: make(chan string, 10),
851 missing: "app1",
852 }
853 s.sess.AddresseeChecker = ac
854 s.sess.setShouldDelay()
855 n1 := protocol.Notification{
856 AppId: "app1",
857 MsgId: "a",
858 Payload: json.RawMessage(`{"m": 1}`),
859 }
860 n2 := protocol.Notification{
861 AppId: "app2",
862 MsgId: "b",
863 Payload: json.RawMessage(`{"m": 2}`),
864 }
865 msg := serverMsg{"notifications",
866 protocol.BroadcastMsg{},
867 protocol.NotificationsMsg{
868 Notifications: []protocol.Notification{n1, n2},
869 }, protocol.ConnBrokenMsg{}}
870 go func() { s.errCh <- s.sess.handleNotifications(&msg) }()
871 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
872 s.upCh <- nil // ack ok
873 c.Check(<-s.errCh, Equals, nil)
874 c.Check(s.sess.ShouldDelay(), Equals, false)
875 c.Assert(s.sess.NotificationsCh, HasLen, 1)
876 c.Check(<-s.sess.NotificationsCh, DeepEquals, &n2)
877 c.Check(ac.ops, HasLen, 3)
878 c.Check(<-ac.ops, Equals, "start")
879 c.Check(<-ac.ops, Equals, "app1")
826}880}
827881
828func (s *msgSuite) TestHandleNotificationsFiltersSeen(c *C) {882func (s *msgSuite) TestHandleNotificationsFiltersSeen(c *C) {
883 ac := &testAddresseeChecking{ops: make(chan string, 10)}
884 s.sess.AddresseeChecker = ac
829 n1 := protocol.Notification{885 n1 := protocol.Notification{
830 AppId: "app1",886 AppId: "app1",
831 MsgId: "a",887 MsgId: "a",
@@ -845,16 +901,18 @@
845 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})901 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
846 s.upCh <- nil // ack ok902 s.upCh <- nil // ack ok
847 c.Check(<-s.errCh, Equals, nil)903 c.Check(<-s.errCh, Equals, nil)
848 c.Assert(len(s.sess.NotificationsCh), Equals, 2)904 c.Assert(s.sess.NotificationsCh, HasLen, 2)
849 c.Check(<-s.sess.NotificationsCh, DeepEquals, &n1)905 c.Check(<-s.sess.NotificationsCh, DeepEquals, &n1)
850 c.Check(<-s.sess.NotificationsCh, DeepEquals, &n2)906 c.Check(<-s.sess.NotificationsCh, DeepEquals, &n2)
907 c.Check(ac.ops, HasLen, 3)
851908
852 // second time they get ignored909 // second time they get ignored
853 go func() { s.errCh <- s.sess.handleNotifications(&msg) }()910 go func() { s.errCh <- s.sess.handleNotifications(&msg) }()
854 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})911 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
855 s.upCh <- nil // ack ok912 s.upCh <- nil // ack ok
856 c.Check(<-s.errCh, Equals, nil)913 c.Check(<-s.errCh, Equals, nil)
857 c.Assert(len(s.sess.NotificationsCh), Equals, 0)914 c.Assert(s.sess.NotificationsCh, HasLen, 0)
915 c.Check(ac.ops, HasLen, 4)
858}916}
859917
860func (s *msgSuite) TestHandleNotificationsBadAckWrite(c *C) {918func (s *msgSuite) TestHandleNotificationsBadAckWrite(c *C) {
861919
=== modified file 'debian/changelog'
--- debian/changelog 2014-07-02 13:14:04 +0000
+++ debian/changelog 2014-07-07 14:23:18 +0000
@@ -1,3 +1,18 @@
1ubuntu-push (0.43) UNRELEASED; urgency=medium
2
3 [ Samuele Pedroni ]
4 * Logic to support unregistering tokens lazily for uninstalled apps
5 * Minimal wrapping of libclick to check if a package is installed for a user
6 * Refactor and cleanup of cleanup/service
7
8 [ John R. Lenton ]
9 * Finalized DBus API (hopefully)
10 * Support emblem counter notifications
11 * Support haptic (vibration) notifications
12 * Support sound notifications
13
14 -- John R. Lenton <john.lenton@canonical.com> Mon, 07 Jul 2014 15:22:42 +0100
15
1ubuntu-push (0.42+14.10.20140702-0ubuntu1) utopic; urgency=medium16ubuntu-push (0.42+14.10.20140702-0ubuntu1) utopic; urgency=medium
217
3 [ Samuele Pedroni ]18 [ Samuele Pedroni ]
419
=== modified file 'debian/config.json'
--- debian/config.json 2014-06-19 21:12:04 +0000
+++ debian/config.json 2014-07-07 14:23:18 +0000
@@ -1,7 +1,7 @@
1{1{
2 "auth_helper": "/usr/lib/ubuntu-push-client/signing-helper",2 "auth_helper": "/usr/lib/ubuntu-push-client/signing-helper",
3 "session_url": "https://push.ubuntu.com/",3 "session_url": "https://push.ubuntu.com/",
4 "registration_url": "https://push.ubuntu.com/register",4 "registration_url": "https://push.ubuntu.com",
5 "connect_timeout": "20s",5 "connect_timeout": "20s",
6 "exchange_timeout": "30s",6 "exchange_timeout": "30s",
7 "hosts_cache_expiry": "12h",7 "hosts_cache_expiry": "12h",
88
=== modified file 'debian/control'
--- debian/control 2014-06-23 13:29:39 +0000
+++ debian/control 2014-07-07 14:23:18 +0000
@@ -20,6 +20,7 @@
20 libubuntuoneauth-2.0-dev,20 libubuntuoneauth-2.0-dev,
21 libdbus-1-dev,21 libdbus-1-dev,
22 libnih-dbus-dev,22 libnih-dbus-dev,
23 libclick-0.4-dev,
23 cmake,24 cmake,
24Standards-Version: 3.9.525Standards-Version: 3.9.5
25Homepage: http://launchpad.net/ubuntu-push26Homepage: http://launchpad.net/ubuntu-push
2627
=== modified file 'launch_helper/helper_output.go'
--- launch_helper/helper_output.go 2014-06-30 12:19:14 +0000
+++ launch_helper/helper_output.go 2014-07-07 14:23:18 +0000
@@ -37,9 +37,9 @@
37// a Vibration generates a vibration in the form of a Pattern set in37// a Vibration generates a vibration in the form of a Pattern set in
38// duration a pattern of on off states, repeated a number of times38// duration a pattern of on off states, repeated a number of times
39type Vibration struct {39type Vibration struct {
40 Duration uint `json:"duration"` // if Duration is present and not 0, it's like a Pattern of [Duration] and a Repeat of 1; otherwise, Pattern and Repeat are used.40 Duration uint32 `json:"duration"` // if Duration is present and not 0, it's like a Pattern of [Duration]; otherwise, Pattern is used.
41 Pattern []uint32 `json:"pattern"`41 Pattern []uint32 `json:"pattern"`
42 Repeat uint32 `json:"repeat"`42 Repeat uint32 `json:"repeat"` // defaults to 1. A value of zero is ignored (so it's like 1).
43}43}
4444
45// a Notification can be any of the above45// a Notification can be any of the above
4646
=== modified file 'messaging/messaging.go'
--- messaging/messaging.go 2014-07-01 00:51:59 +0000
+++ messaging/messaging.go 2014-07-07 14:23:18 +0000
@@ -43,6 +43,7 @@
4343
44func (mmu *MessagingMenu) Present(appId string, notificationId string, notification *launch_helper.Notification) {44func (mmu *MessagingMenu) Present(appId string, notificationId string, notification *launch_helper.Notification) {
45 if notification == nil || notification.Card == nil || !notification.Card.Persist || notification.Card.Summary == "" {45 if notification == nil || notification.Card == nil || !notification.Card.Persist || notification.Card.Summary == "" {
46 mmu.Log.Debugf("[%s] no notification or notification has no persistable card: %#v", notificationId, notification)
46 return47 return
47 }48 }
4849
4950
=== modified file 'messaging/messaging_test.go'
--- messaging/messaging_test.go 2014-07-01 00:51:59 +0000
+++ messaging/messaging_test.go 2014-07-07 14:23:18 +0000
@@ -61,7 +61,7 @@
6161
62 mmu.Present("app-id", "notif-id", &notif)62 mmu.Present("app-id", "notif-id", &notif)
6363
64 c.Check(ms.log.Captured(), Equals, "")64 c.Check(ms.log.Captured(), Matches, "(?sm).*has no persistable card.*")
65}65}
6666
67func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNotPersist(c *C) {67func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNotPersist(c *C) {
@@ -71,17 +71,17 @@
7171
72 mmu.Present("app-id", "notif-id", &notif)72 mmu.Present("app-id", "notif-id", &notif)
7373
74 c.Check(ms.log.Captured(), Equals, "")74 c.Check(ms.log.Captured(), Matches, "(?sm).*has no persistable card.*")
75}75}
7676
77func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNil(c *C) {77func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNil(c *C) {
78 mmu := New(ms.log)78 mmu := New(ms.log)
79 mmu.Present("app-id", "notif-id", nil)79 mmu.Present("app-id", "notif-id", nil)
80 c.Check(ms.log.Captured(), Equals, "")80 c.Check(ms.log.Captured(), Matches, "(?sm).*no notification.*")
81}81}
8282
83func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNilCard(c *C) {83func (ms *MessagingSuite) TestPresentDoesNotPresentsIfNilCard(c *C) {
84 mmu := New(ms.log)84 mmu := New(ms.log)
85 mmu.Present("app-id", "notif-id", &launch_helper.Notification{})85 mmu.Present("app-id", "notif-id", &launch_helper.Notification{})
86 c.Check(ms.log.Captured(), Equals, "")86 c.Check(ms.log.Captured(), Matches, "(?sm).*no notification.*")
87}87}
8888
=== modified file 'server/session/session_test.go'
--- server/session/session_test.go 2014-06-19 13:06:52 +0000
+++ server/session/session_test.go 2014-07-07 14:23:18 +0000
@@ -373,6 +373,7 @@
373 pingTimer: time.NewTimer(pingInterval),373 pingTimer: time.NewTimer(pingInterval),
374 intervalStart: now,374 intervalStart: now,
375 }375 }
376 time.Sleep(10 * time.Millisecond)
376 l.pingTimer.Stop()377 l.pingTimer.Stop()
377 done := l.pingTimerReset(true)378 done := l.pingTimerReset(true)
378 c.Assert(done, Equals, true)379 c.Assert(done, Equals, true)
379380
=== added directory 'sounds'
=== added file 'sounds/sounds.go'
--- sounds/sounds.go 1970-01-01 00:00:00 +0000
+++ sounds/sounds.go 2014-07-07 14:23:18 +0000
@@ -0,0 +1,91 @@
1/*
2 Copyright 2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17package sounds
18
19import (
20 "os"
21 "os/exec"
22 "path"
23
24 "launchpad.net/go-xdg/v0"
25
26 "launchpad.net/ubuntu-push/click"
27 "launchpad.net/ubuntu-push/launch_helper"
28 "launchpad.net/ubuntu-push/logger"
29)
30
31type Sound struct {
32 player string
33 log logger.Logger
34 dataDirs func() []string
35 dataFind func(string) (string, error)
36}
37
38func New(log logger.Logger) *Sound {
39 return &Sound{player: "paplay", log: log, dataDirs: xdg.Data.Dirs, dataFind: xdg.Data.Find}
40}
41
42func (snd *Sound) Present(appId string, nid string, notification *launch_helper.Notification) bool {
43 if notification == nil || notification.Sound == "" {
44 snd.log.Debugf("[%s] no notification or no Sound in the notification; doing nothing: %#v", nid, notification)
45 return false
46 }
47 absPath := snd.findSoundFile(appId, nid, notification.Sound)
48 if absPath == "" {
49 snd.log.Debugf("[%s] unable to find sound %s", nid, notification.Sound)
50 return false
51 }
52 snd.log.Debugf("[%s] playing sound %s using %s", nid, absPath, snd.player)
53 cmd := exec.Command(snd.player, absPath)
54 err := cmd.Start()
55 if err != nil {
56 snd.log.Debugf("[%s] unable to play: %v", nid, err)
57 return false
58 }
59 go func() {
60 err := cmd.Wait()
61 if err != nil {
62 snd.log.Debugf("[%s] error playing sound %s: %v", nid, absPath, err)
63 }
64 }()
65 return true
66}
67
68func (snd *Sound) findSoundFile(appId string, nid string, sound string) string {
69 parsed, err := click.ParseAppId(appId)
70 if err != nil {
71 snd.log.Debugf("[%s] no appId in %#v", nid, appId)
72 return ""
73 }
74 // XXX also support legacy appIds?
75 // first, check package-specific
76 absPath, err := snd.dataFind(path.Join(parsed.Package, sound))
77 if err == nil {
78 // ffffound
79 return absPath
80 }
81 // next, check the XDG data dirs (but skip the first one -- that's "home")
82 // XXX should we only check in $XDG/sounds ? (that's for sound *themes*...)
83 for _, dir := range snd.dataDirs()[1:] {
84 absPath := path.Join(dir, sound)
85 _, err := os.Stat(absPath)
86 if err == nil {
87 return absPath
88 }
89 }
90 return ""
91}
092
=== added file 'sounds/sounds_test.go'
--- sounds/sounds_test.go 1970-01-01 00:00:00 +0000
+++ sounds/sounds_test.go 2014-07-07 14:23:18 +0000
@@ -0,0 +1,85 @@
1/*
2 Copyright 2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17package sounds
18
19import (
20 "errors"
21 "os"
22 "path"
23 "testing"
24
25 . "launchpad.net/gocheck"
26
27 "launchpad.net/ubuntu-push/launch_helper"
28 helpers "launchpad.net/ubuntu-push/testing"
29)
30
31func TestSounds(t *testing.T) { TestingT(t) }
32
33type soundsSuite struct {
34 log *helpers.TestLogger
35}
36
37var _ = Suite(&soundsSuite{})
38
39func (ss *soundsSuite) SetUpTest(c *C) {
40 ss.log = helpers.NewTestLogger(c, "debug")
41}
42
43func (ss *soundsSuite) TestNew(c *C) {
44 s := New(ss.log)
45 c.Check(s.log, Equals, ss.log)
46 c.Check(s.player, Equals, "paplay")
47}
48
49func (ss *soundsSuite) TestPresent(c *C) {
50 s := &Sound{
51 player: "echo", log: ss.log,
52 dataFind: func(s string) (string, error) { return s, nil },
53 }
54
55 c.Check(s.Present("com.example.test_test", "",
56 &launch_helper.Notification{Sound: "hello"}), Equals, true)
57 c.Check(ss.log.Captured(), Matches, `(?sm).* playing sound com.example.test/hello using echo`)
58}
59
60func (ss *soundsSuite) TestPresentFails(c *C) {
61 s := &Sound{player: "/", log: ss.log}
62
63 // nil notification
64 c.Check(s.Present("", "", nil), Equals, false)
65 // no Sound
66 c.Check(s.Present("", "", &launch_helper.Notification{}), Equals, false)
67 // bad player
68 c.Check(s.Present("", "", &launch_helper.Notification{Sound: "hello"}), Equals, false)
69 s.player = "echo"
70 // bad app id
71 c.Check(s.Present("", "", &launch_helper.Notification{Sound: "hello"}), Equals, false)
72 s.dataFind = func(string) (string, error) { return "", errors.New("nope") }
73 s.dataDirs = func() []string { return []string{""} }
74 // no file found
75 c.Check(s.Present("com.example.test_test", "", &launch_helper.Notification{Sound: "hello"}), Equals, false)
76
77 // and now, just to prove it would've worked,
78
79 d := c.MkDir()
80 f, err := os.Create(path.Join(d, "hello"))
81 c.Assert(err, IsNil)
82 f.Close()
83 s.dataDirs = func() []string { return []string{"", d} }
84 c.Check(s.Present("com.example.test_test", "", &launch_helper.Notification{Sound: "hello"}), Equals, true)
85}
086
=== modified file 'testing/helpers.go'
--- testing/helpers.go 2014-06-23 12:46:28 +0000
+++ testing/helpers.go 2014-07-07 14:23:18 +0000
@@ -20,6 +20,7 @@
20import (20import (
21 "encoding/json"21 "encoding/json"
22 "fmt"22 "fmt"
23 "net/url"
23 "os"24 "os"
24 "path"25 "path"
25 "path/filepath"26 "path/filepath"
@@ -160,3 +161,12 @@
160 }161 }
161 return res162 return res
162}163}
164
165// ParseURL parses a URL conveniently.
166func ParseURL(s string) *url.URL {
167 purl, err := url.Parse(s)
168 if err != nil {
169 panic(err)
170 }
171 return purl
172}
163173
=== modified file 'whoopsie/identifier/identifier.go'
--- whoopsie/identifier/identifier.go 2014-06-13 12:23:03 +0000
+++ whoopsie/identifier/identifier.go 2014-07-07 14:23:18 +0000
@@ -62,6 +62,7 @@
62 if gerr == nil && cs != nil {62 if gerr == nil && cs != nil {
63 goto Success63 goto Success
64 }64 }
65 C.g_clear_error(&gerr)
65 time.Sleep(600 * time.Millisecond)66 time.Sleep(600 * time.Millisecond)
66 }67 }
67 return errors.New("whoopsie_identifier_generate still bad after 2m; giving up")68 return errors.New("whoopsie_identifier_generate still bad after 2m; giving up")

Subscribers

People subscribed via source and target branches