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

Proposed by John Lenton
Status: Merged
Approved by: John Lenton
Approved revision: 145
Merged at revision: 142
Proposed branch: lp:~chipaca/ubuntu-push/merge-automatic
Merge into: lp:ubuntu-push
Diff against target: 2417 lines (+1173/-218)
23 files modified
bus/accounts/accounts.go (+310/-0)
bus/accounts/accounts_test.go (+271/-0)
bus/connectivity/connectivity.go (+91/-32)
bus/connectivity/connectivity_test.go (+129/-56)
bus/connectivity/webchecker.go (+7/-1)
bus/connectivity/webchecker_test.go (+6/-1)
bus/endpoint.go (+10/-5)
bus/haptic/haptic.go (+9/-2)
bus/haptic/haptic_test.go (+35/-6)
bus/networkmanager/networkmanager.go (+10/-10)
bus/networkmanager/networkmanager_test.go (+23/-15)
bus/notifications/raw.go (+1/-1)
bus/notifications/raw_test.go (+7/-4)
bus/testing/testing_endpoint.go (+83/-31)
bus/testing/testing_endpoint_test.go (+38/-18)
client/client.go (+4/-2)
client/client_test.go (+12/-9)
client/service/postal.go (+12/-2)
client/service/postal_test.go (+17/-0)
debian/changelog (+30/-18)
sounds/sounds.go (+15/-2)
sounds/sounds_test.go (+44/-3)
testing/helpers.go (+9/-0)
To merge this branch: bzr merge lp:~chipaca/ubuntu-push/merge-automatic
Reviewer Review Type Date Requested Status
John Lenton (community) Approve
Review via email: mp+252083@code.launchpad.net

Commit message

The individual branches that compose this merge were peer-reviewed separately, as indicated in the commit logs.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'bus/accounts'
2=== added file 'bus/accounts/accounts.go'
3--- bus/accounts/accounts.go 1970-01-01 00:00:00 +0000
4+++ bus/accounts/accounts.go 2015-03-06 13:26:11 +0000
5@@ -0,0 +1,310 @@
6+/*
7+ Copyright 2013-2015 Canonical Ltd.
8+
9+ This program is free software: you can redistribute it and/or modify it
10+ under the terms of the GNU General Public License version 3, as published
11+ by the Free Software Foundation.
12+
13+ This program is distributed in the hope that it will be useful, but
14+ WITHOUT ANY WARRANTY; without even the implied warranties of
15+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16+ PURPOSE. See the GNU General Public License for more details.
17+
18+ You should have received a copy of the GNU General Public License along
19+ with this program. If not, see <http://www.gnu.org/licenses/>.
20+*/
21+// accounts exposes some properties that're stored in org.freedesktop.Accounts
22+// (specifically, the ones that we need are all under
23+// com.ubuntu.touch.AccountsService.Sound).
24+package accounts
25+
26+import (
27+ "fmt"
28+ "os/user"
29+ "strings"
30+ "sync"
31+
32+ "launchpad.net/go-dbus/v1"
33+ "launchpad.net/go-xdg/v0"
34+
35+ "launchpad.net/ubuntu-push/bus"
36+ "launchpad.net/ubuntu-push/logger"
37+)
38+
39+// accounts lives on a well-known bus.Address.
40+//
41+// Note this one isn't it: the interface is for dbus.properties, and the path
42+// is missing the UID.
43+var BusAddress bus.Address = bus.Address{
44+ Interface: "org.freedesktop.DBus.Properties",
45+ Path: "/org/freedesktop/Accounts/User",
46+ Name: "org.freedesktop.Accounts",
47+}
48+
49+const accountsSoundIface = "com.ubuntu.touch.AccountsService.Sound"
50+
51+type Accounts interface {
52+ // Start() sets up the asynchronous updating of properties, and does the first update.
53+ Start() error
54+ // Cancel() stops the asynchronous updating of properties.
55+ Cancel() error
56+ // SilentMode() tells you whether the device is in silent mode.
57+ SilentMode() bool
58+ // Vibrate() tells you whether the device is allowed to vibrate.
59+ Vibrate() bool
60+ // MessageSoundFile() tells you the default sound filename.
61+ MessageSoundFile() string
62+ String() string
63+}
64+
65+// Accounts tracks the relevant bits of configuration. Nothing directly
66+// accessible because it is updated asynchronously, so use the accessors.
67+type accounts struct {
68+ endp bus.Endpoint
69+ log logger.Logger
70+ silent bool
71+ vibrate bool
72+ vibrateSilentMode bool
73+ messageSound string
74+ cancellable bus.Cancellable
75+ lck sync.Mutex
76+ updaters map[string]func(dbus.Variant)
77+}
78+
79+// sets up a new Accounts structure, ready to be Start()ed.
80+func New(endp bus.Endpoint, log logger.Logger) Accounts {
81+ a := &accounts{
82+ endp: endp,
83+ log: log,
84+ }
85+
86+ a.updaters = map[string]func(dbus.Variant){
87+ "SilentMode": a.updateSilentMode,
88+ "IncomingMessageVibrate": a.updateVibrate,
89+ "IncomingMessageVibrateSilentMode": a.updateVibrateSilentMode,
90+ "IncomingMessageSound": a.updateMessageSound,
91+ }
92+
93+ return a
94+}
95+
96+// sets up the asynchronous updating of properties, and does the first update.
97+func (a *accounts) Start() error {
98+ err := a.startWatch()
99+ if err != nil {
100+ return err
101+ }
102+ a.update()
103+ return nil
104+}
105+
106+// does sets up the watch on the PropertiesChanged signal. Separate from Start
107+// because it holds a lock.
108+func (a *accounts) startWatch() error {
109+ cancellable, err := a.endp.WatchSignal("PropertiesChanged", a.propsHandler, a.bailoutHandler)
110+ if err != nil {
111+ a.log.Errorf("unable to watch for property changes: %v", err)
112+ return err
113+ }
114+
115+ a.lck.Lock()
116+ defer a.lck.Unlock()
117+ if a.cancellable != nil {
118+ panic("tried to start Accounts twice?")
119+ }
120+ a.cancellable = cancellable
121+
122+ return nil
123+}
124+
125+// cancel the asynchronous updating of properties.
126+func (a *accounts) Cancel() error {
127+ return a.cancellable.Cancel()
128+}
129+
130+// slightly shorter than %#v
131+func (a *accounts) String() string {
132+ return fmt.Sprintf("&accounts{silent: %t, vibrate: %t, vibratesilent: %t, messageSound: %q}",
133+ a.silent, a.vibrate, a.vibrateSilentMode, a.messageSound)
134+}
135+
136+// merely log that the watch loop has bailed; not much we can do.
137+func (a *accounts) bailoutHandler() {
138+ a.log.Debugf("loop bailed out")
139+}
140+
141+// handle PropertiesChanged, which is described in
142+// http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties
143+func (a *accounts) propsHandler(ns ...interface{}) {
144+ if len(ns) != 3 {
145+ a.log.Errorf("PropertiesChanged delivered %d things instead of 3.", len(ns))
146+ return
147+ }
148+
149+ iface, ok := ns[0].(string)
150+ if !ok {
151+ a.log.Errorf("PropertiesChanged 1st param not a string: %#v.", ns[0])
152+ return
153+ }
154+ if iface != accountsSoundIface {
155+ a.log.Debugf("PropertiesChanged for %#v, ignoring.", iface)
156+ return
157+ }
158+ changed, ok := ns[1].(map[interface{}]interface{})
159+ if !ok {
160+ a.log.Errorf("PropertiesChanged 2nd param not a map: %#v.", ns[1])
161+ return
162+ }
163+ if len(changed) != 0 {
164+ // not seen in the wild, but easy to implement properly (ie
165+ // using the values we're given) if it starts to
166+ // happen. Meanwhile just do a full update.
167+ a.log.Infof("PropertiesChanged provided 'changed'; reverting to full update.")
168+ a.update()
169+ return
170+ }
171+ invalid, ok := ns[2].([]interface{})
172+ if !ok {
173+ a.log.Errorf("PropertiesChanged 3rd param not a list of properties: %#v.", ns[2])
174+ return
175+ }
176+ a.log.Debugf("props changed: %#v.", invalid)
177+ switch len(invalid) {
178+ case 0:
179+ // nothing to do?
180+ a.log.Debugf("PropertiesChanged 3rd param is empty; doing nothing.")
181+ case 1:
182+ // the common case right now
183+ k, ok := invalid[0].(string)
184+ if !ok {
185+ a.log.Errorf("PropertiesChanged 3rd param's only entry not a string: %#v.", invalid[0])
186+ return
187+ }
188+ updater, ok := a.updaters[k]
189+ if ok {
190+ var v dbus.Variant
191+ err := a.endp.Call("Get", []interface{}{accountsSoundIface, k}, &v)
192+ if err != nil {
193+ a.log.Errorf("when calling Get for %s: %v", k, err)
194+ return
195+ }
196+ a.log.Debugf("Get for %s got %#v.", k, v)
197+ // updaters must be called with the lock held
198+ a.lck.Lock()
199+ defer a.lck.Unlock()
200+ updater(v)
201+ a.log.Debugf("updated %s.", k)
202+ }
203+ default:
204+ // not seen in the wild, but we probably want to drop to a
205+ // full update if getting more than one change anyway.
206+ a.log.Infof("PropertiesChanged provided more than one 'invalid'; reverting to full update.")
207+ a.update()
208+ }
209+}
210+
211+func (a *accounts) updateSilentMode(vsilent dbus.Variant) {
212+ silent, ok := vsilent.Value.(bool)
213+ if !ok {
214+ a.log.Errorf("SilentMode needed a bool.")
215+ return
216+ }
217+
218+ a.silent = silent
219+}
220+
221+func (a *accounts) updateVibrate(vvibrate dbus.Variant) {
222+ vibrate, ok := vvibrate.Value.(bool)
223+ if !ok {
224+ a.log.Errorf("IncomingMessageVibrate needed a bool.")
225+ return
226+ }
227+
228+ a.vibrate = vibrate
229+}
230+
231+func (a *accounts) updateVibrateSilentMode(vvibrateSilentMode dbus.Variant) {
232+ vibrateSilentMode, ok := vvibrateSilentMode.Value.(bool)
233+ if !ok {
234+ a.log.Errorf("IncomingMessageVibrateSilentMode needed a bool.")
235+ return
236+ }
237+
238+ a.vibrateSilentMode = vibrateSilentMode
239+}
240+
241+func (a *accounts) updateMessageSound(vsnd dbus.Variant) {
242+ snd, ok := vsnd.Value.(string)
243+ if !ok {
244+ a.log.Errorf("IncomingMessageSound needed a string.")
245+ return
246+ }
247+
248+ for _, dir := range xdg.Data.Dirs()[1:] {
249+ if dir[len(dir)-1] != '/' {
250+ dir += "/"
251+ }
252+ if strings.HasPrefix(snd, dir) {
253+ snd = snd[len(dir):]
254+ break
255+ }
256+ }
257+
258+ a.messageSound = snd
259+}
260+
261+func (a *accounts) update() {
262+ props := make(map[string]dbus.Variant)
263+ err := a.endp.Call("GetAll", []interface{}{accountsSoundIface}, &props)
264+ if err != nil {
265+ a.log.Errorf("when calling GetAll: %v", err)
266+ return
267+ }
268+ a.log.Debugf("GetAll got: %#v", props)
269+
270+ a.lck.Lock()
271+ defer a.lck.Unlock()
272+
273+ for name, updater := range a.updaters {
274+ updater(props[name])
275+ }
276+}
277+
278+// is the device in silent mode?
279+func (a *accounts) SilentMode() bool {
280+ a.lck.Lock()
281+ defer a.lck.Unlock()
282+
283+ return a.silent
284+}
285+
286+// should notifications vibrate?
287+func (a *accounts) Vibrate() bool {
288+ a.lck.Lock()
289+ defer a.lck.Unlock()
290+
291+ if a.silent {
292+ return a.vibrateSilentMode
293+ } else {
294+ return a.vibrate
295+ }
296+}
297+
298+// what is the default sound file?
299+func (a *accounts) MessageSoundFile() string {
300+ a.lck.Lock()
301+ defer a.lck.Unlock()
302+
303+ return a.messageSound
304+}
305+
306+// the BusAddress should actually end with the UID of the user in question;
307+// here we do what's needed to get that.
308+func init() {
309+ u, err := user.Current()
310+ if err != nil {
311+ panic(err)
312+ }
313+
314+ BusAddress.Path += u.Uid
315+}
316
317=== added file 'bus/accounts/accounts_test.go'
318--- bus/accounts/accounts_test.go 1970-01-01 00:00:00 +0000
319+++ bus/accounts/accounts_test.go 2015-03-06 13:26:11 +0000
320@@ -0,0 +1,271 @@
321+/*
322+ Copyright 2013-2015 Canonical Ltd.
323+
324+ This program is free software: you can redistribute it and/or modify it
325+ under the terms of the GNU General Public License version 3, as published
326+ by the Free Software Foundation.
327+
328+ This program is distributed in the hope that it will be useful, but
329+ WITHOUT ANY WARRANTY; without even the implied warranties of
330+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
331+ PURPOSE. See the GNU General Public License for more details.
332+
333+ You should have received a copy of the GNU General Public License along
334+ with this program. If not, see <http://www.gnu.org/licenses/>.
335+*/
336+
337+package accounts
338+
339+import (
340+ "errors"
341+ "testing"
342+
343+ "launchpad.net/go-dbus/v1"
344+ . "launchpad.net/gocheck"
345+
346+ testibus "launchpad.net/ubuntu-push/bus/testing"
347+ helpers "launchpad.net/ubuntu-push/testing"
348+ "launchpad.net/ubuntu-push/testing/condition"
349+)
350+
351+// hook up gocheck
352+func TestAcc(t *testing.T) { TestingT(t) }
353+
354+type AccSuite struct {
355+ log *helpers.TestLogger
356+}
357+
358+var _ = Suite(&AccSuite{})
359+
360+type TestCancellable struct {
361+ canceled bool
362+ err error
363+}
364+
365+func (t *TestCancellable) Cancel() error {
366+ t.canceled = true
367+ return t.err
368+}
369+
370+func (s *AccSuite) SetUpTest(c *C) {
371+ s.log = helpers.NewTestLogger(c, "debug")
372+}
373+
374+func (s *AccSuite) TestBusAddressPathUidLoaded(c *C) {
375+ c.Check(BusAddress.Path, Matches, `.*\d+`)
376+}
377+
378+func (s *AccSuite) TestCancelCancelsCancellable(c *C) {
379+ err := errors.New("cancel error")
380+ t := &TestCancellable{err: err}
381+ a := New(nil, s.log).(*accounts)
382+ a.cancellable = t
383+
384+ c.Check(a.Cancel(), Equals, err)
385+ c.Check(t.canceled, Equals, true)
386+}
387+
388+func (s *AccSuite) TestStartReportsWatchError(c *C) {
389+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
390+ a := New(endp, s.log).(*accounts)
391+ c.Assert(a, NotNil)
392+
393+ err := a.Start()
394+ c.Check(err, NotNil)
395+}
396+
397+func (s *AccSuite) TestStartSetsCancellable(c *C) {
398+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), true)
399+ a := New(endp, s.log).(*accounts)
400+ c.Assert(a, NotNil)
401+
402+ c.Check(a.cancellable, IsNil)
403+ err := a.Start()
404+ c.Check(err, IsNil)
405+ c.Check(a.cancellable, NotNil)
406+ a.Cancel()
407+}
408+
409+func (s *AccSuite) TestStartPanicsIfCalledTwice(c *C) {
410+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), true, true)
411+ a := New(endp, s.log).(*accounts)
412+ c.Assert(a, NotNil)
413+
414+ c.Check(a.cancellable, IsNil)
415+ err := a.Start()
416+ c.Check(err, IsNil)
417+ c.Check(func() { a.startWatch() }, PanicMatches, `.* twice\?`)
418+ a.Cancel()
419+}
420+
421+func (s *AccSuite) TestUpdateCallsUpdaters(c *C) {
422+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true),
423+ map[string]dbus.Variant{"x": dbus.Variant{"hello"}})
424+ a := New(endp, s.log).(*accounts)
425+ c.Assert(a, NotNil)
426+ var x dbus.Variant
427+ a.updaters = map[string]func(dbus.Variant){
428+ "x": func(v dbus.Variant) { x = v },
429+ }
430+ a.update()
431+
432+ c.Check(x.Value, Equals, "hello")
433+}
434+
435+func (s *AccSuite) TestUpdateSilentModeBails(c *C) {
436+ a := New(nil, s.log).(*accounts)
437+ a.updateSilentMode(dbus.Variant{"rubbish"})
438+ c.Check(s.log.Captured(), Matches, `(?ms)ERROR SilentMode needed a bool.`)
439+}
440+
441+func (s *AccSuite) TestUpdateSilentModeWorks(c *C) {
442+ a := New(nil, s.log).(*accounts)
443+ c.Check(a.silent, Equals, false)
444+ a.updateSilentMode(dbus.Variant{true})
445+ c.Check(a.silent, Equals, true)
446+}
447+
448+func (s *AccSuite) TestUpdateVibrateBails(c *C) {
449+ a := New(nil, s.log).(*accounts)
450+ a.updateVibrate(dbus.Variant{"rubbish"})
451+ c.Check(s.log.Captured(), Matches, `(?ms)ERROR IncomingMessageVibrate needed a bool.`)
452+}
453+
454+func (s *AccSuite) TestUpdateVibrateWorks(c *C) {
455+ a := New(nil, s.log).(*accounts)
456+ c.Check(a.vibrate, Equals, false)
457+ a.updateVibrate(dbus.Variant{true})
458+ c.Check(a.vibrate, Equals, true)
459+}
460+
461+func (s *AccSuite) TestUpdateVibrateSilentModeBails(c *C) {
462+ a := New(nil, s.log).(*accounts)
463+ a.updateVibrateSilentMode(dbus.Variant{"rubbish"})
464+ c.Check(s.log.Captured(), Matches, `(?ms)ERROR IncomingMessageVibrateSilentMode needed a bool.`)
465+}
466+
467+func (s *AccSuite) TestUpdateVibrateSilentModeWorks(c *C) {
468+ a := New(nil, s.log).(*accounts)
469+ c.Check(a.vibrateSilentMode, Equals, false)
470+ a.updateVibrateSilentMode(dbus.Variant{true})
471+ c.Check(a.vibrateSilentMode, Equals, true)
472+}
473+
474+func (s *AccSuite) TestUpdateMessageSoundBails(c *C) {
475+ a := New(nil, s.log).(*accounts)
476+ a.updateMessageSound(dbus.Variant{42})
477+ c.Check(s.log.Captured(), Matches, `(?ms)ERROR IncomingMessageSound needed a string.`)
478+}
479+
480+func (s *AccSuite) TestUpdateMessageSoundWorks(c *C) {
481+ a := New(nil, s.log).(*accounts)
482+ c.Check(a.messageSound, Equals, "")
483+ a.updateMessageSound(dbus.Variant{"xyzzy"})
484+ c.Check(a.messageSound, Equals, "xyzzy")
485+}
486+
487+func (s *AccSuite) TestUpdateMessageSoundPrunesXDG(c *C) {
488+ a := New(nil, s.log).(*accounts)
489+ a.updateMessageSound(dbus.Variant{"/usr/share/xyzzy"})
490+ c.Check(a.messageSound, Equals, "xyzzy")
491+}
492+
493+func (s *AccSuite) TestPropsHandler(c *C) {
494+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
495+
496+ // testing a series of bad args for propsHandler: none,
497+ New(endp, s.log).(*accounts).propsHandler()
498+ c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged delivered 0 things.*`)
499+ s.log.ResetCapture()
500+
501+ // bad type for all,
502+ New(endp, s.log).(*accounts).propsHandler(nil, nil, nil)
503+ c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 1st param not a string.*`)
504+ s.log.ResetCapture()
505+
506+ // wrong interface,
507+ New(endp, s.log).(*accounts).propsHandler("xyzzy", nil, nil)
508+ c.Check(s.log.Captured(), Matches, `(?ms).*DEBUG PropertiesChanged for "xyzzy", ignoring\..*`)
509+ s.log.ResetCapture()
510+
511+ // bad type for 2nd and 3rd,
512+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, nil, nil)
513+ c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 2nd param not a map.*`)
514+ s.log.ResetCapture()
515+
516+ // not-seen-in-the-wild 'changed' argument (first non-error outcome),
517+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{"x": "y"}, nil)
518+ // tracking the update() via the GetAll call it generates (which will fail because of the testibus of Work(false) above)
519+ c.Check(s.log.Captured(), Matches, `(?ms).*INFO PropertiesChanged provided 'changed'.*ERROR when calling GetAll.*`)
520+ s.log.ResetCapture()
521+
522+ // bad type for 3rd (with empty 2nd),
523+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, nil)
524+ c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 3rd param not a list of properties.*`)
525+ s.log.ResetCapture()
526+
527+ // bad type for elements of 3rd,
528+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{42})
529+ c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 3rd param's only entry not a string.*`)
530+ s.log.ResetCapture()
531+
532+ // empty 3rd (not an error; hard to test "do ),
533+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{})
534+ c.Check(s.log.Captured(), Matches, `(?ms).*DEBUG PropertiesChanged 3rd param is empty.*`)
535+ s.log.ResetCapture()
536+
537+ // more than one 2rd (also not an error; again looking at the GetAll failure to confirm update() got called),
538+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{"hi", "there"})
539+ c.Check(s.log.Captured(), Matches, `(?ms).*INFO.* reverting to full update.*ERROR when calling GetAll.*`)
540+ s.log.ResetCapture()
541+
542+ // bus trouble for a single entry in the 3rd,
543+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{"SilentMode"})
544+ c.Check(s.log.Captured(), Matches, `(?ms).*ERROR when calling Get for SilentMode.*`)
545+ s.log.ResetCapture()
546+
547+ // and finally, the common case: a single entry in the 3rd param, that gets updated individually.
548+ xOuter := dbus.Variant{"x"}
549+ a := New(testibus.NewTestingEndpoint(nil, condition.Work(true), xOuter), s.log).(*accounts)
550+ called := false
551+ a.updaters = map[string]func(dbus.Variant){"xyzzy": func(x dbus.Variant) {
552+ c.Check(x, Equals, xOuter)
553+ called = true
554+ }}
555+ a.propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{"xyzzy"})
556+ c.Check(called, Equals, true)
557+}
558+
559+func (s *AccSuite) TestSilentMode(c *C) {
560+ a := New(nil, s.log).(*accounts)
561+ c.Check(a.SilentMode(), Equals, false)
562+ a.silent = true
563+ c.Check(a.SilentMode(), Equals, true)
564+}
565+
566+func (s *AccSuite) TestVibrate(c *C) {
567+ a := New(nil, s.log).(*accounts)
568+ c.Check(a.Vibrate(), Equals, false)
569+ a.vibrate = true
570+ c.Check(a.Vibrate(), Equals, true)
571+ a.silent = true
572+ c.Check(a.Vibrate(), Equals, false)
573+ a.vibrateSilentMode = true
574+ c.Check(a.Vibrate(), Equals, true)
575+ a.vibrate = false
576+ c.Check(a.Vibrate(), Equals, true)
577+}
578+
579+func (s *AccSuite) TestMessageSoundFile(c *C) {
580+ a := New(nil, s.log).(*accounts)
581+ c.Check(a.MessageSoundFile(), Equals, "")
582+ a.messageSound = "xyzzy"
583+ c.Check(a.MessageSoundFile(), Equals, "xyzzy")
584+}
585+
586+func (s *AccSuite) TestString(c *C) {
587+ a := New(nil, s.log).(*accounts)
588+ a.vibrate = true
589+ a.messageSound = "x"
590+ c.Check(a.String(), Equals, `&accounts{silent: false, vibrate: true, vibratesilent: false, messageSound: "x"}`)
591+}
592
593=== modified file 'bus/connectivity/connectivity.go'
594--- bus/connectivity/connectivity.go 2015-01-22 11:05:37 +0000
595+++ bus/connectivity/connectivity.go 2015-03-06 13:26:11 +0000
596@@ -1,5 +1,5 @@
597 /*
598- Copyright 2013-2014 Canonical Ltd.
599+ Copyright 2013-2015 Canonical Ltd.
600
601 This program is free software: you can redistribute it and/or modify it
602 under the terms of the GNU General Public License version 3, as published
603@@ -24,12 +24,14 @@
604
605 import (
606 "errors"
607+ "sync"
608+ "time"
609+
610 "launchpad.net/ubuntu-push/bus"
611 "launchpad.net/ubuntu-push/bus/networkmanager"
612 "launchpad.net/ubuntu-push/config"
613 "launchpad.net/ubuntu-push/logger"
614 "launchpad.net/ubuntu-push/util"
615- "time"
616 )
617
618 // The configuration for ConnectedState, intended to be populated from a config file.
619@@ -45,23 +47,56 @@
620 ConnectivityCheckMD5 string `json:"connectivity_check_md5"`
621 }
622
623-type connectedState struct {
624+// ConnectedState helps tracking connectivity.
625+type ConnectedState struct {
626 networkStateCh <-chan networkmanager.State
627 networkConCh <-chan string
628 config ConnectivityConfig
629 log logger.Logger
630 endp bus.Endpoint
631 connAttempts uint32
632- webget func(ch chan<- bool)
633+ webchk Webchecker
634 webgetCh chan bool
635 currentState networkmanager.State
636 lastSent bool
637 timer *time.Timer
638+ doneLck sync.Mutex
639+ done chan struct{}
640+ canceled bool
641+ stateWatch bus.Cancellable
642+ conWatch bus.Cancellable
643+}
644+
645+// New makes a ConnectedState for connectivity tracking.
646+//
647+// The endpoint need not be dialed; Track() will Dial() and
648+// Close() it as it sees fit.
649+func New(endp bus.Endpoint, config ConnectivityConfig, log logger.Logger) *ConnectedState {
650+ wg := NewWebchecker(config.ConnectivityCheckURL, config.ConnectivityCheckMD5, 10*time.Second, log)
651+ return &ConnectedState{
652+ config: config,
653+ log: log,
654+ endp: endp,
655+ webchk: wg,
656+ done: make(chan struct{}),
657+ }
658+}
659+
660+// cancel watches if any
661+func (cs *ConnectedState) reset() {
662+ if cs.stateWatch != nil {
663+ cs.stateWatch.Cancel()
664+ cs.stateWatch = nil
665+ }
666+ if cs.conWatch != nil {
667+ cs.conWatch.Cancel()
668+ cs.conWatch = nil
669+ }
670 }
671
672 // start connects to the bus, gets the initial NetworkManager state, and sets
673 // up the watch.
674-func (cs *connectedState) start() networkmanager.State {
675+func (cs *ConnectedState) start() networkmanager.State {
676 var initial networkmanager.State
677 var stateCh <-chan networkmanager.State
678 var primary string
679@@ -72,8 +107,9 @@
680 cs.connAttempts += ar.Redial()
681 nm := networkmanager.New(cs.endp, cs.log)
682
683+ cs.reset()
684 // set up the watch
685- stateCh, err = nm.WatchState()
686+ stateCh, cs.stateWatch, err = nm.WatchState()
687 if err != nil {
688 cs.log.Debugf("failed to set up the state watch: %s", err)
689 goto Continue
690@@ -87,15 +123,15 @@
691 }
692 cs.log.Debugf("got initial state of %s", initial)
693
694+ conCh, cs.conWatch, err = nm.WatchPrimaryConnection()
695+ if err != nil {
696+ cs.log.Debugf("failed to set up the connection watch: %s", err)
697+ goto Continue
698+ }
699+
700 primary = nm.GetPrimaryConnection()
701 cs.log.Debugf("primary connection starts as %#v", primary)
702
703- conCh, err = nm.WatchPrimaryConnection()
704- if err != nil {
705- cs.log.Debugf("failed to set up the connection watch: %s", err)
706- goto Continue
707- }
708-
709 cs.networkStateCh = stateCh
710 cs.networkConCh = conCh
711
712@@ -107,9 +143,11 @@
713 }
714 }
715
716-// connectedStateStep takes one step forwards in the “am I connected?”
717+var errCanceled = errors.New("canceled")
718+
719+// step takes one step forwards in the “am I connected?”
720 // answering state machine.
721-func (cs *connectedState) connectedStateStep() (bool, error) {
722+func (cs *ConnectedState) step() (bool, error) {
723 stabilizingTimeout := cs.config.StabilizingTimeout.Duration
724 recheckTimeout := cs.config.RecheckTimeout.Duration
725 log := cs.log
726@@ -117,6 +155,8 @@
727 Loop:
728 for {
729 select {
730+ case <-cs.done:
731+ return false, errCanceled
732 case <-cs.networkConCh:
733 cs.webgetCh = nil
734 cs.timer.Reset(stabilizingTimeout)
735@@ -155,8 +195,13 @@
736 case <-cs.timer.C:
737 if cs.currentState == networkmanager.ConnectedGlobal {
738 log.Debugf("connectivity: timer signal, state: ConnectedGlobal, checking...")
739- cs.webgetCh = make(chan bool)
740- go cs.webget(cs.webgetCh)
741+ // use a buffered channel, otherwise
742+ // we may leak webcheckers that cannot
743+ // send their result because we have
744+ // cleared webgetCh and wont receive
745+ // on it
746+ cs.webgetCh = make(chan bool, 1)
747+ go cs.webchk.Webcheck(cs.webgetCh)
748 }
749
750 case connected := <-cs.webgetCh:
751@@ -173,35 +218,49 @@
752 return cs.lastSent, nil
753 }
754
755-// ConnectedState sends the initial NetworkManager state and changes to it
756+// Track sends the initial NetworkManager state and changes to it
757 // over the "out" channel. Sends "false" as soon as it detects trouble, "true"
758 // after checking actual connectivity.
759 //
760-// The endpoint need not be dialed; connectivity will Dial() and Close()
761-// it as it sees fit.
762-func ConnectedState(endp bus.Endpoint, config ConnectivityConfig, log logger.Logger, out chan<- bool) {
763- wg := NewWebchecker(config.ConnectivityCheckURL, config.ConnectivityCheckMD5, 10*time.Second, log)
764- cs := &connectedState{
765- config: config,
766- log: log,
767- endp: endp,
768- webget: wg.Webcheck,
769- }
770+func (cs *ConnectedState) Track(out chan<- bool) {
771
772 Start:
773- log.Debugf("sending initial 'disconnected'.")
774- out <- false
775+ cs.log.Debugf("sending initial 'disconnected'.")
776+ select {
777+ case <-cs.done:
778+ return
779+ case out <- false:
780+ }
781 cs.lastSent = false
782 cs.currentState = cs.start()
783+ defer cs.reset()
784 cs.timer = time.NewTimer(cs.config.StabilizingTimeout.Duration)
785
786 for {
787- v, err := cs.connectedStateStep()
788+ v, err := cs.step()
789+ if err == errCanceled {
790+ return
791+ }
792 if err != nil {
793 // tear it all down and start over
794- log.Errorf("%s", err)
795+ cs.log.Errorf("%s", err)
796 goto Start
797 }
798- out <- v
799+ select {
800+ case <-cs.done:
801+ return
802+ case out <- v:
803+ }
804+ }
805+}
806+
807+// Cancel stops the ConnectedState machinary.
808+func (cs *ConnectedState) Cancel() {
809+ cs.doneLck.Lock()
810+ defer cs.doneLck.Unlock()
811+ if !cs.canceled {
812+ cs.canceled = true
813+ close(cs.done)
814+ cs.webchk.Close()
815 }
816 }
817
818=== modified file 'bus/connectivity/connectivity_test.go'
819--- bus/connectivity/connectivity_test.go 2015-01-21 17:55:35 +0000
820+++ bus/connectivity/connectivity_test.go 2015-03-06 13:26:11 +0000
821@@ -1,5 +1,5 @@
822 /*
823- Copyright 2013-2014 Canonical Ltd.
824+ Copyright 2013-2015 Canonical Ltd.
825
826 This program is free software: you can redistribute it and/or modify it
827 under the terms of the GNU General Public License version 3, as published
828@@ -58,22 +58,37 @@
829 s.log = helpers.NewTestLogger(c, "debug")
830 }
831
832+var (
833+ helloCon = dbus.ObjectPath("hello")
834+ helloConProps = map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{helloCon}}
835+)
836+
837 /*
838- tests for connectedState's Start() method
839+ tests for ConnectedState's Start() method
840 */
841
842 // when given a working config and bus, Start() will work
843 func (s *ConnSuite) TestStartWorks(c *C) {
844- endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(networkmanager.Connecting))
845- cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
846+ endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(networkmanager.Connecting), helloCon)
847+ cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
848+
849+ nopTicker := make(chan []interface{})
850+ testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
851+ testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
852+ defer close(nopTicker)
853
854 c.Check(cs.start(), Equals, networkmanager.Connecting)
855 }
856
857 // if the bus fails a couple of times, we're still OK
858 func (s *ConnSuite) TestStartRetriesConnect(c *C) {
859- endp := testingbus.NewTestingEndpoint(condition.Fail2Work(2), condition.Work(true), uint32(networkmanager.Connecting))
860- cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
861+ endp := testingbus.NewTestingEndpoint(condition.Fail2Work(2), condition.Work(true), uint32(networkmanager.Connecting), helloCon)
862+ cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
863+
864+ nopTicker := make(chan []interface{})
865+ testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
866+ testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
867+ defer close(nopTicker)
868
869 c.Check(cs.start(), Equals, networkmanager.Connecting)
870 c.Check(cs.connAttempts, Equals, uint32(3)) // 1 more than the Fail2Work
871@@ -81,8 +96,13 @@
872
873 // when the calls to NetworkManager fails for a bit, we're still OK
874 func (s *ConnSuite) TestStartRetriesCall(c *C) {
875- endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Fail2Work(5), uint32(networkmanager.Connecting))
876- cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
877+ endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Fail2Work(5), uint32(networkmanager.Connecting), helloCon)
878+ cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
879+
880+ nopTicker := make(chan []interface{})
881+ testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
882+ testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
883+ defer close(nopTicker)
884
885 c.Check(cs.start(), Equals, networkmanager.Connecting)
886
887@@ -91,11 +111,19 @@
888
889 // when some of the calls to NetworkManager fails for a bit, we're still OK
890 func (s *ConnSuite) TestStartRetriesCall2(c *C) {
891- cond := condition.Chain(3, condition.Work(true), 1, condition.Work(false),
892+ cond := condition.Chain(1, condition.Work(true), 1, condition.Work(false),
893 1, condition.Work(true))
894
895- endp := testingbus.NewTestingEndpoint(condition.Work(true), cond, uint32(networkmanager.Connecting))
896- cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
897+ endp := testingbus.NewTestingEndpoint(condition.Work(true), cond,
898+ uint32(networkmanager.Connecting), helloCon,
899+ uint32(networkmanager.Connecting), helloCon,
900+ )
901+ cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
902+
903+ nopTicker := make(chan []interface{})
904+ testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
905+ testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
906+ defer close(nopTicker)
907
908 c.Check(cs.start(), Equals, networkmanager.Connecting)
909 }
910@@ -105,17 +133,25 @@
911 // watch, we recover and try again.
912 func (s *ConnSuite) TestStartRetriesWatch(c *C) {
913 nmcond := condition.Chain(
914- 1, condition.Work(true), // 1 call to nm works
915+ 2, condition.Work(true), // 2 call to nm works
916 1, condition.Work(false), // 1 call to nm fails
917 0, condition.Work(true)) // and everything works from there on
918 endp := testingbus.NewTestingEndpoint(condition.Work(true), nmcond,
919 uint32(networkmanager.Connecting),
920- uint32(networkmanager.ConnectedGlobal))
921- cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
922+ uint32(networkmanager.Connecting),
923+ helloCon,
924+ )
925+ cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
926+ watchTicker := make(chan []interface{}, 1)
927+ nopTicker := make(chan []interface{})
928+ testingbus.SetWatchSource(endp, "StateChanged", watchTicker)
929+ testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
930+ defer close(nopTicker)
931+ defer close(watchTicker)
932
933 c.Check(cs.start(), Equals, networkmanager.Connecting)
934 c.Check(cs.connAttempts, Equals, uint32(2))
935- c.Check(<-cs.networkStateCh, Equals, networkmanager.Connecting)
936+ watchTicker <- []interface{}{uint32(networkmanager.ConnectedGlobal)}
937 c.Check(<-cs.networkStateCh, Equals, networkmanager.ConnectedGlobal)
938 }
939
940@@ -144,7 +180,7 @@
941 }
942 }
943
944-func (rep *racyEndpoint) WatchSignal(member string, f func(...interface{}), d func()) error {
945+func (rep *racyEndpoint) WatchSignal(member string, f func(...interface{}), d func()) (bus.Cancellable, error) {
946 if member == "StateChanged" {
947 // we count never having gotten the state as happening "after" now.
948 rep.lock.RLock()
949@@ -157,7 +193,7 @@
950 d()
951 }()
952 }
953- return nil
954+ return nil, nil
955 }
956
957 func (*racyEndpoint) Close() {}
958@@ -186,7 +222,7 @@
959 func (s *ConnSuite) TestStartAvoidsRace(c *C) {
960 for delta := time.Second; delta > 1; delta /= 2 {
961 rep := &racyEndpoint{delta: delta}
962- cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: rep}
963+ cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: rep}
964 f := Commentf("when delta=%s", delta)
965 c.Assert(cs.start(), Equals, networkmanager.Connecting, f)
966 c.Assert(takeNext(cs.networkStateCh), Equals, networkmanager.ConnectedGlobal, f)
967@@ -194,9 +230,18 @@
968 }
969
970 /*
971- tests for connectedStateStep()
972+ tests for step()
973 */
974
975+type testWebchk func(ch chan<- bool)
976+
977+func (x testWebchk) Webcheck(ch chan<- bool) {
978+ x(ch)
979+}
980+
981+func (x testWebchk) Close() {
982+}
983+
984 func (s *ConnSuite) TestSteps(c *C) {
985 var webget_p condition.Interface = condition.Work(true)
986 recheck_timeout := 50 * time.Millisecond
987@@ -205,24 +250,24 @@
988 RecheckTimeout: config.ConfigTimeDuration{recheck_timeout},
989 }
990 ch := make(chan networkmanager.State, 10)
991- cs := &connectedState{
992+ cs := &ConnectedState{
993 config: cfg,
994 networkStateCh: ch,
995 timer: time.NewTimer(time.Second),
996 log: s.log,
997- webget: func(ch chan<- bool) { ch <- webget_p.OK() },
998+ webchk: testWebchk(func(ch chan<- bool) { ch <- webget_p.OK() }),
999 lastSent: false,
1000 }
1001 ch <- networkmanager.ConnectedGlobal
1002- f, e := cs.connectedStateStep()
1003+ f, e := cs.step()
1004 c.Check(e, IsNil)
1005 c.Check(f, Equals, true)
1006 ch <- networkmanager.Disconnected
1007 ch <- networkmanager.ConnectedGlobal
1008- f, e = cs.connectedStateStep()
1009+ f, e = cs.step()
1010 c.Check(e, IsNil)
1011 c.Check(f, Equals, false)
1012- f, e = cs.connectedStateStep()
1013+ f, e = cs.step()
1014 c.Check(e, IsNil)
1015 c.Check(f, Equals, true)
1016
1017@@ -230,7 +275,7 @@
1018 webget_p = condition.Fail2Work(1)
1019 ch <- networkmanager.Disconnected
1020 ch <- networkmanager.ConnectedGlobal
1021- f, e = cs.connectedStateStep()
1022+ f, e = cs.step()
1023 c.Check(e, IsNil)
1024 c.Check(f, Equals, false) // first false is from the Disconnected
1025
1026@@ -239,7 +284,7 @@
1027 _t := time.NewTimer(recheck_timeout / 2)
1028
1029 go func() {
1030- f, e := cs.connectedStateStep()
1031+ f, e := cs.step()
1032 c.Check(e, IsNil)
1033 _ch <- f
1034 }()
1035@@ -257,15 +302,15 @@
1036 ch <- networkmanager.Disconnected // this should not
1037 ch <- networkmanager.ConnectedGlobal // this should trigger a 'true'
1038
1039- f, e = cs.connectedStateStep()
1040+ f, e = cs.step()
1041 c.Check(e, IsNil)
1042 c.Check(f, Equals, false)
1043- f, e = cs.connectedStateStep()
1044+ f, e = cs.step()
1045 c.Check(e, IsNil)
1046 c.Check(f, Equals, true)
1047
1048 close(ch) // this should make it error out
1049- _, e = cs.connectedStateStep()
1050+ _, e = cs.step()
1051 c.Check(e, NotNil)
1052 }
1053
1054@@ -285,32 +330,50 @@
1055 }
1056
1057 endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true),
1058- uint32(networkmanager.ConnectedGlobal),
1059- uint32(networkmanager.Disconnected),
1060+ uint32(networkmanager.Disconnected),
1061+ helloCon,
1062+ uint32(networkmanager.Disconnected),
1063+ helloCon,
1064 )
1065
1066- watchTicker := make(chan bool)
1067- testingbus.SetWatchTicker(endp, watchTicker)
1068+ watchTicker := make(chan []interface{})
1069+ testingbus.SetWatchSource(endp, "StateChanged", watchTicker)
1070+ nopTicker := make(chan []interface{})
1071+ testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
1072
1073 out := make(chan bool)
1074 dt := time.Second / 10
1075 timer := time.NewTimer(dt)
1076- go ConnectedState(endp, cfg, s.log, out)
1077+ cs := New(endp, cfg, s.log)
1078+ defer cs.Cancel()
1079+ go cs.Track(out)
1080 var v bool
1081 expecteds := []struct {
1082- p bool
1083- s string
1084- n int
1085+ p bool
1086+ s string
1087+ todo string
1088 }{
1089- {false, "first state is always false", 0},
1090- {true, "then it should be true as per ConnectedGlobal above", 0},
1091- {false, "then it should be false (Disconnected)", 2},
1092- {false, "then it should be false again because it's restarted", 2},
1093+ {false, "first state is always false", ""},
1094+ {true, "then it should be true as per ConnectedGlobal above", "ConnectedGlobal"},
1095+ {false, "then it should be false (Disconnected)", "Disconnected"},
1096+ {false, "then it should be false again because it's restarted", "close"},
1097 }
1098
1099+ defer func() {
1100+ if watchTicker != nil {
1101+ close(watchTicker)
1102+ }
1103+ }()
1104+ defer close(nopTicker)
1105 for i, expected := range expecteds {
1106- for j := 0; j < expected.n; j++ {
1107- watchTicker <- true
1108+ switch expected.todo {
1109+ case "ConnectedGlobal":
1110+ watchTicker <- []interface{}{uint32(networkmanager.ConnectedGlobal)}
1111+ case "Disconnected":
1112+ watchTicker <- []interface{}{uint32(networkmanager.Disconnected)}
1113+ case "close":
1114+ close(watchTicker)
1115+ watchTicker = nil
1116 }
1117 timer.Reset(dt)
1118 select {
1119@@ -335,31 +398,41 @@
1120
1121 endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true),
1122 uint32(networkmanager.ConnectedGlobal),
1123- map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("hello")}},
1124+ helloCon,
1125 )
1126
1127- watchTicker := make(chan bool)
1128- testingbus.SetWatchTicker(endp, watchTicker)
1129+ watchTicker := make(chan []interface{})
1130+ testingbus.SetWatchSource(endp, "PropertiesChanged", watchTicker)
1131+ nopTicker := make(chan []interface{})
1132+ testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
1133
1134 out := make(chan bool)
1135 dt := time.Second / 10
1136 timer := time.NewTimer(dt)
1137- go ConnectedState(endp, cfg, s.log, out)
1138+ cs := New(endp, cfg, s.log)
1139+ defer cs.Cancel()
1140+ go cs.Track(out)
1141 var v bool
1142 expecteds := []struct {
1143- p bool
1144- s string
1145- n int
1146+ p bool
1147+ s string
1148+ changedConn bool
1149 }{
1150- {false, "first state is always false", 0},
1151- {true, "then it should be true as per ConnectedGlobal above", 0},
1152- {false, "then, false (PrimaryConnection changed)", 2},
1153- {true, "then it should be true (webcheck passed)", 0},
1154+ {false, "first state is always false", false},
1155+ {true, "then it should be true as per ConnectedGlobal above", false},
1156+ {false, "then, false (PrimaryConnection changed)", true},
1157+ {true, "then it should be true (webcheck passed)", false},
1158 }
1159
1160+ defer func() {
1161+ if watchTicker != nil {
1162+ close(watchTicker)
1163+ }
1164+ }()
1165+ defer close(nopTicker)
1166 for i, expected := range expecteds {
1167- for j := 0; j < expected.n; j++ {
1168- watchTicker <- true
1169+ if expected.changedConn {
1170+ watchTicker <- []interface{}{helloConProps}
1171 }
1172 timer.Reset(dt)
1173 select {
1174
1175=== modified file 'bus/connectivity/webchecker.go'
1176--- bus/connectivity/webchecker.go 2015-02-05 13:38:31 +0000
1177+++ bus/connectivity/webchecker.go 2015-03-06 13:26:11 +0000
1178@@ -1,5 +1,5 @@
1179 /*
1180- Copyright 2013-2014 Canonical Ltd.
1181+ Copyright 2013-2015 Canonical Ltd.
1182
1183 This program is free software: you can redistribute it and/or modify it
1184 under the terms of the GNU General Public License version 3, as published
1185@@ -40,6 +40,8 @@
1186 // contents match the target. If so, then it sends true; if anything
1187 // fails, it sends false.
1188 Webcheck(chan<- bool)
1189+ // Close idle connections.
1190+ Close()
1191 }
1192
1193 type webchecker struct {
1194@@ -89,3 +91,7 @@
1195 ch <- false
1196 }
1197 }
1198+
1199+func (wb *webchecker) Close() {
1200+ wb.cli.Transport.(*http13.Transport).CloseIdleConnections()
1201+}
1202
1203=== modified file 'bus/connectivity/webchecker_test.go'
1204--- bus/connectivity/webchecker_test.go 2015-02-05 13:38:31 +0000
1205+++ bus/connectivity/webchecker_test.go 2015-03-06 13:26:11 +0000
1206@@ -1,5 +1,5 @@
1207 /*
1208- Copyright 2013-2014 Canonical Ltd.
1209+ Copyright 2013-2015 Canonical Ltd.
1210
1211 This program is free software: you can redistribute it and/or modify it
1212 under the terms of the GNU General Public License version 3, as published
1213@@ -81,6 +81,7 @@
1214 defer ts.Close()
1215
1216 ck := NewWebchecker(ts.URL, staticHash, 5*time.Second, s.log)
1217+ defer ck.Close()
1218 ch := make(chan bool, 1)
1219 ck.Webcheck(ch)
1220 c.Check(<-ch, Equals, true)
1221@@ -89,6 +90,7 @@
1222 // Webchecker sends false if the download fails.
1223 func (s *WebcheckerSuite) TestActualFails(c *C) {
1224 ck := NewWebchecker("garbage://", "", 5*time.Second, s.log)
1225+ defer ck.Close()
1226 ch := make(chan bool, 1)
1227 ck.Webcheck(ch)
1228 c.Check(<-ch, Equals, false)
1229@@ -100,6 +102,7 @@
1230 defer ts.Close()
1231
1232 ck := NewWebchecker(ts.URL, staticHash, 5*time.Second, s.log)
1233+ defer ck.Close()
1234 ch := make(chan bool, 1)
1235 ck.Webcheck(ch)
1236 c.Check(<-ch, Equals, false)
1237@@ -112,6 +115,7 @@
1238 defer ts.Close()
1239
1240 ck := NewWebchecker(ts.URL, bigHash, 5*time.Second, s.log)
1241+ defer ck.Close()
1242 ch := make(chan bool, 1)
1243 ck.Webcheck(ch)
1244 c.Check(<-ch, Equals, false)
1245@@ -131,6 +135,7 @@
1246 }()
1247
1248 ck := NewWebchecker(ts.URL, bigHash, time.Second, s.log)
1249+ defer ck.Close()
1250 ch := make(chan bool, 1)
1251 ck.Webcheck(ch)
1252 c.Check(<-ch, Equals, false)
1253
1254=== modified file 'bus/endpoint.go'
1255--- bus/endpoint.go 2015-01-22 09:52:07 +0000
1256+++ bus/endpoint.go 2015-03-06 13:26:11 +0000
1257@@ -35,10 +35,15 @@
1258 type BusMethod func(string, []interface{}, []interface{}) ([]interface{}, error)
1259 type DispatchMap map[string]BusMethod
1260
1261+// Cancellable can be canceled.
1262+type Cancellable interface {
1263+ Cancel() error
1264+}
1265+
1266 // bus.Endpoint represents the DBus connection itself.
1267 type Endpoint interface {
1268 GrabName(allowReplacement bool) <-chan error
1269- WatchSignal(member string, f func(...interface{}), d func()) error
1270+ WatchSignal(member string, f func(...interface{}), d func()) (Cancellable, error)
1271 WatchMethod(DispatchMap, string, ...interface{})
1272 Signal(string, string, []interface{}) error
1273 Call(member string, args []interface{}, rvs ...interface{}) error
1274@@ -123,16 +128,16 @@
1275 // sends the values over a channel, and d() would close the channel.
1276 //
1277 // XXX: untested
1278-func (endp *endpoint) WatchSignal(member string, f func(...interface{}), d func()) error {
1279+func (endp *endpoint) WatchSignal(member string, f func(...interface{}), d func()) (Cancellable, error) {
1280 watch, err := endp.proxy.WatchSignal(endp.addr.Interface, member)
1281 if err != nil {
1282 endp.log.Debugf("failed to set up the watch: %s", err)
1283- return err
1284+ return nil, err
1285 }
1286
1287 go endp.unpackMessages(watch, f, d, member)
1288
1289- return nil
1290+ return watch, nil
1291 }
1292
1293 // Call() invokes the provided member method (on the name, path and
1294@@ -324,6 +329,6 @@
1295 }
1296 f(endp.unpackOneMsg(msg, member)...)
1297 }
1298- endp.log.Errorf("got not-OK from %s watch", member)
1299+ endp.log.Debugf("got not-OK from %s watch", member)
1300 d()
1301 }
1302
1303=== modified file 'bus/haptic/haptic.go'
1304--- bus/haptic/haptic.go 2014-08-08 01:07:38 +0000
1305+++ bus/haptic/haptic.go 2015-03-06 13:26:11 +0000
1306@@ -20,6 +20,7 @@
1307
1308 import (
1309 "launchpad.net/ubuntu-push/bus"
1310+ "launchpad.net/ubuntu-push/bus/accounts"
1311 "launchpad.net/ubuntu-push/click"
1312 "launchpad.net/ubuntu-push/launch_helper"
1313 "launchpad.net/ubuntu-push/logger"
1314@@ -36,12 +37,13 @@
1315 type Haptic struct {
1316 bus bus.Endpoint
1317 log logger.Logger
1318+ acc accounts.Accounts
1319 fallback *launch_helper.Vibration
1320 }
1321
1322 // New returns a new Haptic that'll use the provided bus.Endpoint
1323-func New(endp bus.Endpoint, log logger.Logger, fallback *launch_helper.Vibration) *Haptic {
1324- return &Haptic{endp, log, fallback}
1325+func New(endp bus.Endpoint, log logger.Logger, acc accounts.Accounts, fallback *launch_helper.Vibration) *Haptic {
1326+ return &Haptic{endp, log, acc, fallback}
1327 }
1328
1329 // Present presents the notification via a vibrate pattern
1330@@ -50,6 +52,11 @@
1331 panic("please check notification is not nil before calling present")
1332 }
1333
1334+ if !haptic.acc.Vibrate() {
1335+ haptic.log.Debugf("[%s] vibrate disabled by user.", nid)
1336+ return false
1337+ }
1338+
1339 vib := notification.Vibration(haptic.fallback)
1340 if vib == nil {
1341 haptic.log.Debugf("[%s] notification has no Vibrate.", nid)
1342
1343=== modified file 'bus/haptic/haptic_test.go'
1344--- bus/haptic/haptic_test.go 2014-08-08 01:07:38 +0000
1345+++ bus/haptic/haptic_test.go 2015-03-06 13:26:11 +0000
1346@@ -35,20 +35,36 @@
1347 type hapticSuite struct {
1348 log *helpers.TestLogger
1349 app *click.AppId
1350-}
1351+ acc *mockAccounts
1352+}
1353+
1354+type mockAccounts struct {
1355+ vib bool
1356+ sil bool
1357+ snd string
1358+ err error
1359+}
1360+
1361+func (m *mockAccounts) Start() error { return m.err }
1362+func (m *mockAccounts) Cancel() error { return m.err }
1363+func (m *mockAccounts) SilentMode() bool { return m.sil }
1364+func (m *mockAccounts) Vibrate() bool { return m.vib }
1365+func (m *mockAccounts) MessageSoundFile() string { return m.snd }
1366+func (m *mockAccounts) String() string { return "<mockAccounts>" }
1367
1368 var _ = Suite(&hapticSuite{})
1369
1370 func (hs *hapticSuite) SetUpTest(c *C) {
1371 hs.log = helpers.NewTestLogger(c, "debug")
1372 hs.app = clickhelp.MustParseAppId("com.example.test_test-app_0")
1373+ hs.acc = &mockAccounts{true, false, "xyzzy", nil}
1374 }
1375
1376 // checks that Present() actually calls VibratePattern
1377 func (hs *hapticSuite) TestPresentPresents(c *C) {
1378 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
1379
1380- ec := New(endp, hs.log, nil)
1381+ ec := New(endp, hs.log, hs.acc, nil)
1382 notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100], "repeat": 2}`)}
1383 c.Check(ec.Present(hs.app, "nid", &notif), Equals, true)
1384 callArgs := testibus.GetCallArgs(endp)
1385@@ -61,7 +77,7 @@
1386 func (hs *hapticSuite) TestPresentDefaultsRepeatTo1(c *C) {
1387 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
1388
1389- ec := New(endp, hs.log, nil)
1390+ ec := New(endp, hs.log, hs.acc, nil)
1391 // note: no Repeat:
1392 notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100]}`)}
1393 c.Check(ec.Present(hs.app, "nid", &notif), Equals, true)
1394@@ -76,7 +92,7 @@
1395 func (hs *hapticSuite) TestSkipIfMissing(c *C) {
1396 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
1397
1398- ec := New(endp, hs.log, nil)
1399+ ec := New(endp, hs.log, hs.acc, nil)
1400 // no Vibration in the notificaton
1401 c.Check(ec.Present(hs.app, "", &launch_helper.Notification{}), Equals, false)
1402 // empty Vibration
1403@@ -85,11 +101,24 @@
1404 c.Check(ec.Present(hs.app, "", &launch_helper.Notification{RawVibration: json.RawMessage(`{}`)}), Equals, false)
1405 }
1406
1407+// check that Present() does not present if the accounts' Vibrate() returns false
1408+func (hs *hapticSuite) TestPresentSkipsIfVibrateDisabled(c *C) {
1409+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
1410+ fallback := &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2}
1411+
1412+ ec := New(endp, hs.log, hs.acc, fallback)
1413+ notif := launch_helper.Notification{RawVibration: json.RawMessage(`true`)}
1414+ c.Assert(ec.Present(hs.app, "nid", &notif), Equals, true)
1415+ // ok!
1416+ hs.acc.vib = false
1417+ c.Check(ec.Present(hs.app, "nid", &notif), Equals, false)
1418+}
1419+
1420 // check that Present() panics if the notification is nil
1421 func (hs *hapticSuite) TestPanicsIfNil(c *C) {
1422 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
1423
1424- ec := New(endp, hs.log, nil)
1425+ ec := New(endp, hs.log, hs.acc, nil)
1426 // no notification at all
1427 c.Check(func() { ec.Present(hs.app, "", nil) }, Panics, `please check notification is not nil before calling present`)
1428 }
1429@@ -99,7 +128,7 @@
1430 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
1431 fallback := &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2}
1432
1433- ec := New(endp, hs.log, fallback)
1434+ ec := New(endp, hs.log, hs.acc, fallback)
1435 notif := launch_helper.Notification{RawVibration: json.RawMessage(`false`)}
1436 c.Check(ec.Present(hs.app, "nid", &notif), Equals, false)
1437 notif = launch_helper.Notification{RawVibration: json.RawMessage(`true`)}
1438
1439=== modified file 'bus/networkmanager/networkmanager.go'
1440--- bus/networkmanager/networkmanager.go 2015-01-22 09:52:07 +0000
1441+++ bus/networkmanager/networkmanager.go 2015-03-06 13:26:11 +0000
1442@@ -42,13 +42,13 @@
1443 GetState() State
1444 // WatchState listens for changes to NetworkManager's state, and sends
1445 // them out over the channel returned.
1446- WatchState() (<-chan State, error)
1447+ WatchState() (<-chan State, bus.Cancellable, error)
1448 // GetPrimaryConnection fetches and returns NetworkManager's current
1449 // primary connection.
1450 GetPrimaryConnection() string
1451 // WatchPrimaryConnection listens for changes of NetworkManager's
1452 // Primary Connection, and sends it out over the channel returned.
1453- WatchPrimaryConnection() (<-chan string, error)
1454+ WatchPrimaryConnection() (<-chan string, bus.Cancellable, error)
1455 }
1456
1457 type networkManager struct {
1458@@ -85,9 +85,9 @@
1459 return State(v)
1460 }
1461
1462-func (nm *networkManager) WatchState() (<-chan State, error) {
1463+func (nm *networkManager) WatchState() (<-chan State, bus.Cancellable, error) {
1464 ch := make(chan State)
1465- err := nm.bus.WatchSignal("StateChanged",
1466+ w, err := nm.bus.WatchSignal("StateChanged",
1467 func(ns ...interface{}) {
1468 stint, ok := ns[0].(uint32)
1469 if !ok {
1470@@ -101,10 +101,10 @@
1471 func() { close(ch) })
1472 if err != nil {
1473 nm.log.Debugf("Failed to set up the watch: %s", err)
1474- return nil, err
1475+ return nil, nil, err
1476 }
1477
1478- return ch, nil
1479+ return ch, w, nil
1480 }
1481
1482 func (nm *networkManager) GetPrimaryConnection() string {
1483@@ -124,9 +124,9 @@
1484 return string(v)
1485 }
1486
1487-func (nm *networkManager) WatchPrimaryConnection() (<-chan string, error) {
1488+func (nm *networkManager) WatchPrimaryConnection() (<-chan string, bus.Cancellable, error) {
1489 ch := make(chan string)
1490- err := nm.bus.WatchSignal("PropertiesChanged",
1491+ w, err := nm.bus.WatchSignal("PropertiesChanged",
1492 func(ppsi ...interface{}) {
1493 pps, ok := ppsi[0].(map[string]dbus.Variant)
1494 if !ok {
1495@@ -147,8 +147,8 @@
1496 }, func() { close(ch) })
1497 if err != nil {
1498 nm.log.Debugf("failed to set up the watch: %s", err)
1499- return nil, err
1500+ return nil, nil, err
1501 }
1502
1503- return ch, nil
1504+ return ch, w, nil
1505 }
1506
1507=== modified file 'bus/networkmanager/networkmanager_test.go'
1508--- bus/networkmanager/networkmanager_test.go 2014-04-04 12:01:42 +0000
1509+++ bus/networkmanager/networkmanager_test.go 2015-03-06 13:26:11 +0000
1510@@ -90,8 +90,9 @@
1511 func (s *NMSuite) TestWatchState(c *C) {
1512 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), uint32(Unknown), uint32(Asleep), uint32(ConnectedGlobal))
1513 nm := New(tc, s.log)
1514- ch, err := nm.WatchState()
1515- c.Check(err, IsNil)
1516+ ch, w, err := nm.WatchState()
1517+ c.Assert(err, IsNil)
1518+ defer w.Cancel()
1519 l := []State{<-ch, <-ch, <-ch}
1520 c.Check(l, DeepEquals, []State{Unknown, Asleep, ConnectedGlobal})
1521 }
1522@@ -99,7 +100,7 @@
1523 // WatchState returns on error if the dbus call fails
1524 func (s *NMSuite) TestWatchStateFails(c *C) {
1525 nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log)
1526- _, err := nm.WatchState()
1527+ _, _, err := nm.WatchState()
1528 c.Check(err, NotNil)
1529 }
1530
1531@@ -107,8 +108,9 @@
1532 func (s *NMSuite) TestWatchStateClosesOnWatchBail(c *C) {
1533 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true))
1534 nm := New(tc, s.log)
1535- ch, err := nm.WatchState()
1536- c.Check(err, IsNil)
1537+ ch, w, err := nm.WatchState()
1538+ c.Assert(err, IsNil)
1539+ defer w.Cancel()
1540 _, ok := <-ch
1541 c.Check(ok, Equals, false)
1542 }
1543@@ -117,8 +119,9 @@
1544 func (s *NMSuite) TestWatchStateSurvivesRubbishValues(c *C) {
1545 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "a")
1546 nm := New(tc, s.log)
1547- ch, err := nm.WatchState()
1548- c.Check(err, IsNil)
1549+ ch, w, err := nm.WatchState()
1550+ c.Assert(err, IsNil)
1551+ defer w.Cancel()
1552 _, ok := <-ch
1553 c.Check(ok, Equals, false)
1554 }
1555@@ -164,8 +167,9 @@
1556 mkPriConMap("/b/2"),
1557 mkPriConMap("/c/3"))
1558 nm := New(tc, s.log)
1559- ch, err := nm.WatchPrimaryConnection()
1560- c.Check(err, IsNil)
1561+ ch, w, err := nm.WatchPrimaryConnection()
1562+ c.Assert(err, IsNil)
1563+ defer w.Cancel()
1564 l := []string{<-ch, <-ch, <-ch}
1565 c.Check(l, DeepEquals, []string{"/a/1", "/b/2", "/c/3"})
1566 }
1567@@ -173,7 +177,7 @@
1568 // WatchPrimaryConnection returns on error if the dbus call fails
1569 func (s *NMSuite) TestWatchPrimaryConnectionFails(c *C) {
1570 nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log)
1571- _, err := nm.WatchPrimaryConnection()
1572+ _, _, err := nm.WatchPrimaryConnection()
1573 c.Check(err, NotNil)
1574 }
1575
1576@@ -181,8 +185,9 @@
1577 func (s *NMSuite) TestWatchPrimaryConnectionClosesOnWatchBail(c *C) {
1578 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true))
1579 nm := New(tc, s.log)
1580- ch, err := nm.WatchPrimaryConnection()
1581- c.Check(err, IsNil)
1582+ ch, w, err := nm.WatchPrimaryConnection()
1583+ c.Assert(err, IsNil)
1584+ defer w.Cancel()
1585 _, ok := <-ch
1586 c.Check(ok, Equals, false)
1587 }
1588@@ -191,8 +196,9 @@
1589 func (s *NMSuite) TestWatchPrimaryConnectionSurvivesRubbishValues(c *C) {
1590 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "a")
1591 nm := New(tc, s.log)
1592- ch, err := nm.WatchPrimaryConnection()
1593+ ch, w, err := nm.WatchPrimaryConnection()
1594 c.Assert(err, IsNil)
1595+ defer w.Cancel()
1596 _, ok := <-ch
1597 c.Check(ok, Equals, false)
1598 }
1599@@ -204,8 +210,9 @@
1600 map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("42")}},
1601 )
1602 nm := New(tc, s.log)
1603- ch, err := nm.WatchPrimaryConnection()
1604+ ch, w, err := nm.WatchPrimaryConnection()
1605 c.Assert(err, IsNil)
1606+ defer w.Cancel()
1607 v, ok := <-ch
1608 c.Check(ok, Equals, true)
1609 c.Check(v, Equals, "42")
1610@@ -218,8 +225,9 @@
1611 map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("42")}},
1612 )
1613 nm := New(tc, s.log)
1614- ch, err := nm.WatchPrimaryConnection()
1615+ ch, w, err := nm.WatchPrimaryConnection()
1616 c.Assert(err, IsNil)
1617+ defer w.Cancel()
1618 v, ok := <-ch
1619 c.Check(ok, Equals, true)
1620 c.Check(v, Equals, "42")
1621
1622=== modified file 'bus/notifications/raw.go'
1623--- bus/notifications/raw.go 2015-01-22 09:52:07 +0000
1624+++ bus/notifications/raw.go 2015-03-06 13:26:11 +0000
1625@@ -93,7 +93,7 @@
1626 // and sends them over the channel provided
1627 func (raw *RawNotifications) WatchActions() (<-chan *RawAction, error) {
1628 ch := make(chan *RawAction)
1629- err := raw.bus.WatchSignal("ActionInvoked",
1630+ _, err := raw.bus.WatchSignal("ActionInvoked",
1631 func(ns ...interface{}) {
1632 if len(ns) != 2 {
1633 raw.log.Debugf("ActionInvoked delivered %d things instead of 2", len(ns))
1634
1635=== modified file 'bus/notifications/raw_test.go'
1636--- bus/notifications/raw_test.go 2014-08-15 10:33:04 +0000
1637+++ bus/notifications/raw_test.go 2015-03-06 13:26:11 +0000
1638@@ -111,14 +111,16 @@
1639 errstr string
1640 endp bus.Endpoint
1641 works bool
1642+ src chan []interface{}
1643 }
1644
1645 func (s *RawSuite) TestWatchActionsToleratesDBusWeirdness(c *C) {
1646 X := func(errstr string, args ...interface{}) tst {
1647- endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), args)
1648- // stop the endpoint from closing the channel:
1649- testibus.SetWatchTicker(endp, make(chan bool))
1650- return tst{errstr, endp, errstr == ""}
1651+ endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true))
1652+ src := make(chan []interface{}, 1)
1653+ testibus.SetWatchSource(endp, "ActionInvoked", src)
1654+ src <- args
1655+ return tst{errstr, endp, errstr == "", src}
1656 }
1657
1658 ts := []tst{
1659@@ -146,6 +148,7 @@
1660 }
1661 c.Check(s.log.Captured(), Matches, `(?ms).*`+t.errstr+`.*`)
1662 s.log.ResetCapture()
1663+ close(t.src)
1664 }
1665
1666 }
1667
1668=== modified file 'bus/testing/testing_endpoint.go'
1669--- bus/testing/testing_endpoint.go 2015-01-29 09:48:40 +0000
1670+++ bus/testing/testing_endpoint.go 2015-03-06 13:26:11 +0000
1671@@ -36,13 +36,15 @@
1672 }
1673
1674 type testingEndpoint struct {
1675- dialCond condition.Interface
1676- callCond condition.Interface
1677- retvals [][]interface{}
1678- watchTicker chan bool
1679- watchLck sync.RWMutex
1680- callArgs []callArgs
1681- callArgsLck sync.RWMutex
1682+ dialCond condition.Interface
1683+ callCond condition.Interface
1684+ usedLck sync.Mutex
1685+ used int
1686+ retvals [][]interface{}
1687+ watchSources map[string]chan []interface{}
1688+ watchLck sync.RWMutex
1689+ callArgs []callArgs
1690+ callArgsLck sync.RWMutex
1691 }
1692
1693 // Build a bus.Endpoint that calls OK() on its condition before returning
1694@@ -51,7 +53,7 @@
1695 // NOTE: Call() always returns the first return value; Watch() will provide
1696 // each of them in turn, irrespective of whether Call has been called.
1697 func NewMultiValuedTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvalses ...[]interface{}) bus.Endpoint {
1698- return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses}
1699+ return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses, watchSources: make(map[string]chan []interface{})}
1700 }
1701
1702 func NewTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvals ...interface{}) bus.Endpoint {
1703@@ -59,15 +61,15 @@
1704 for i, x := range retvals {
1705 retvalses[i] = []interface{}{x}
1706 }
1707- return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses}
1708+ return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses, watchSources: make(map[string]chan []interface{})}
1709 }
1710
1711-// If SetWatchTicker is called with a non-nil watchTicker, it is used
1712-// instead of the default timeout to wait while sending values over
1713-// WatchSignal. Set it to nil again to restore default behaviour.
1714-func SetWatchTicker(tc bus.Endpoint, watchTicker chan bool) {
1715+// If SetWatchSource is called with a non-nil watchSource, it is used
1716+// instead of the default timeout and retvals to get values to send
1717+// over WatchSignal. Set it to nil again to restore default behaviour.
1718+func SetWatchSource(tc bus.Endpoint, member string, watchSource chan []interface{}) {
1719 tc.(*testingEndpoint).watchLck.Lock()
1720- tc.(*testingEndpoint).watchTicker = watchTicker
1721+ tc.(*testingEndpoint).watchSources[member] = watchSource
1722 tc.(*testingEndpoint).watchLck.Unlock()
1723 }
1724
1725@@ -78,31 +80,77 @@
1726 return tc.(*testingEndpoint).callArgs
1727 }
1728
1729+type watchCancel struct {
1730+ done chan struct{}
1731+ cancelled chan struct{}
1732+ lck sync.Mutex
1733+ member string
1734+}
1735+
1736+// this waits for actual cancelllation for test convenience
1737+func (wc *watchCancel) Cancel() error {
1738+ wc.lck.Lock()
1739+ defer wc.lck.Unlock()
1740+ if wc.cancelled != nil {
1741+ close(wc.cancelled)
1742+ wc.cancelled = nil
1743+ <-wc.done
1744+ }
1745+ return nil
1746+}
1747+
1748 // See Endpoint's WatchSignal. This WatchSignal will check its condition to
1749 // decide whether to return an error, or provide each of its return values
1750-func (tc *testingEndpoint) WatchSignal(member string, f func(...interface{}), d func()) error {
1751+// or values from the previously set watchSource for member.
1752+func (tc *testingEndpoint) WatchSignal(member string, f func(...interface{}), d func()) (bus.Cancellable, error) {
1753 if tc.callCond.OK() {
1754+ cancelled := make(chan struct{})
1755+ done := make(chan struct{})
1756 go func() {
1757- for _, v := range tc.retvals {
1758- f(v...)
1759- tc.watchLck.RLock()
1760- ticker := tc.watchTicker
1761- tc.watchLck.RUnlock()
1762- if ticker != nil {
1763- _, ok := <-ticker
1764+ tc.watchLck.RLock()
1765+ source := tc.watchSources[member]
1766+ tc.watchLck.RUnlock()
1767+ if source == nil {
1768+ tc.usedLck.Lock()
1769+ idx := tc.used
1770+ tc.used++
1771+ tc.usedLck.Unlock()
1772+ source = make(chan []interface{})
1773+ go func() {
1774+ Feed:
1775+ for _, v := range tc.retvals[idx:] {
1776+ select {
1777+ case source <- v:
1778+ case <-cancelled:
1779+ break Feed
1780+ }
1781+ select {
1782+ case <-time.After(10 * time.Millisecond):
1783+ case <-cancelled:
1784+ break Feed
1785+ }
1786+ }
1787+ close(source)
1788+ }()
1789+ }
1790+ Receive:
1791+ for {
1792+ select {
1793+ case v, ok := <-source:
1794 if !ok {
1795- // bail out
1796- return
1797+ break Receive
1798 }
1799- } else {
1800- time.Sleep(10 * time.Millisecond)
1801+ f(v...)
1802+ case <-cancelled:
1803+ break Receive
1804 }
1805 }
1806 d()
1807+ close(done)
1808 }()
1809- return nil
1810+ return &watchCancel{cancelled: cancelled, done: done, member: member}, nil
1811 } else {
1812- return errors.New("no way")
1813+ return nil, errors.New("no way")
1814 }
1815 }
1816
1817@@ -116,20 +164,24 @@
1818 if tc.callCond.OK() {
1819 expected := len(rvs)
1820 var provided int
1821- if len(tc.retvals) == 0 {
1822+ tc.usedLck.Lock()
1823+ idx := tc.used
1824+ tc.used++
1825+ tc.usedLck.Unlock()
1826+ if len(tc.retvals) <= idx {
1827 if expected != 0 {
1828 panic("No return values provided!")
1829 }
1830 provided = 0
1831 } else {
1832- provided = len(tc.retvals[0])
1833+ provided = len(tc.retvals[idx])
1834 }
1835 if provided != expected {
1836 return errors.New("provided/expected return vals mismatch")
1837 }
1838 if provided != 0 {
1839 x := dbus.NewMethodCallMessage("", "", "", "")
1840- err := x.AppendArgs(tc.retvals[0]...)
1841+ err := x.AppendArgs(tc.retvals[idx]...)
1842 if err != nil {
1843 return err
1844 }
1845
1846=== modified file 'bus/testing/testing_endpoint_test.go'
1847--- bus/testing/testing_endpoint_test.go 2014-07-04 23:00:42 +0000
1848+++ bus/testing/testing_endpoint_test.go 2015-03-06 13:26:11 +0000
1849@@ -17,11 +17,13 @@
1850 package testing
1851
1852 import (
1853+ "testing"
1854+ "time"
1855+
1856 . "launchpad.net/gocheck"
1857+
1858 "launchpad.net/ubuntu-push/bus"
1859 "launchpad.net/ubuntu-push/testing/condition"
1860- "testing"
1861- "time"
1862 )
1863
1864 // hook up gocheck
1865@@ -100,8 +102,9 @@
1866 var m, n uint32 = 42, 17
1867 endp := NewTestingEndpoint(nil, condition.Work(true), m, n)
1868 ch := make(chan uint32)
1869- e := endp.WatchSignal("what", func(us ...interface{}) { ch <- us[0].(uint32) }, func() { close(ch) })
1870- c.Check(e, IsNil)
1871+ w, e := endp.WatchSignal("which", func(us ...interface{}) { ch <- us[0].(uint32) }, func() { close(ch) })
1872+ c.Assert(e, IsNil)
1873+ defer w.Cancel()
1874 c.Check(<-ch, Equals, m)
1875 c.Check(<-ch, Equals, n)
1876 }
1877@@ -110,8 +113,9 @@
1878 func (s *TestingEndpointSuite) TestWatchDestructor(c *C) {
1879 endp := NewTestingEndpoint(nil, condition.Work(true))
1880 ch := make(chan uint32)
1881- e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
1882- c.Check(e, IsNil)
1883+ w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
1884+ c.Assert(e, IsNil)
1885+ defer w.Cancel()
1886 _, ok := <-ch
1887 c.Check(ok, Equals, false)
1888 }
1889@@ -130,25 +134,28 @@
1890 // Test that WatchSignal() with a negative condition returns an error.
1891 func (s *TestingEndpointSuite) TestWatchFails(c *C) {
1892 endp := NewTestingEndpoint(nil, condition.Work(false))
1893- e := endp.WatchSignal("what", func(us ...interface{}) {}, func() {})
1894+ w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() {})
1895 c.Check(e, NotNil)
1896+ c.Check(w, IsNil)
1897 }
1898
1899-// Test WatchSignal can use the WatchTicker instead of a timeout (if
1900+// Test WatchSignal can use a watchSource instead of a timeout and retvals (if
1901 // the former is not nil)
1902-func (s *TestingEndpointSuite) TestWatchTicker(c *C) {
1903- watchTicker := make(chan bool, 3)
1904- watchTicker <- true
1905- watchTicker <- true
1906- watchTicker <- true
1907+func (s *TestingEndpointSuite) TestWatchSources(c *C) {
1908+ watchTicker := make(chan []interface{}, 3)
1909+ watchTicker <- []interface{}{true}
1910+ watchTicker <- []interface{}{true}
1911+ watchTicker <- []interface{}{true}
1912 c.Assert(len(watchTicker), Equals, 3)
1913
1914 endp := NewTestingEndpoint(nil, condition.Work(true), 0, 0)
1915- SetWatchTicker(endp, watchTicker)
1916+ SetWatchSource(endp, "what", watchTicker)
1917 ch := make(chan int)
1918- e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
1919- c.Check(e, IsNil)
1920+ w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
1921+ c.Assert(e, IsNil)
1922+ defer w.Cancel()
1923
1924+ close(watchTicker)
1925 // wait for the destructor to be called
1926 select {
1927 case <-time.Tick(10 * time.Millisecond):
1928@@ -156,8 +163,21 @@
1929 case <-ch:
1930 }
1931
1932- // now if all went well, the ticker will have been tuck twice.
1933- c.Assert(len(watchTicker), Equals, 1)
1934+ // now if all went well, the ticker will have been exhausted.
1935+ c.Assert(len(watchTicker), Equals, 0)
1936+}
1937+
1938+// Test that WatchSignal() calls the destructor callback when canceled.
1939+func (s *TestingEndpointSuite) TestWatchCancel(c *C) {
1940+ endp := NewTestingEndpoint(nil, condition.Work(true))
1941+ ch := make(chan uint32)
1942+ w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
1943+ c.Assert(e, IsNil)
1944+ defer w.Cancel()
1945+ SetWatchSource(endp, "what", make(chan []interface{}))
1946+ w.Cancel()
1947+ _, ok := <-ch
1948+ c.Check(ok, Equals, false)
1949 }
1950
1951 // Tests that GetProperty() works
1952
1953=== modified file 'client/client.go'
1954--- client/client.go 2015-01-26 21:00:27 +0000
1955+++ client/client.go 2015-03-06 13:26:11 +0000
1956@@ -280,8 +280,10 @@
1957
1958 // takeTheBus starts the connection(s) to D-Bus and sets up associated event channels
1959 func (client *PushClient) takeTheBus() error {
1960- go connectivity.ConnectedState(client.connectivityEndp,
1961- client.config.ConnectivityConfig, client.log, client.connCh)
1962+ fmt.Println("FOO")
1963+ cs := connectivity.New(client.connectivityEndp,
1964+ client.config.ConnectivityConfig, client.log)
1965+ go cs.Track(client.connCh)
1966 util.NewAutoRedialer(client.systemImageEndp).Redial()
1967 sysimg := systemimage.New(client.systemImageEndp, client.log)
1968 info, err := sysimg.Info()
1969
1970=== modified file 'client/client_test.go'
1971--- client/client_test.go 2015-02-06 13:09:16 +0000
1972+++ client/client_test.go 2015-03-06 13:26:11 +0000
1973@@ -31,6 +31,7 @@
1974 "testing"
1975 "time"
1976
1977+ "launchpad.net/go-dbus/v1"
1978 . "launchpad.net/gocheck"
1979
1980 "launchpad.net/ubuntu-push/accounts"
1981@@ -205,12 +206,7 @@
1982 }
1983
1984 func (cs *clientSuite) TearDownTest(c *C) {
1985- //fmt.Println("GOROUTINE# ", runtime.NumGoroutine())
1986- /*
1987- var x [16*1024]byte
1988- sz := runtime.Stack(x[:], true)
1989- fmt.Println(string(x[:sz]))
1990- */
1991+ //helpers.DumpGoroutines()
1992 }
1993
1994 type sqlientSuite struct{ clientSuite }
1995@@ -657,13 +653,19 @@
1996 // testing endpoints
1997 cCond := condition.Fail2Work(7)
1998 cEndp := testibus.NewTestingEndpoint(cCond, condition.Work(true),
1999- uint32(networkmanager.ConnectedGlobal),
2000+ uint32(networkmanager.Connecting),
2001+ dbus.ObjectPath("hello"),
2002+ uint32(networkmanager.Connecting),
2003+ dbus.ObjectPath("hello"),
2004 )
2005 siCond := condition.Fail2Work(2)
2006 siEndp := testibus.NewMultiValuedTestingEndpoint(siCond, condition.Work(true), []interface{}{int32(101), "mako", "daily", "Unknown", map[string]string{}})
2007- tickerCh := make(chan bool)
2008- testibus.SetWatchTicker(cEndp, tickerCh)
2009+ tickerCh := make(chan []interface{})
2010+ nopTickerCh := make(chan []interface{})
2011+ testibus.SetWatchSource(cEndp, "StateChanged", tickerCh)
2012+ testibus.SetWatchSource(cEndp, "PropertiesChanged", nopTickerCh)
2013 defer close(tickerCh)
2014+ defer close(nopTickerCh)
2015 // ok, create the thing
2016 cli := NewPushClient(cs.configPath, cs.leveldbPath)
2017 cli.log = cs.log
2018@@ -679,6 +681,7 @@
2019 c.Assert(cli.takeTheBus(), IsNil)
2020
2021 c.Check(takeNextBool(cli.connCh), Equals, false)
2022+ tickerCh <- []interface{}{uint32(networkmanager.ConnectedGlobal)}
2023 c.Check(takeNextBool(cli.connCh), Equals, true)
2024 // the connectivity endpoint retried until connected
2025 c.Check(cCond.OK(), Equals, true)
2026
2027=== modified file 'client/service/postal.go'
2028--- client/service/postal.go 2015-01-21 17:21:42 +0000
2029+++ client/service/postal.go 2015-03-06 13:26:11 +0000
2030@@ -24,6 +24,7 @@
2031 "code.google.com/p/go-uuid/uuid"
2032
2033 "launchpad.net/ubuntu-push/bus"
2034+ "launchpad.net/ubuntu-push/bus/accounts"
2035 "launchpad.net/ubuntu-push/bus/emblemcounter"
2036 "launchpad.net/ubuntu-push/bus/haptic"
2037 "launchpad.net/ubuntu-push/bus/notifications"
2038@@ -75,6 +76,7 @@
2039 // the endpoints are only exposed for testing from client
2040 // XXX: uncouple some more so this isn't necessary
2041 EmblemCounterEndp bus.Endpoint
2042+ AccountsEndp bus.Endpoint
2043 HapticEndp bus.Endpoint
2044 NotificationsEndp bus.Endpoint
2045 UnityGreeterEndp bus.Endpoint
2046@@ -82,6 +84,7 @@
2047 // presenters:
2048 Presenters []Presenter
2049 emblemCounter *emblemcounter.EmblemCounter
2050+ accounts accounts.Accounts
2051 haptic *haptic.Haptic
2052 notifications *notifications.RawNotifications
2053 sound *sounds.Sound
2054@@ -117,6 +120,7 @@
2055 svc.fallbackSound = setup.FallbackSound
2056 svc.NotificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, log)
2057 svc.EmblemCounterEndp = bus.SessionBus.Endpoint(emblemcounter.BusAddress, log)
2058+ svc.AccountsEndp = bus.SystemBus.Endpoint(accounts.BusAddress, log)
2059 svc.HapticEndp = bus.SessionBus.Endpoint(haptic.BusAddress, log)
2060 svc.UnityGreeterEndp = bus.SessionBus.Endpoint(unitygreeter.BusAddress, log)
2061 svc.WindowStackEndp = bus.SessionBus.Endpoint(windowstack.BusAddress, log)
2062@@ -158,8 +162,13 @@
2063 svc.urlDispatcher = urldispatcher.New(svc.Log)
2064 svc.notifications = notifications.Raw(svc.NotificationsEndp, svc.Log)
2065 svc.emblemCounter = emblemcounter.New(svc.EmblemCounterEndp, svc.Log)
2066- svc.haptic = haptic.New(svc.HapticEndp, svc.Log, svc.fallbackVibration)
2067- svc.sound = sounds.New(svc.Log, svc.fallbackSound)
2068+ svc.accounts = accounts.New(svc.AccountsEndp, svc.Log)
2069+ err = svc.accounts.Start()
2070+ if err != nil {
2071+ return err
2072+ }
2073+ svc.haptic = haptic.New(svc.HapticEndp, svc.Log, svc.accounts, svc.fallbackVibration)
2074+ svc.sound = sounds.New(svc.Log, svc.accounts, svc.fallbackSound)
2075 svc.messagingMenu = messaging.New(svc.Log)
2076 svc.Presenters = []Presenter{
2077 svc.notifications,
2078@@ -228,6 +237,7 @@
2079 }{
2080 {"notifications", svc.NotificationsEndp},
2081 {"emblemcounter", svc.EmblemCounterEndp},
2082+ {"accounts", svc.AccountsEndp},
2083 {"haptic", svc.HapticEndp},
2084 {"unitygreeter", svc.UnityGreeterEndp},
2085 {"windowstack", svc.WindowStackEndp},
2086
2087=== modified file 'client/service/postal_test.go'
2088--- client/service/postal_test.go 2014-09-09 22:54:04 +0000
2089+++ client/service/postal_test.go 2015-03-06 13:26:11 +0000
2090@@ -169,6 +169,8 @@
2091 hapticBus bus.Endpoint
2092 unityGreeterBus bus.Endpoint
2093 winStackBus bus.Endpoint
2094+ accountsBus bus.Endpoint
2095+ accountsCh chan []interface{}
2096 fakeLauncher *fakeHelperLauncher
2097 getTempDir func(string) (string, error)
2098 oldIsBlisted func(*click.AppId) bool
2099@@ -194,6 +196,7 @@
2100 ps.bus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
2101 ps.notifBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
2102 ps.counterBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
2103+ ps.accountsBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), map[string]dbus.Variant{"IncomingMessageVibrate": dbus.Variant{true}})
2104 ps.hapticBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
2105 ps.unityGreeterBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), false)
2106 ps.winStackBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{})
2107@@ -206,11 +209,15 @@
2108 tmpDir := filepath.Join(d, pkgName)
2109 return tmpDir, os.MkdirAll(tmpDir, 0700)
2110 }
2111+
2112+ ps.accountsCh = make(chan []interface{})
2113+ testibus.SetWatchSource(ps.accountsBus, "PropertiesChanged", ps.accountsCh)
2114 }
2115
2116 func (ps *postalSuite) TearDownTest(c *C) {
2117 isBlacklisted = ps.oldIsBlisted
2118 launch_helper.GetTempDir = ps.getTempDir
2119+ close(ps.accountsCh)
2120 }
2121
2122 func (ts *trivialPostalSuite) SetUpTest(c *C) {
2123@@ -227,6 +234,7 @@
2124 pst.Bus = ps.bus
2125 pst.NotificationsEndp = ps.notifBus
2126 pst.EmblemCounterEndp = ps.counterBus
2127+ pst.AccountsEndp = ps.accountsBus
2128 pst.HapticEndp = ps.hapticBus
2129 pst.UnityGreeterEndp = ps.unityGreeterBus
2130 pst.WindowStackEndp = ps.winStackBus
2131@@ -544,6 +552,7 @@
2132 svc := NewPostalService(ps.cfg, ps.log)
2133 svc.Bus = endp
2134 svc.EmblemCounterEndp = endp
2135+ svc.AccountsEndp = ps.accountsBus
2136 svc.HapticEndp = endp
2137 svc.NotificationsEndp = endp
2138 svc.UnityGreeterEndp = ps.unityGreeterBus
2139@@ -552,6 +561,10 @@
2140 svc.fallbackVibration = &launch_helper.Vibration{Pattern: []uint32{1}}
2141 c.Assert(svc.Start(), IsNil)
2142
2143+ nopTicker := make(chan []interface{})
2144+ testibus.SetWatchSource(endp, "ActionInvoked", nopTicker)
2145+ defer close(nopTicker)
2146+
2147 // Persist is false so we just check the log
2148 card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true, Persist: false}
2149 vib := json.RawMessage(`true`)
2150@@ -837,6 +850,10 @@
2151 }
2152
2153 func (ps *postalSuite) TestBlacklisted(c *C) {
2154+ ps.winStackBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{},
2155+ []windowstack.WindowsInfo{},
2156+ []windowstack.WindowsInfo{},
2157+ []windowstack.WindowsInfo{})
2158 svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
2159 svc.Start()
2160 ps.blacklisted = false
2161
2162=== modified file 'debian/changelog'
2163--- debian/changelog 2015-02-09 11:08:02 +0000
2164+++ debian/changelog 2015-03-06 13:26:11 +0000
2165@@ -1,42 +1,54 @@
2166+ubuntu-push (0.68-0ubuntu1) UNRELEASED; urgency=medium
2167+
2168+ [ John R. Lenton ]
2169+ * Use settings from org.freedesktop.Accounts for silent mode, when to
2170+ vibrate, and default sound. [client] (LP: #1426418, LP: #1427702)
2171+
2172+ [ Samuele "Yak Shaver of the Year" Pedroni ]
2173+ * Closing webchecker (avoids leaving goroutines around in testing). [client]
2174+ * WatchSignal cancelling, and connectivity exposed cancelling, make
2175+ connectivity start not leave watches behind (more goroutine
2176+ cleanup). [client]
2177+ * TestTakeTheBusWorks doesn't block anymore, fixed leaking of
2178+ webcheckers. [client]
2179+
2180+ -- John R. Lenton <john.lenton@canonical.com> Fri, 06 Mar 2015 10:50:34 +0000
2181+
2182 ubuntu-push (0.67+15.04.20150209-0ubuntu1) vivid; urgency=medium
2183
2184 [ John R. Lenton ]
2185 * Updated precommit script. [dev]
2186-
2187 * Include code examples in docs (instead of repeating). [docs]
2188+ * Cleanup and improve logging, and make log messages more
2189+ consistent. [client]
2190+ * Partially work around bug lp:1390663 in a minimally intrusive way
2191+ (real fix will have to wait). [client]
2192+ * Add an explicit check and log message for nil error on webcheck's
2193+ CopyN. [client]
2194
2195+ [ Samuele Pedroni ]
2196 * Make tests more robust in the face of go 1.3 [client, server]
2197-
2198 * Introduce StartClientAuthFlex for acceptance tests: Start a client
2199 with auth, take a devId regexp, don't check any client event; support
2200 connbroken in acceptanceclient. [server]
2201-
2202- * Cleanup and improve logging, and make log messages more
2203- consistent. [client]
2204-
2205- * Partially work around bug lp:1390663 in a minimally intrusive way
2206- (real fix will have to wait). [client]
2207-
2208+ * Clean up goroutines in tests. [client]
2209+ * Workaround gc issue with 1.3 and 32 bits. Fixes FTBFS. [client]
2210+
2211+ [ Bret Barker ]
2212 * Add SIGQUIT handler to spit out stack dumps; more logging
2213 tweaks. [client, server]
2214+ * Log line nums, enabled when logLevel = debug. [client server]
2215
2216+ [ Roberto Alsina ]
2217 * Adds a couple of buttons to exercise more APIs, version bump to
2218 0.44. [sample app]
2219
2220+ [ Guillermo Gonzalez ]
2221 * Add APIError to server/acceptance/kit that includes the body for
2222 debugging. [server]
2223-
2224 * Add DisableKeepAlives and MaxIdleConnsPerHost to the APIClient
2225 SetupClient method. [server]
2226
2227- * Clean up goroutines in tests. [client]
2228-
2229- * Add an explicit check and log message for nil error on webcheck's CopyN. [client]
2230-
2231- * Log line nums, enabled when logLevel = debug. [client server]
2232-
2233- * Workaround gc issue with 1.3 and 32 bits. Fixes FTBFS. [client]
2234-
2235 -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 09 Feb 2015 11:08:02 +0000
2236
2237 ubuntu-push (0.66+15.04.20141211-0ubuntu1) vivid; urgency=medium
2238
2239=== modified file 'sounds/sounds.go'
2240--- sounds/sounds.go 2014-08-08 09:03:42 +0000
2241+++ sounds/sounds.go 2015-03-06 13:26:11 +0000
2242@@ -25,6 +25,7 @@
2243
2244 "launchpad.net/go-xdg/v0"
2245
2246+ "launchpad.net/ubuntu-push/bus/accounts"
2247 "launchpad.net/ubuntu-push/click"
2248 "launchpad.net/ubuntu-push/launch_helper"
2249 "launchpad.net/ubuntu-push/logger"
2250@@ -33,15 +34,17 @@
2251 type Sound struct {
2252 player string
2253 log logger.Logger
2254+ acc accounts.Accounts
2255 fallback string
2256 dataDirs func() []string
2257 dataFind func(string) (string, error)
2258 }
2259
2260-func New(log logger.Logger, fallback string) *Sound {
2261+func New(log logger.Logger, acc accounts.Accounts, fallback string) *Sound {
2262 return &Sound{
2263 player: "paplay",
2264 log: log,
2265+ acc: acc,
2266 fallback: fallback,
2267 dataDirs: xdg.Data.Dirs,
2268 dataFind: xdg.Data.Find,
2269@@ -53,7 +56,17 @@
2270 panic("please check notification is not nil before calling present")
2271 }
2272
2273- sound := notification.Sound(snd.fallback)
2274+ if snd.acc.SilentMode() {
2275+ snd.log.Debugf("[%s] no sounds: silent mode on.", nid)
2276+ return false
2277+ }
2278+
2279+ fallback := snd.acc.MessageSoundFile()
2280+ if fallback == "" {
2281+ fallback = snd.fallback
2282+ }
2283+
2284+ sound := notification.Sound(fallback)
2285 if sound == "" {
2286 snd.log.Debugf("[%s] notification has no Sound: %#v", nid, sound)
2287 return false
2288
2289=== modified file 'sounds/sounds_test.go'
2290--- sounds/sounds_test.go 2014-08-08 09:03:42 +0000
2291+++ sounds/sounds_test.go 2015-03-06 13:26:11 +0000
2292@@ -36,25 +36,42 @@
2293 type soundsSuite struct {
2294 log *helpers.TestLogger
2295 app *click.AppId
2296+ acc *mockAccounts
2297 }
2298
2299 var _ = Suite(&soundsSuite{})
2300
2301+type mockAccounts struct {
2302+ vib bool
2303+ sil bool
2304+ snd string
2305+ err error
2306+}
2307+
2308+func (m *mockAccounts) Start() error { return m.err }
2309+func (m *mockAccounts) Cancel() error { return m.err }
2310+func (m *mockAccounts) SilentMode() bool { return m.sil }
2311+func (m *mockAccounts) Vibrate() bool { return m.vib }
2312+func (m *mockAccounts) MessageSoundFile() string { return m.snd }
2313+func (m *mockAccounts) String() string { return "<mockAccounts>" }
2314+
2315 func (ss *soundsSuite) SetUpTest(c *C) {
2316 ss.log = helpers.NewTestLogger(c, "debug")
2317 ss.app = clickhelp.MustParseAppId("com.example.test_test_0")
2318+ ss.acc = &mockAccounts{true, false, "", nil}
2319 }
2320
2321 func (ss *soundsSuite) TestNew(c *C) {
2322- s := New(ss.log, "foo")
2323+ s := New(ss.log, ss.acc, "foo")
2324 c.Check(s.log, Equals, ss.log)
2325 c.Check(s.player, Equals, "paplay")
2326 c.Check(s.fallback, Equals, "foo")
2327+ c.Check(s.acc, Equals, ss.acc)
2328 }
2329
2330 func (ss *soundsSuite) TestPresent(c *C) {
2331 s := &Sound{
2332- player: "echo", log: ss.log,
2333+ player: "echo", log: ss.log, acc: ss.acc,
2334 dataFind: func(s string) (string, error) { return s, nil },
2335 }
2336
2337@@ -65,7 +82,7 @@
2338
2339 func (ss *soundsSuite) TestPresentSimple(c *C) {
2340 s := &Sound{
2341- player: "echo", log: ss.log,
2342+ player: "echo", log: ss.log, acc: ss.acc,
2343 dataFind: func(s string) (string, error) { return s, nil },
2344 fallback: "fallback",
2345 }
2346@@ -73,12 +90,18 @@
2347 c.Check(s.Present(ss.app, "",
2348 &launch_helper.Notification{RawSound: json.RawMessage(`true`)}), Equals, true)
2349 c.Check(ss.log.Captured(), Matches, `(?sm).* playing sound com.example.test/fallback using echo`)
2350+ ss.acc.snd = "from-prefs"
2351+ ss.log.ResetCapture()
2352+ c.Check(s.Present(ss.app, "",
2353+ &launch_helper.Notification{RawSound: json.RawMessage(`true`)}), Equals, true)
2354+ c.Check(ss.log.Captured(), Matches, `(?sm).* playing sound com.example.test/from-prefs using echo`)
2355 }
2356
2357 func (ss *soundsSuite) TestPresentFails(c *C) {
2358 s := &Sound{
2359 player: "/",
2360 log: ss.log,
2361+ acc: ss.acc,
2362 dataFind: func(string) (string, error) { return "", errors.New("nope") },
2363 dataDirs: func() []string { return []string{""} },
2364 }
2365@@ -107,6 +130,7 @@
2366 s := &Sound{
2367 player: "/",
2368 log: ss.log,
2369+ acc: ss.acc,
2370 dataFind: func(string) (string, error) { return "", errors.New("nope") },
2371 dataDirs: func() []string { return []string{""} },
2372 }
2373@@ -120,6 +144,7 @@
2374 s := &Sound{
2375 player: "/",
2376 log: ss.log,
2377+ acc: ss.acc,
2378 dataFind: func(string) (string, error) { return "", errors.New("nope") },
2379 dataDirs: func() []string { return []string{""} },
2380 }
2381@@ -128,3 +153,19 @@
2382 c.Check(err, IsNil)
2383 c.Check(sound, Equals, "bar")
2384 }
2385+
2386+func (ss *soundsSuite) TestSkipIfSilentMode(c *C) {
2387+ s := &Sound{
2388+ player: "echo",
2389+ log: ss.log,
2390+ acc: ss.acc,
2391+ fallback: "fallback",
2392+ dataFind: func(s string) (string, error) { return s, nil },
2393+ }
2394+
2395+ c.Check(s.Present(ss.app, "",
2396+ &launch_helper.Notification{RawSound: json.RawMessage(`true`)}), Equals, true)
2397+ ss.acc.sil = true
2398+ c.Check(s.Present(ss.app, "",
2399+ &launch_helper.Notification{RawSound: json.RawMessage(`true`)}), Equals, false)
2400+}
2401
2402=== modified file 'testing/helpers.go'
2403--- testing/helpers.go 2015-01-22 09:52:07 +0000
2404+++ testing/helpers.go 2015-03-06 13:26:11 +0000
2405@@ -170,3 +170,12 @@
2406 }
2407 return purl
2408 }
2409+
2410+// DumpGoroutines dumps current goroutines.
2411+func DumpGoroutines() {
2412+ var buf [64 * 1024]byte
2413+ sz := runtime.Stack(buf[:], true)
2414+ dump := string(buf[:sz])
2415+ fmt.Println(dump)
2416+ fmt.Println("#goroutines#", strings.Count("\n"+dump, "\ngoroutine "))
2417+}

Subscribers

People subscribed via source and target branches