Merge lp:~pedronis/ubuntu-push/automatic-into-krillin-rtm into lp:ubuntu-push/krillin-rtm

Proposed by Samuele Pedroni
Status: Merged
Approved by: Roberto Alsina
Approved revision: 142
Merged at revision: 141
Proposed branch: lp:~pedronis/ubuntu-push/automatic-into-krillin-rtm
Merge into: lp:ubuntu-push/krillin-rtm
Diff against target: 6757 lines (+2552/-853)
72 files modified
.precommit (+21/-19)
PACKAGE_DEPS (+1/-0)
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 (+12/-3)
bus/connectivity/webchecker_test.go (+9/-3)
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 (+85/-29)
bus/testing/testing_endpoint_test.go (+38/-18)
click/cappinfo/cappinfo.go (+31/-0)
click/cclick/cclick.go (+1/-0)
click/click.go (+4/-0)
click/click_test.go (+14/-0)
client/client.go (+22/-48)
client/client_test.go (+69/-108)
client/service/postal.go (+12/-2)
client/service/postal_test.go (+17/-0)
client/service/service.go (+2/-0)
client/service/service_test.go (+28/-5)
client/session/seenstate/seenstate.go (+6/-1)
client/session/seenstate/sqlseenstate.go (+5/-0)
client/session/seenstate/sqlseenstate_test.go (+9/-0)
client/session/session.go (+208/-82)
client/session/session_test.go (+364/-178)
docs/Makefile (+4/-0)
docs/_common.txt (+58/-44)
docs/example-client/components/ChatClient.qml (+2/-2)
docs/example-client/helloHelper-apparmor.json (+1/-0)
docs/example-client/main.qml (+47/-18)
docs/example-client/manifest.json (+2/-2)
docs/example-server/app.js (+31/-13)
docs/example-server/config/config.js (+1/-1)
docs/example-server/index.html (+2/-0)
docs/example-server/notify-form.html (+61/-6)
docs/example-server/test/app_test.js (+128/-32)
docs/highlevel.txt (+2/-2)
docs/lowlevel.txt (+4/-2)
launch_helper/kindpool_test.go (+1/-1)
logger/logger.go (+12/-7)
logger/logger_test.go (+12/-0)
messaging/messaging_test.go (+14/-7)
poller/poller.go (+8/-2)
server/acceptance/kit/api.go (+14/-3)
server/acceptance/suites/helpers.go (+8/-7)
server/acceptance/suites/suite.go (+2/-1)
server/api/handlers.go (+15/-1)
server/api/handlers_test.go (+22/-6)
server/broker/broker.go (+6/-1)
server/broker/simple/simple.go (+1/-1)
server/broker/simple/suite_test.go (+10/-0)
server/broker/testsuite/suite.go (+31/-29)
server/dev/server.go (+3/-1)
server/listener/listener.go (+12/-2)
server/listener/listener_test.go (+33/-6)
server/runner_devices.go (+2/-2)
server/runner_test.go (+5/-2)
server/session/session.go (+3/-3)
server/session/session_test.go (+7/-6)
server/tlsconfig.go (+12/-0)
sounds/sounds.go (+15/-2)
sounds/sounds_test.go (+44/-3)
testing/helpers.go (+9/-0)
util/redialer.go (+45/-11)
util/redialer_states.gv (+9/-0)
To merge this branch: bzr merge lp:~pedronis/ubuntu-push/automatic-into-krillin-rtm
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Review via email: mp+254285@code.launchpad.net

Commit message

[Bret Barker, Samuele Pedroni]
  * Partial fix of lp:1390663
     - Remove SessionStateSettle sleep on wake, + more debug logging.
     - Don't hold a lock for a long time on handleErrConn, trigger
       autoRedial on Error more actively.

  [John Lenton]
  * Refactor code maintaining session (better fix for lp:1390663)
  * Prune the XDG path from the beginning of accounts-set sound files.
  * Use accounts' settings from sound and haptic.
  * Add an explicit check and log message for nil error on webcheck's
    CopyN.
  * Move logging to info; improve logging of legacy helper errors;
    switch some logs to error from debug.

  [Bret Barker]
  * Add SIGQUIT handler to spit out stack dumps; more logging
    tweaks. [client, server]
  * Log line nums, enabled when logLevel = debug.

  [Samuele Pedroni]
  * Unit test improvements
  * Workaround gc issue with 1.3 and 32 bits.

  [Roberto Ralsina]
  * Example and docs improvements.

  [ Guillermo Gonzalez ]
  * When The server reply 401 on /register, make the DBus call to Register
    return ErrBadAuth instead of ErrBadRequest.
  * Fix click hook for legacy apps
  * Add ClearCookie method to the session and call it from handleAccountsChange.
  * click.AppId.SymbolicIcon() now tries to use X-Ubuntu-SymbolicIcon
    and then fallback to icon+"-symbolic"

To post a comment you must log in.
Revision history for this message
Roberto Alsina (ralsina) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.precommit'
2--- .precommit 2014-01-23 10:03:39 +0000
3+++ .precommit 2015-03-26 16:42:21 +0000
4@@ -5,25 +5,27 @@
5 # And put this here-document in ~/.bazaar/plugins/precommit_script.py:
6 <<EOF
7 import os
8-import subprocess
9-from bzrlib.mutabletree import MutableTree
10-from bzrlib import errors
11-
12-def start_commit_hook(*_):
13- """This hook will execute '.precommit' script from root path of the bazaar
14- branch. Commit will be canceled if precommit fails."""
15-
16- # this hook only makes sense if a precommit file exist.
17- if not os.path.exists(".precommit"):
18- return
19- try:
20- subprocess.check_call(os.path.abspath(".precommit"))
21- # if precommit fails (process return not zero) cancel commit.
22- except subprocess.CalledProcessError:
23- raise errors.BzrError("pre commit check failed.")
24-
25-MutableTree.hooks.install_named_hook('start_commit', start_commit_hook,
26- 'Run "precommit" script on start_commit')
27+
28+if not os.getenv("SKIP_COMMIT_HOOK"):
29+ import subprocess
30+ from bzrlib.mutabletree import MutableTree
31+ from bzrlib import errors
32+
33+ def start_commit_hook(*_):
34+ """This hook will execute '.precommit' script from root path of the bazaar
35+ branch. Commit will be canceled if precommit fails."""
36+
37+ # this hook only makes sense if a precommit file exist.
38+ if not os.path.exists(".precommit"):
39+ return
40+ try:
41+ subprocess.check_call(os.path.abspath(".precommit"))
42+ # if precommit fails (process return not zero) cancel commit.
43+ except subprocess.CalledProcessError:
44+ raise errors.BzrError("pre commit check failed (set SKIP_COMMIT_HOOK to skip).")
45+
46+ MutableTree.hooks.install_named_hook('start_commit', start_commit_hook,
47+ 'Run "precommit" script on start_commit')
48 EOF
49
50 make check-format # or whatever
51
52=== modified file 'PACKAGE_DEPS'
53--- PACKAGE_DEPS 2014-09-05 10:48:36 +0000
54+++ PACKAGE_DEPS 2015-03-26 16:42:21 +0000
55@@ -12,3 +12,4 @@
56 libclick-0.4-dev
57 liburl-dispatcher1-dev
58 libaccounts-glib-dev
59+system-image-dbus
60
61=== added directory 'bus/accounts'
62=== added file 'bus/accounts/accounts.go'
63--- bus/accounts/accounts.go 1970-01-01 00:00:00 +0000
64+++ bus/accounts/accounts.go 2015-03-26 16:42:21 +0000
65@@ -0,0 +1,310 @@
66+/*
67+ Copyright 2013-2015 Canonical Ltd.
68+
69+ This program is free software: you can redistribute it and/or modify it
70+ under the terms of the GNU General Public License version 3, as published
71+ by the Free Software Foundation.
72+
73+ This program is distributed in the hope that it will be useful, but
74+ WITHOUT ANY WARRANTY; without even the implied warranties of
75+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
76+ PURPOSE. See the GNU General Public License for more details.
77+
78+ You should have received a copy of the GNU General Public License along
79+ with this program. If not, see <http://www.gnu.org/licenses/>.
80+*/
81+// accounts exposes some properties that're stored in org.freedesktop.Accounts
82+// (specifically, the ones that we need are all under
83+// com.ubuntu.touch.AccountsService.Sound).
84+package accounts
85+
86+import (
87+ "fmt"
88+ "os/user"
89+ "strings"
90+ "sync"
91+
92+ "launchpad.net/go-dbus/v1"
93+ "launchpad.net/go-xdg/v0"
94+
95+ "launchpad.net/ubuntu-push/bus"
96+ "launchpad.net/ubuntu-push/logger"
97+)
98+
99+// accounts lives on a well-known bus.Address.
100+//
101+// Note this one isn't it: the interface is for dbus.properties, and the path
102+// is missing the UID.
103+var BusAddress bus.Address = bus.Address{
104+ Interface: "org.freedesktop.DBus.Properties",
105+ Path: "/org/freedesktop/Accounts/User",
106+ Name: "org.freedesktop.Accounts",
107+}
108+
109+const accountsSoundIface = "com.ubuntu.touch.AccountsService.Sound"
110+
111+type Accounts interface {
112+ // Start() sets up the asynchronous updating of properties, and does the first update.
113+ Start() error
114+ // Cancel() stops the asynchronous updating of properties.
115+ Cancel() error
116+ // SilentMode() tells you whether the device is in silent mode.
117+ SilentMode() bool
118+ // Vibrate() tells you whether the device is allowed to vibrate.
119+ Vibrate() bool
120+ // MessageSoundFile() tells you the default sound filename.
121+ MessageSoundFile() string
122+ String() string
123+}
124+
125+// Accounts tracks the relevant bits of configuration. Nothing directly
126+// accessible because it is updated asynchronously, so use the accessors.
127+type accounts struct {
128+ endp bus.Endpoint
129+ log logger.Logger
130+ silent bool
131+ vibrate bool
132+ vibrateSilentMode bool
133+ messageSound string
134+ cancellable bus.Cancellable
135+ lck sync.Mutex
136+ updaters map[string]func(dbus.Variant)
137+}
138+
139+// sets up a new Accounts structure, ready to be Start()ed.
140+func New(endp bus.Endpoint, log logger.Logger) Accounts {
141+ a := &accounts{
142+ endp: endp,
143+ log: log,
144+ }
145+
146+ a.updaters = map[string]func(dbus.Variant){
147+ "SilentMode": a.updateSilentMode,
148+ "IncomingMessageVibrate": a.updateVibrate,
149+ "IncomingMessageVibrateSilentMode": a.updateVibrateSilentMode,
150+ "IncomingMessageSound": a.updateMessageSound,
151+ }
152+
153+ return a
154+}
155+
156+// sets up the asynchronous updating of properties, and does the first update.
157+func (a *accounts) Start() error {
158+ err := a.startWatch()
159+ if err != nil {
160+ return err
161+ }
162+ a.update()
163+ return nil
164+}
165+
166+// does sets up the watch on the PropertiesChanged signal. Separate from Start
167+// because it holds a lock.
168+func (a *accounts) startWatch() error {
169+ cancellable, err := a.endp.WatchSignal("PropertiesChanged", a.propsHandler, a.bailoutHandler)
170+ if err != nil {
171+ a.log.Errorf("unable to watch for property changes: %v", err)
172+ return err
173+ }
174+
175+ a.lck.Lock()
176+ defer a.lck.Unlock()
177+ if a.cancellable != nil {
178+ panic("tried to start Accounts twice?")
179+ }
180+ a.cancellable = cancellable
181+
182+ return nil
183+}
184+
185+// cancel the asynchronous updating of properties.
186+func (a *accounts) Cancel() error {
187+ return a.cancellable.Cancel()
188+}
189+
190+// slightly shorter than %#v
191+func (a *accounts) String() string {
192+ return fmt.Sprintf("&accounts{silent: %t, vibrate: %t, vibratesilent: %t, messageSound: %q}",
193+ a.silent, a.vibrate, a.vibrateSilentMode, a.messageSound)
194+}
195+
196+// merely log that the watch loop has bailed; not much we can do.
197+func (a *accounts) bailoutHandler() {
198+ a.log.Debugf("loop bailed out")
199+}
200+
201+// handle PropertiesChanged, which is described in
202+// http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties
203+func (a *accounts) propsHandler(ns ...interface{}) {
204+ if len(ns) != 3 {
205+ a.log.Errorf("PropertiesChanged delivered %d things instead of 3.", len(ns))
206+ return
207+ }
208+
209+ iface, ok := ns[0].(string)
210+ if !ok {
211+ a.log.Errorf("PropertiesChanged 1st param not a string: %#v.", ns[0])
212+ return
213+ }
214+ if iface != accountsSoundIface {
215+ a.log.Debugf("PropertiesChanged for %#v, ignoring.", iface)
216+ return
217+ }
218+ changed, ok := ns[1].(map[interface{}]interface{})
219+ if !ok {
220+ a.log.Errorf("PropertiesChanged 2nd param not a map: %#v.", ns[1])
221+ return
222+ }
223+ if len(changed) != 0 {
224+ // not seen in the wild, but easy to implement properly (ie
225+ // using the values we're given) if it starts to
226+ // happen. Meanwhile just do a full update.
227+ a.log.Infof("PropertiesChanged provided 'changed'; reverting to full update.")
228+ a.update()
229+ return
230+ }
231+ invalid, ok := ns[2].([]interface{})
232+ if !ok {
233+ a.log.Errorf("PropertiesChanged 3rd param not a list of properties: %#v.", ns[2])
234+ return
235+ }
236+ a.log.Debugf("props changed: %#v.", invalid)
237+ switch len(invalid) {
238+ case 0:
239+ // nothing to do?
240+ a.log.Debugf("PropertiesChanged 3rd param is empty; doing nothing.")
241+ case 1:
242+ // the common case right now
243+ k, ok := invalid[0].(string)
244+ if !ok {
245+ a.log.Errorf("PropertiesChanged 3rd param's only entry not a string: %#v.", invalid[0])
246+ return
247+ }
248+ updater, ok := a.updaters[k]
249+ if ok {
250+ var v dbus.Variant
251+ err := a.endp.Call("Get", []interface{}{accountsSoundIface, k}, &v)
252+ if err != nil {
253+ a.log.Errorf("when calling Get for %s: %v", k, err)
254+ return
255+ }
256+ a.log.Debugf("Get for %s got %#v.", k, v)
257+ // updaters must be called with the lock held
258+ a.lck.Lock()
259+ defer a.lck.Unlock()
260+ updater(v)
261+ a.log.Debugf("updated %s.", k)
262+ }
263+ default:
264+ // not seen in the wild, but we probably want to drop to a
265+ // full update if getting more than one change anyway.
266+ a.log.Infof("PropertiesChanged provided more than one 'invalid'; reverting to full update.")
267+ a.update()
268+ }
269+}
270+
271+func (a *accounts) updateSilentMode(vsilent dbus.Variant) {
272+ silent, ok := vsilent.Value.(bool)
273+ if !ok {
274+ a.log.Errorf("SilentMode needed a bool.")
275+ return
276+ }
277+
278+ a.silent = silent
279+}
280+
281+func (a *accounts) updateVibrate(vvibrate dbus.Variant) {
282+ vibrate, ok := vvibrate.Value.(bool)
283+ if !ok {
284+ a.log.Errorf("IncomingMessageVibrate needed a bool.")
285+ return
286+ }
287+
288+ a.vibrate = vibrate
289+}
290+
291+func (a *accounts) updateVibrateSilentMode(vvibrateSilentMode dbus.Variant) {
292+ vibrateSilentMode, ok := vvibrateSilentMode.Value.(bool)
293+ if !ok {
294+ a.log.Errorf("IncomingMessageVibrateSilentMode needed a bool.")
295+ return
296+ }
297+
298+ a.vibrateSilentMode = vibrateSilentMode
299+}
300+
301+func (a *accounts) updateMessageSound(vsnd dbus.Variant) {
302+ snd, ok := vsnd.Value.(string)
303+ if !ok {
304+ a.log.Errorf("IncomingMessageSound needed a string.")
305+ return
306+ }
307+
308+ for _, dir := range xdg.Data.Dirs()[1:] {
309+ if dir[len(dir)-1] != '/' {
310+ dir += "/"
311+ }
312+ if strings.HasPrefix(snd, dir) {
313+ snd = snd[len(dir):]
314+ break
315+ }
316+ }
317+
318+ a.messageSound = snd
319+}
320+
321+func (a *accounts) update() {
322+ props := make(map[string]dbus.Variant)
323+ err := a.endp.Call("GetAll", []interface{}{accountsSoundIface}, &props)
324+ if err != nil {
325+ a.log.Errorf("when calling GetAll: %v", err)
326+ return
327+ }
328+ a.log.Debugf("GetAll got: %#v", props)
329+
330+ a.lck.Lock()
331+ defer a.lck.Unlock()
332+
333+ for name, updater := range a.updaters {
334+ updater(props[name])
335+ }
336+}
337+
338+// is the device in silent mode?
339+func (a *accounts) SilentMode() bool {
340+ a.lck.Lock()
341+ defer a.lck.Unlock()
342+
343+ return a.silent
344+}
345+
346+// should notifications vibrate?
347+func (a *accounts) Vibrate() bool {
348+ a.lck.Lock()
349+ defer a.lck.Unlock()
350+
351+ if a.silent {
352+ return a.vibrateSilentMode
353+ } else {
354+ return a.vibrate
355+ }
356+}
357+
358+// what is the default sound file?
359+func (a *accounts) MessageSoundFile() string {
360+ a.lck.Lock()
361+ defer a.lck.Unlock()
362+
363+ return a.messageSound
364+}
365+
366+// the BusAddress should actually end with the UID of the user in question;
367+// here we do what's needed to get that.
368+func init() {
369+ u, err := user.Current()
370+ if err != nil {
371+ panic(err)
372+ }
373+
374+ BusAddress.Path += u.Uid
375+}
376
377=== added file 'bus/accounts/accounts_test.go'
378--- bus/accounts/accounts_test.go 1970-01-01 00:00:00 +0000
379+++ bus/accounts/accounts_test.go 2015-03-26 16:42:21 +0000
380@@ -0,0 +1,271 @@
381+/*
382+ Copyright 2013-2015 Canonical Ltd.
383+
384+ This program is free software: you can redistribute it and/or modify it
385+ under the terms of the GNU General Public License version 3, as published
386+ by the Free Software Foundation.
387+
388+ This program is distributed in the hope that it will be useful, but
389+ WITHOUT ANY WARRANTY; without even the implied warranties of
390+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
391+ PURPOSE. See the GNU General Public License for more details.
392+
393+ You should have received a copy of the GNU General Public License along
394+ with this program. If not, see <http://www.gnu.org/licenses/>.
395+*/
396+
397+package accounts
398+
399+import (
400+ "errors"
401+ "testing"
402+
403+ "launchpad.net/go-dbus/v1"
404+ . "launchpad.net/gocheck"
405+
406+ testibus "launchpad.net/ubuntu-push/bus/testing"
407+ helpers "launchpad.net/ubuntu-push/testing"
408+ "launchpad.net/ubuntu-push/testing/condition"
409+)
410+
411+// hook up gocheck
412+func TestAcc(t *testing.T) { TestingT(t) }
413+
414+type AccSuite struct {
415+ log *helpers.TestLogger
416+}
417+
418+var _ = Suite(&AccSuite{})
419+
420+type TestCancellable struct {
421+ canceled bool
422+ err error
423+}
424+
425+func (t *TestCancellable) Cancel() error {
426+ t.canceled = true
427+ return t.err
428+}
429+
430+func (s *AccSuite) SetUpTest(c *C) {
431+ s.log = helpers.NewTestLogger(c, "debug")
432+}
433+
434+func (s *AccSuite) TestBusAddressPathUidLoaded(c *C) {
435+ c.Check(BusAddress.Path, Matches, `.*\d+`)
436+}
437+
438+func (s *AccSuite) TestCancelCancelsCancellable(c *C) {
439+ err := errors.New("cancel error")
440+ t := &TestCancellable{err: err}
441+ a := New(nil, s.log).(*accounts)
442+ a.cancellable = t
443+
444+ c.Check(a.Cancel(), Equals, err)
445+ c.Check(t.canceled, Equals, true)
446+}
447+
448+func (s *AccSuite) TestStartReportsWatchError(c *C) {
449+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
450+ a := New(endp, s.log).(*accounts)
451+ c.Assert(a, NotNil)
452+
453+ err := a.Start()
454+ c.Check(err, NotNil)
455+}
456+
457+func (s *AccSuite) TestStartSetsCancellable(c *C) {
458+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), true)
459+ a := New(endp, s.log).(*accounts)
460+ c.Assert(a, NotNil)
461+
462+ c.Check(a.cancellable, IsNil)
463+ err := a.Start()
464+ c.Check(err, IsNil)
465+ c.Check(a.cancellable, NotNil)
466+ a.Cancel()
467+}
468+
469+func (s *AccSuite) TestStartPanicsIfCalledTwice(c *C) {
470+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), true, true)
471+ a := New(endp, s.log).(*accounts)
472+ c.Assert(a, NotNil)
473+
474+ c.Check(a.cancellable, IsNil)
475+ err := a.Start()
476+ c.Check(err, IsNil)
477+ c.Check(func() { a.startWatch() }, PanicMatches, `.* twice\?`)
478+ a.Cancel()
479+}
480+
481+func (s *AccSuite) TestUpdateCallsUpdaters(c *C) {
482+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true),
483+ map[string]dbus.Variant{"x": dbus.Variant{"hello"}})
484+ a := New(endp, s.log).(*accounts)
485+ c.Assert(a, NotNil)
486+ var x dbus.Variant
487+ a.updaters = map[string]func(dbus.Variant){
488+ "x": func(v dbus.Variant) { x = v },
489+ }
490+ a.update()
491+
492+ c.Check(x.Value, Equals, "hello")
493+}
494+
495+func (s *AccSuite) TestUpdateSilentModeBails(c *C) {
496+ a := New(nil, s.log).(*accounts)
497+ a.updateSilentMode(dbus.Variant{"rubbish"})
498+ c.Check(s.log.Captured(), Matches, `(?ms)ERROR SilentMode needed a bool.`)
499+}
500+
501+func (s *AccSuite) TestUpdateSilentModeWorks(c *C) {
502+ a := New(nil, s.log).(*accounts)
503+ c.Check(a.silent, Equals, false)
504+ a.updateSilentMode(dbus.Variant{true})
505+ c.Check(a.silent, Equals, true)
506+}
507+
508+func (s *AccSuite) TestUpdateVibrateBails(c *C) {
509+ a := New(nil, s.log).(*accounts)
510+ a.updateVibrate(dbus.Variant{"rubbish"})
511+ c.Check(s.log.Captured(), Matches, `(?ms)ERROR IncomingMessageVibrate needed a bool.`)
512+}
513+
514+func (s *AccSuite) TestUpdateVibrateWorks(c *C) {
515+ a := New(nil, s.log).(*accounts)
516+ c.Check(a.vibrate, Equals, false)
517+ a.updateVibrate(dbus.Variant{true})
518+ c.Check(a.vibrate, Equals, true)
519+}
520+
521+func (s *AccSuite) TestUpdateVibrateSilentModeBails(c *C) {
522+ a := New(nil, s.log).(*accounts)
523+ a.updateVibrateSilentMode(dbus.Variant{"rubbish"})
524+ c.Check(s.log.Captured(), Matches, `(?ms)ERROR IncomingMessageVibrateSilentMode needed a bool.`)
525+}
526+
527+func (s *AccSuite) TestUpdateVibrateSilentModeWorks(c *C) {
528+ a := New(nil, s.log).(*accounts)
529+ c.Check(a.vibrateSilentMode, Equals, false)
530+ a.updateVibrateSilentMode(dbus.Variant{true})
531+ c.Check(a.vibrateSilentMode, Equals, true)
532+}
533+
534+func (s *AccSuite) TestUpdateMessageSoundBails(c *C) {
535+ a := New(nil, s.log).(*accounts)
536+ a.updateMessageSound(dbus.Variant{42})
537+ c.Check(s.log.Captured(), Matches, `(?ms)ERROR IncomingMessageSound needed a string.`)
538+}
539+
540+func (s *AccSuite) TestUpdateMessageSoundWorks(c *C) {
541+ a := New(nil, s.log).(*accounts)
542+ c.Check(a.messageSound, Equals, "")
543+ a.updateMessageSound(dbus.Variant{"xyzzy"})
544+ c.Check(a.messageSound, Equals, "xyzzy")
545+}
546+
547+func (s *AccSuite) TestUpdateMessageSoundPrunesXDG(c *C) {
548+ a := New(nil, s.log).(*accounts)
549+ a.updateMessageSound(dbus.Variant{"/usr/share/xyzzy"})
550+ c.Check(a.messageSound, Equals, "xyzzy")
551+}
552+
553+func (s *AccSuite) TestPropsHandler(c *C) {
554+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
555+
556+ // testing a series of bad args for propsHandler: none,
557+ New(endp, s.log).(*accounts).propsHandler()
558+ c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged delivered 0 things.*`)
559+ s.log.ResetCapture()
560+
561+ // bad type for all,
562+ New(endp, s.log).(*accounts).propsHandler(nil, nil, nil)
563+ c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 1st param not a string.*`)
564+ s.log.ResetCapture()
565+
566+ // wrong interface,
567+ New(endp, s.log).(*accounts).propsHandler("xyzzy", nil, nil)
568+ c.Check(s.log.Captured(), Matches, `(?ms).*DEBUG PropertiesChanged for "xyzzy", ignoring\..*`)
569+ s.log.ResetCapture()
570+
571+ // bad type for 2nd and 3rd,
572+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, nil, nil)
573+ c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 2nd param not a map.*`)
574+ s.log.ResetCapture()
575+
576+ // not-seen-in-the-wild 'changed' argument (first non-error outcome),
577+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{"x": "y"}, nil)
578+ // tracking the update() via the GetAll call it generates (which will fail because of the testibus of Work(false) above)
579+ c.Check(s.log.Captured(), Matches, `(?ms).*INFO PropertiesChanged provided 'changed'.*ERROR when calling GetAll.*`)
580+ s.log.ResetCapture()
581+
582+ // bad type for 3rd (with empty 2nd),
583+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, nil)
584+ c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 3rd param not a list of properties.*`)
585+ s.log.ResetCapture()
586+
587+ // bad type for elements of 3rd,
588+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{42})
589+ c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 3rd param's only entry not a string.*`)
590+ s.log.ResetCapture()
591+
592+ // empty 3rd (not an error; hard to test "do ),
593+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{})
594+ c.Check(s.log.Captured(), Matches, `(?ms).*DEBUG PropertiesChanged 3rd param is empty.*`)
595+ s.log.ResetCapture()
596+
597+ // more than one 2rd (also not an error; again looking at the GetAll failure to confirm update() got called),
598+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{"hi", "there"})
599+ c.Check(s.log.Captured(), Matches, `(?ms).*INFO.* reverting to full update.*ERROR when calling GetAll.*`)
600+ s.log.ResetCapture()
601+
602+ // bus trouble for a single entry in the 3rd,
603+ New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{"SilentMode"})
604+ c.Check(s.log.Captured(), Matches, `(?ms).*ERROR when calling Get for SilentMode.*`)
605+ s.log.ResetCapture()
606+
607+ // and finally, the common case: a single entry in the 3rd param, that gets updated individually.
608+ xOuter := dbus.Variant{"x"}
609+ a := New(testibus.NewTestingEndpoint(nil, condition.Work(true), xOuter), s.log).(*accounts)
610+ called := false
611+ a.updaters = map[string]func(dbus.Variant){"xyzzy": func(x dbus.Variant) {
612+ c.Check(x, Equals, xOuter)
613+ called = true
614+ }}
615+ a.propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{"xyzzy"})
616+ c.Check(called, Equals, true)
617+}
618+
619+func (s *AccSuite) TestSilentMode(c *C) {
620+ a := New(nil, s.log).(*accounts)
621+ c.Check(a.SilentMode(), Equals, false)
622+ a.silent = true
623+ c.Check(a.SilentMode(), Equals, true)
624+}
625+
626+func (s *AccSuite) TestVibrate(c *C) {
627+ a := New(nil, s.log).(*accounts)
628+ c.Check(a.Vibrate(), Equals, false)
629+ a.vibrate = true
630+ c.Check(a.Vibrate(), Equals, true)
631+ a.silent = true
632+ c.Check(a.Vibrate(), Equals, false)
633+ a.vibrateSilentMode = true
634+ c.Check(a.Vibrate(), Equals, true)
635+ a.vibrate = false
636+ c.Check(a.Vibrate(), Equals, true)
637+}
638+
639+func (s *AccSuite) TestMessageSoundFile(c *C) {
640+ a := New(nil, s.log).(*accounts)
641+ c.Check(a.MessageSoundFile(), Equals, "")
642+ a.messageSound = "xyzzy"
643+ c.Check(a.MessageSoundFile(), Equals, "xyzzy")
644+}
645+
646+func (s *AccSuite) TestString(c *C) {
647+ a := New(nil, s.log).(*accounts)
648+ a.vibrate = true
649+ a.messageSound = "x"
650+ c.Check(a.String(), Equals, `&accounts{silent: false, vibrate: true, vibratesilent: false, messageSound: "x"}`)
651+}
652
653=== modified file 'bus/connectivity/connectivity.go'
654--- bus/connectivity/connectivity.go 2015-01-22 17:34:18 +0000
655+++ bus/connectivity/connectivity.go 2015-03-26 16:42:21 +0000
656@@ -1,5 +1,5 @@
657 /*
658- Copyright 2013-2014 Canonical Ltd.
659+ Copyright 2013-2015 Canonical Ltd.
660
661 This program is free software: you can redistribute it and/or modify it
662 under the terms of the GNU General Public License version 3, as published
663@@ -24,12 +24,14 @@
664
665 import (
666 "errors"
667+ "sync"
668+ "time"
669+
670 "launchpad.net/ubuntu-push/bus"
671 "launchpad.net/ubuntu-push/bus/networkmanager"
672 "launchpad.net/ubuntu-push/config"
673 "launchpad.net/ubuntu-push/logger"
674 "launchpad.net/ubuntu-push/util"
675- "time"
676 )
677
678 // The configuration for ConnectedState, intended to be populated from a config file.
679@@ -45,23 +47,56 @@
680 ConnectivityCheckMD5 string `json:"connectivity_check_md5"`
681 }
682
683-type connectedState struct {
684+// ConnectedState helps tracking connectivity.
685+type ConnectedState struct {
686 networkStateCh <-chan networkmanager.State
687 networkConCh <-chan string
688 config ConnectivityConfig
689 log logger.Logger
690 endp bus.Endpoint
691 connAttempts uint32
692- webget func(ch chan<- bool)
693+ webchk Webchecker
694 webgetCh chan bool
695 currentState networkmanager.State
696 lastSent bool
697 timer *time.Timer
698+ doneLck sync.Mutex
699+ done chan struct{}
700+ canceled bool
701+ stateWatch bus.Cancellable
702+ conWatch bus.Cancellable
703+}
704+
705+// New makes a ConnectedState for connectivity tracking.
706+//
707+// The endpoint need not be dialed; Track() will Dial() and
708+// Close() it as it sees fit.
709+func New(endp bus.Endpoint, config ConnectivityConfig, log logger.Logger) *ConnectedState {
710+ wg := NewWebchecker(config.ConnectivityCheckURL, config.ConnectivityCheckMD5, 10*time.Second, log)
711+ return &ConnectedState{
712+ config: config,
713+ log: log,
714+ endp: endp,
715+ webchk: wg,
716+ done: make(chan struct{}),
717+ }
718+}
719+
720+// cancel watches if any
721+func (cs *ConnectedState) reset() {
722+ if cs.stateWatch != nil {
723+ cs.stateWatch.Cancel()
724+ cs.stateWatch = nil
725+ }
726+ if cs.conWatch != nil {
727+ cs.conWatch.Cancel()
728+ cs.conWatch = nil
729+ }
730 }
731
732 // start connects to the bus, gets the initial NetworkManager state, and sets
733 // up the watch.
734-func (cs *connectedState) start() networkmanager.State {
735+func (cs *ConnectedState) start() networkmanager.State {
736 var initial networkmanager.State
737 var stateCh <-chan networkmanager.State
738 var primary string
739@@ -72,8 +107,9 @@
740 cs.connAttempts += ar.Redial()
741 nm := networkmanager.New(cs.endp, cs.log)
742
743+ cs.reset()
744 // set up the watch
745- stateCh, err = nm.WatchState()
746+ stateCh, cs.stateWatch, err = nm.WatchState()
747 if err != nil {
748 cs.log.Debugf("failed to set up the state watch: %s", err)
749 goto Continue
750@@ -87,15 +123,15 @@
751 }
752 cs.log.Debugf("got initial state of %s", initial)
753
754+ conCh, cs.conWatch, err = nm.WatchPrimaryConnection()
755+ if err != nil {
756+ cs.log.Debugf("failed to set up the connection watch: %s", err)
757+ goto Continue
758+ }
759+
760 primary = nm.GetPrimaryConnection()
761 cs.log.Debugf("primary connection starts as %#v", primary)
762
763- conCh, err = nm.WatchPrimaryConnection()
764- if err != nil {
765- cs.log.Debugf("failed to set up the connection watch: %s", err)
766- goto Continue
767- }
768-
769 cs.networkStateCh = stateCh
770 cs.networkConCh = conCh
771
772@@ -107,9 +143,11 @@
773 }
774 }
775
776-// connectedStateStep takes one step forwards in the “am I connected?”
777+var errCanceled = errors.New("canceled")
778+
779+// step takes one step forwards in the “am I connected?”
780 // answering state machine.
781-func (cs *connectedState) connectedStateStep() (bool, error) {
782+func (cs *ConnectedState) step() (bool, error) {
783 stabilizingTimeout := cs.config.StabilizingTimeout.Duration
784 recheckTimeout := cs.config.RecheckTimeout.Duration
785 log := cs.log
786@@ -117,6 +155,8 @@
787 Loop:
788 for {
789 select {
790+ case <-cs.done:
791+ return false, errCanceled
792 case <-cs.networkConCh:
793 cs.webgetCh = nil
794 cs.timer.Reset(stabilizingTimeout)
795@@ -155,8 +195,13 @@
796 case <-cs.timer.C:
797 if cs.currentState == networkmanager.ConnectedGlobal {
798 log.Debugf("connectivity: timer signal, state: ConnectedGlobal, checking...")
799- cs.webgetCh = make(chan bool)
800- go cs.webget(cs.webgetCh)
801+ // use a buffered channel, otherwise
802+ // we may leak webcheckers that cannot
803+ // send their result because we have
804+ // cleared webgetCh and wont receive
805+ // on it
806+ cs.webgetCh = make(chan bool, 1)
807+ go cs.webchk.Webcheck(cs.webgetCh)
808 }
809
810 case connected := <-cs.webgetCh:
811@@ -173,35 +218,49 @@
812 return cs.lastSent, nil
813 }
814
815-// ConnectedState sends the initial NetworkManager state and changes to it
816+// Track sends the initial NetworkManager state and changes to it
817 // over the "out" channel. Sends "false" as soon as it detects trouble, "true"
818 // after checking actual connectivity.
819 //
820-// The endpoint need not be dialed; connectivity will Dial() and Close()
821-// it as it sees fit.
822-func ConnectedState(endp bus.Endpoint, config ConnectivityConfig, log logger.Logger, out chan<- bool) {
823- wg := NewWebchecker(config.ConnectivityCheckURL, config.ConnectivityCheckMD5, 10*time.Second, log)
824- cs := &connectedState{
825- config: config,
826- log: log,
827- endp: endp,
828- webget: wg.Webcheck,
829- }
830+func (cs *ConnectedState) Track(out chan<- bool) {
831
832 Start:
833- log.Debugf("sending initial 'disconnected'.")
834- out <- false
835+ cs.log.Debugf("sending initial 'disconnected'.")
836+ select {
837+ case <-cs.done:
838+ return
839+ case out <- false:
840+ }
841 cs.lastSent = false
842 cs.currentState = cs.start()
843+ defer cs.reset()
844 cs.timer = time.NewTimer(cs.config.StabilizingTimeout.Duration)
845
846 for {
847- v, err := cs.connectedStateStep()
848+ v, err := cs.step()
849+ if err == errCanceled {
850+ return
851+ }
852 if err != nil {
853 // tear it all down and start over
854- log.Errorf("%s", err)
855+ cs.log.Errorf("%s", err)
856 goto Start
857 }
858- out <- v
859+ select {
860+ case <-cs.done:
861+ return
862+ case out <- v:
863+ }
864+ }
865+}
866+
867+// Cancel stops the ConnectedState machinary.
868+func (cs *ConnectedState) Cancel() {
869+ cs.doneLck.Lock()
870+ defer cs.doneLck.Unlock()
871+ if !cs.canceled {
872+ cs.canceled = true
873+ close(cs.done)
874+ cs.webchk.Close()
875 }
876 }
877
878=== modified file 'bus/connectivity/connectivity_test.go'
879--- bus/connectivity/connectivity_test.go 2015-01-22 17:34:18 +0000
880+++ bus/connectivity/connectivity_test.go 2015-03-26 16:42:21 +0000
881@@ -1,5 +1,5 @@
882 /*
883- Copyright 2013-2014 Canonical Ltd.
884+ Copyright 2013-2015 Canonical Ltd.
885
886 This program is free software: you can redistribute it and/or modify it
887 under the terms of the GNU General Public License version 3, as published
888@@ -58,22 +58,37 @@
889 s.log = helpers.NewTestLogger(c, "debug")
890 }
891
892+var (
893+ helloCon = dbus.ObjectPath("hello")
894+ helloConProps = map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{helloCon}}
895+)
896+
897 /*
898- tests for connectedState's Start() method
899+ tests for ConnectedState's Start() method
900 */
901
902 // when given a working config and bus, Start() will work
903 func (s *ConnSuite) TestStartWorks(c *C) {
904- endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(networkmanager.Connecting))
905- cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
906+ endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(networkmanager.Connecting), helloCon)
907+ cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
908+
909+ nopTicker := make(chan []interface{})
910+ testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
911+ testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
912+ defer close(nopTicker)
913
914 c.Check(cs.start(), Equals, networkmanager.Connecting)
915 }
916
917 // if the bus fails a couple of times, we're still OK
918 func (s *ConnSuite) TestStartRetriesConnect(c *C) {
919- endp := testingbus.NewTestingEndpoint(condition.Fail2Work(2), condition.Work(true), uint32(networkmanager.Connecting))
920- cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
921+ endp := testingbus.NewTestingEndpoint(condition.Fail2Work(2), condition.Work(true), uint32(networkmanager.Connecting), helloCon)
922+ cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
923+
924+ nopTicker := make(chan []interface{})
925+ testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
926+ testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
927+ defer close(nopTicker)
928
929 c.Check(cs.start(), Equals, networkmanager.Connecting)
930 c.Check(cs.connAttempts, Equals, uint32(3)) // 1 more than the Fail2Work
931@@ -81,8 +96,13 @@
932
933 // when the calls to NetworkManager fails for a bit, we're still OK
934 func (s *ConnSuite) TestStartRetriesCall(c *C) {
935- endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Fail2Work(5), uint32(networkmanager.Connecting))
936- cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
937+ endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Fail2Work(5), uint32(networkmanager.Connecting), helloCon)
938+ cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
939+
940+ nopTicker := make(chan []interface{})
941+ testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
942+ testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
943+ defer close(nopTicker)
944
945 c.Check(cs.start(), Equals, networkmanager.Connecting)
946
947@@ -91,11 +111,19 @@
948
949 // when some of the calls to NetworkManager fails for a bit, we're still OK
950 func (s *ConnSuite) TestStartRetriesCall2(c *C) {
951- cond := condition.Chain(3, condition.Work(true), 1, condition.Work(false),
952+ cond := condition.Chain(1, condition.Work(true), 1, condition.Work(false),
953 1, condition.Work(true))
954
955- endp := testingbus.NewTestingEndpoint(condition.Work(true), cond, uint32(networkmanager.Connecting))
956- cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
957+ endp := testingbus.NewTestingEndpoint(condition.Work(true), cond,
958+ uint32(networkmanager.Connecting), helloCon,
959+ uint32(networkmanager.Connecting), helloCon,
960+ )
961+ cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
962+
963+ nopTicker := make(chan []interface{})
964+ testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
965+ testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
966+ defer close(nopTicker)
967
968 c.Check(cs.start(), Equals, networkmanager.Connecting)
969 }
970@@ -105,17 +133,25 @@
971 // watch, we recover and try again.
972 func (s *ConnSuite) TestStartRetriesWatch(c *C) {
973 nmcond := condition.Chain(
974- 1, condition.Work(true), // 1 call to nm works
975+ 2, condition.Work(true), // 2 call to nm works
976 1, condition.Work(false), // 1 call to nm fails
977 0, condition.Work(true)) // and everything works from there on
978 endp := testingbus.NewTestingEndpoint(condition.Work(true), nmcond,
979 uint32(networkmanager.Connecting),
980- uint32(networkmanager.ConnectedGlobal))
981- cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
982+ uint32(networkmanager.Connecting),
983+ helloCon,
984+ )
985+ cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
986+ watchTicker := make(chan []interface{}, 1)
987+ nopTicker := make(chan []interface{})
988+ testingbus.SetWatchSource(endp, "StateChanged", watchTicker)
989+ testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
990+ defer close(nopTicker)
991+ defer close(watchTicker)
992
993 c.Check(cs.start(), Equals, networkmanager.Connecting)
994 c.Check(cs.connAttempts, Equals, uint32(2))
995- c.Check(<-cs.networkStateCh, Equals, networkmanager.Connecting)
996+ watchTicker <- []interface{}{uint32(networkmanager.ConnectedGlobal)}
997 c.Check(<-cs.networkStateCh, Equals, networkmanager.ConnectedGlobal)
998 }
999
1000@@ -144,7 +180,7 @@
1001 }
1002 }
1003
1004-func (rep *racyEndpoint) WatchSignal(member string, f func(...interface{}), d func()) error {
1005+func (rep *racyEndpoint) WatchSignal(member string, f func(...interface{}), d func()) (bus.Cancellable, error) {
1006 if member == "StateChanged" {
1007 // we count never having gotten the state as happening "after" now.
1008 rep.lock.RLock()
1009@@ -157,7 +193,7 @@
1010 d()
1011 }()
1012 }
1013- return nil
1014+ return nil, nil
1015 }
1016
1017 func (*racyEndpoint) Close() {}
1018@@ -186,7 +222,7 @@
1019 func (s *ConnSuite) TestStartAvoidsRace(c *C) {
1020 for delta := time.Second; delta > 1; delta /= 2 {
1021 rep := &racyEndpoint{delta: delta}
1022- cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: rep}
1023+ cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: rep}
1024 f := Commentf("when delta=%s", delta)
1025 c.Assert(cs.start(), Equals, networkmanager.Connecting, f)
1026 c.Assert(takeNext(cs.networkStateCh), Equals, networkmanager.ConnectedGlobal, f)
1027@@ -194,9 +230,18 @@
1028 }
1029
1030 /*
1031- tests for connectedStateStep()
1032+ tests for step()
1033 */
1034
1035+type testWebchk func(ch chan<- bool)
1036+
1037+func (x testWebchk) Webcheck(ch chan<- bool) {
1038+ x(ch)
1039+}
1040+
1041+func (x testWebchk) Close() {
1042+}
1043+
1044 func (s *ConnSuite) TestSteps(c *C) {
1045 var webget_p condition.Interface = condition.Work(true)
1046 recheck_timeout := 50 * time.Millisecond
1047@@ -205,24 +250,24 @@
1048 RecheckTimeout: config.ConfigTimeDuration{recheck_timeout},
1049 }
1050 ch := make(chan networkmanager.State, 10)
1051- cs := &connectedState{
1052+ cs := &ConnectedState{
1053 config: cfg,
1054 networkStateCh: ch,
1055 timer: time.NewTimer(time.Second),
1056 log: s.log,
1057- webget: func(ch chan<- bool) { ch <- webget_p.OK() },
1058+ webchk: testWebchk(func(ch chan<- bool) { ch <- webget_p.OK() }),
1059 lastSent: false,
1060 }
1061 ch <- networkmanager.ConnectedGlobal
1062- f, e := cs.connectedStateStep()
1063+ f, e := cs.step()
1064 c.Check(e, IsNil)
1065 c.Check(f, Equals, true)
1066 ch <- networkmanager.Disconnected
1067 ch <- networkmanager.ConnectedGlobal
1068- f, e = cs.connectedStateStep()
1069+ f, e = cs.step()
1070 c.Check(e, IsNil)
1071 c.Check(f, Equals, false)
1072- f, e = cs.connectedStateStep()
1073+ f, e = cs.step()
1074 c.Check(e, IsNil)
1075 c.Check(f, Equals, true)
1076
1077@@ -230,7 +275,7 @@
1078 webget_p = condition.Fail2Work(1)
1079 ch <- networkmanager.Disconnected
1080 ch <- networkmanager.ConnectedGlobal
1081- f, e = cs.connectedStateStep()
1082+ f, e = cs.step()
1083 c.Check(e, IsNil)
1084 c.Check(f, Equals, false) // first false is from the Disconnected
1085
1086@@ -239,7 +284,7 @@
1087 _t := time.NewTimer(recheck_timeout / 2)
1088
1089 go func() {
1090- f, e := cs.connectedStateStep()
1091+ f, e := cs.step()
1092 c.Check(e, IsNil)
1093 _ch <- f
1094 }()
1095@@ -257,15 +302,15 @@
1096 ch <- networkmanager.Disconnected // this should not
1097 ch <- networkmanager.ConnectedGlobal // this should trigger a 'true'
1098
1099- f, e = cs.connectedStateStep()
1100+ f, e = cs.step()
1101 c.Check(e, IsNil)
1102 c.Check(f, Equals, false)
1103- f, e = cs.connectedStateStep()
1104+ f, e = cs.step()
1105 c.Check(e, IsNil)
1106 c.Check(f, Equals, true)
1107
1108 close(ch) // this should make it error out
1109- _, e = cs.connectedStateStep()
1110+ _, e = cs.step()
1111 c.Check(e, NotNil)
1112 }
1113
1114@@ -285,32 +330,50 @@
1115 }
1116
1117 endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true),
1118- uint32(networkmanager.ConnectedGlobal),
1119- uint32(networkmanager.Disconnected),
1120+ uint32(networkmanager.Disconnected),
1121+ helloCon,
1122+ uint32(networkmanager.Disconnected),
1123+ helloCon,
1124 )
1125
1126- watchTicker := make(chan bool)
1127- testingbus.SetWatchTicker(endp, watchTicker)
1128+ watchTicker := make(chan []interface{})
1129+ testingbus.SetWatchSource(endp, "StateChanged", watchTicker)
1130+ nopTicker := make(chan []interface{})
1131+ testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
1132
1133 out := make(chan bool)
1134 dt := time.Second / 10
1135 timer := time.NewTimer(dt)
1136- go ConnectedState(endp, cfg, s.log, out)
1137+ cs := New(endp, cfg, s.log)
1138+ defer cs.Cancel()
1139+ go cs.Track(out)
1140 var v bool
1141 expecteds := []struct {
1142- p bool
1143- s string
1144- n int
1145+ p bool
1146+ s string
1147+ todo string
1148 }{
1149- {false, "first state is always false", 0},
1150- {true, "then it should be true as per ConnectedGlobal above", 0},
1151- {false, "then it should be false (Disconnected)", 2},
1152- {false, "then it should be false again because it's restarted", 2},
1153+ {false, "first state is always false", ""},
1154+ {true, "then it should be true as per ConnectedGlobal above", "ConnectedGlobal"},
1155+ {false, "then it should be false (Disconnected)", "Disconnected"},
1156+ {false, "then it should be false again because it's restarted", "close"},
1157 }
1158
1159+ defer func() {
1160+ if watchTicker != nil {
1161+ close(watchTicker)
1162+ }
1163+ }()
1164+ defer close(nopTicker)
1165 for i, expected := range expecteds {
1166- for j := 0; j < expected.n; j++ {
1167- watchTicker <- true
1168+ switch expected.todo {
1169+ case "ConnectedGlobal":
1170+ watchTicker <- []interface{}{uint32(networkmanager.ConnectedGlobal)}
1171+ case "Disconnected":
1172+ watchTicker <- []interface{}{uint32(networkmanager.Disconnected)}
1173+ case "close":
1174+ close(watchTicker)
1175+ watchTicker = nil
1176 }
1177 timer.Reset(dt)
1178 select {
1179@@ -335,31 +398,41 @@
1180
1181 endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true),
1182 uint32(networkmanager.ConnectedGlobal),
1183- map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("hello")}},
1184+ helloCon,
1185 )
1186
1187- watchTicker := make(chan bool)
1188- testingbus.SetWatchTicker(endp, watchTicker)
1189+ watchTicker := make(chan []interface{})
1190+ testingbus.SetWatchSource(endp, "PropertiesChanged", watchTicker)
1191+ nopTicker := make(chan []interface{})
1192+ testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
1193
1194 out := make(chan bool)
1195 dt := time.Second / 10
1196 timer := time.NewTimer(dt)
1197- go ConnectedState(endp, cfg, s.log, out)
1198+ cs := New(endp, cfg, s.log)
1199+ defer cs.Cancel()
1200+ go cs.Track(out)
1201 var v bool
1202 expecteds := []struct {
1203- p bool
1204- s string
1205- n int
1206+ p bool
1207+ s string
1208+ changedConn bool
1209 }{
1210- {false, "first state is always false", 0},
1211- {true, "then it should be true as per ConnectedGlobal above", 0},
1212- {false, "then, false (PrimaryConnection changed)", 2},
1213- {true, "then it should be true (webcheck passed)", 0},
1214+ {false, "first state is always false", false},
1215+ {true, "then it should be true as per ConnectedGlobal above", false},
1216+ {false, "then, false (PrimaryConnection changed)", true},
1217+ {true, "then it should be true (webcheck passed)", false},
1218 }
1219
1220+ defer func() {
1221+ if watchTicker != nil {
1222+ close(watchTicker)
1223+ }
1224+ }()
1225+ defer close(nopTicker)
1226 for i, expected := range expecteds {
1227- for j := 0; j < expected.n; j++ {
1228- watchTicker <- true
1229+ if expected.changedConn {
1230+ watchTicker <- []interface{}{helloConProps}
1231 }
1232 timer.Reset(dt)
1233 select {
1234
1235=== modified file 'bus/connectivity/webchecker.go'
1236--- bus/connectivity/webchecker.go 2015-01-22 17:34:18 +0000
1237+++ bus/connectivity/webchecker.go 2015-03-26 16:42:21 +0000
1238@@ -1,5 +1,5 @@
1239 /*
1240- Copyright 2013-2014 Canonical Ltd.
1241+ Copyright 2013-2015 Canonical Ltd.
1242
1243 This program is free software: you can redistribute it and/or modify it
1244 under the terms of the GNU General Public License version 3, as published
1245@@ -40,6 +40,8 @@
1246 // contents match the target. If so, then it sends true; if anything
1247 // fails, it sends false.
1248 Webcheck(chan<- bool)
1249+ // Close idle connections.
1250+ Close()
1251 }
1252
1253 type webchecker struct {
1254@@ -72,8 +74,11 @@
1255 hash := md5.New()
1256 _, err = io.CopyN(hash, response.Body, 1024)
1257 if err != io.EOF {
1258- wb.log.Errorf("reading %s, expecting EOF, got: %v",
1259- wb.url, err)
1260+ if err == nil {
1261+ wb.log.Errorf("reading %s, but response body is larger than 1k.", wb.url)
1262+ } else {
1263+ wb.log.Errorf("reading %s, expecting EOF, got: %v", wb.url, err)
1264+ }
1265 ch <- false
1266 return
1267 }
1268@@ -86,3 +91,7 @@
1269 ch <- false
1270 }
1271 }
1272+
1273+func (wb *webchecker) Close() {
1274+ wb.cli.Transport.(*http13.Transport).CloseIdleConnections()
1275+}
1276
1277=== modified file 'bus/connectivity/webchecker_test.go'
1278--- bus/connectivity/webchecker_test.go 2014-03-20 12:24:33 +0000
1279+++ bus/connectivity/webchecker_test.go 2015-03-26 16:42:21 +0000
1280@@ -1,5 +1,5 @@
1281 /*
1282- Copyright 2013-2014 Canonical Ltd.
1283+ Copyright 2013-2015 Canonical Ltd.
1284
1285 This program is free software: you can redistribute it and/or modify it
1286 under the terms of the GNU General Public License version 3, as published
1287@@ -18,7 +18,6 @@
1288
1289 import (
1290 . "launchpad.net/gocheck"
1291- "launchpad.net/ubuntu-push/logger"
1292 helpers "launchpad.net/ubuntu-push/testing"
1293 "launchpad.net/ubuntu-push/util"
1294 "net/http"
1295@@ -28,7 +27,7 @@
1296
1297 type WebcheckerSuite struct {
1298 timeouts []time.Duration
1299- log logger.Logger
1300+ log *helpers.TestLogger
1301 }
1302
1303 var _ = Suite(&WebcheckerSuite{})
1304@@ -82,6 +81,7 @@
1305 defer ts.Close()
1306
1307 ck := NewWebchecker(ts.URL, staticHash, 5*time.Second, s.log)
1308+ defer ck.Close()
1309 ch := make(chan bool, 1)
1310 ck.Webcheck(ch)
1311 c.Check(<-ch, Equals, true)
1312@@ -90,6 +90,7 @@
1313 // Webchecker sends false if the download fails.
1314 func (s *WebcheckerSuite) TestActualFails(c *C) {
1315 ck := NewWebchecker("garbage://", "", 5*time.Second, s.log)
1316+ defer ck.Close()
1317 ch := make(chan bool, 1)
1318 ck.Webcheck(ch)
1319 c.Check(<-ch, Equals, false)
1320@@ -101,9 +102,11 @@
1321 defer ts.Close()
1322
1323 ck := NewWebchecker(ts.URL, staticHash, 5*time.Second, s.log)
1324+ defer ck.Close()
1325 ch := make(chan bool, 1)
1326 ck.Webcheck(ch)
1327 c.Check(<-ch, Equals, false)
1328+ c.Check(s.log.Captured(), Matches, "(?ism).*content mismatch.*")
1329 }
1330
1331 // Webchecker sends false if the download is too big
1332@@ -112,9 +115,11 @@
1333 defer ts.Close()
1334
1335 ck := NewWebchecker(ts.URL, bigHash, 5*time.Second, s.log)
1336+ defer ck.Close()
1337 ch := make(chan bool, 1)
1338 ck.Webcheck(ch)
1339 c.Check(<-ch, Equals, false)
1340+ c.Check(s.log.Captured(), Matches, "(?ism).*larger than 1k.*")
1341 }
1342
1343 // Webchecker sends false if the request timeouts
1344@@ -130,6 +135,7 @@
1345 }()
1346
1347 ck := NewWebchecker(ts.URL, bigHash, time.Second, s.log)
1348+ defer ck.Close()
1349 ch := make(chan bool, 1)
1350 ck.Webcheck(ch)
1351 c.Check(<-ch, Equals, false)
1352
1353=== modified file 'bus/endpoint.go'
1354--- bus/endpoint.go 2015-01-22 17:34:18 +0000
1355+++ bus/endpoint.go 2015-03-26 16:42:21 +0000
1356@@ -35,10 +35,15 @@
1357 type BusMethod func(string, []interface{}, []interface{}) ([]interface{}, error)
1358 type DispatchMap map[string]BusMethod
1359
1360+// Cancellable can be canceled.
1361+type Cancellable interface {
1362+ Cancel() error
1363+}
1364+
1365 // bus.Endpoint represents the DBus connection itself.
1366 type Endpoint interface {
1367 GrabName(allowReplacement bool) <-chan error
1368- WatchSignal(member string, f func(...interface{}), d func()) error
1369+ WatchSignal(member string, f func(...interface{}), d func()) (Cancellable, error)
1370 WatchMethod(DispatchMap, string, ...interface{})
1371 Signal(string, string, []interface{}) error
1372 Call(member string, args []interface{}, rvs ...interface{}) error
1373@@ -123,16 +128,16 @@
1374 // sends the values over a channel, and d() would close the channel.
1375 //
1376 // XXX: untested
1377-func (endp *endpoint) WatchSignal(member string, f func(...interface{}), d func()) error {
1378+func (endp *endpoint) WatchSignal(member string, f func(...interface{}), d func()) (Cancellable, error) {
1379 watch, err := endp.proxy.WatchSignal(endp.addr.Interface, member)
1380 if err != nil {
1381 endp.log.Debugf("failed to set up the watch: %s", err)
1382- return err
1383+ return nil, err
1384 }
1385
1386 go endp.unpackMessages(watch, f, d, member)
1387
1388- return nil
1389+ return watch, nil
1390 }
1391
1392 // Call() invokes the provided member method (on the name, path and
1393@@ -324,6 +329,6 @@
1394 }
1395 f(endp.unpackOneMsg(msg, member)...)
1396 }
1397- endp.log.Errorf("got not-OK from %s watch", member)
1398+ endp.log.Debugf("got not-OK from %s watch", member)
1399 d()
1400 }
1401
1402=== modified file 'bus/haptic/haptic.go'
1403--- bus/haptic/haptic.go 2014-08-08 01:07:38 +0000
1404+++ bus/haptic/haptic.go 2015-03-26 16:42:21 +0000
1405@@ -20,6 +20,7 @@
1406
1407 import (
1408 "launchpad.net/ubuntu-push/bus"
1409+ "launchpad.net/ubuntu-push/bus/accounts"
1410 "launchpad.net/ubuntu-push/click"
1411 "launchpad.net/ubuntu-push/launch_helper"
1412 "launchpad.net/ubuntu-push/logger"
1413@@ -36,12 +37,13 @@
1414 type Haptic struct {
1415 bus bus.Endpoint
1416 log logger.Logger
1417+ acc accounts.Accounts
1418 fallback *launch_helper.Vibration
1419 }
1420
1421 // New returns a new Haptic that'll use the provided bus.Endpoint
1422-func New(endp bus.Endpoint, log logger.Logger, fallback *launch_helper.Vibration) *Haptic {
1423- return &Haptic{endp, log, fallback}
1424+func New(endp bus.Endpoint, log logger.Logger, acc accounts.Accounts, fallback *launch_helper.Vibration) *Haptic {
1425+ return &Haptic{endp, log, acc, fallback}
1426 }
1427
1428 // Present presents the notification via a vibrate pattern
1429@@ -50,6 +52,11 @@
1430 panic("please check notification is not nil before calling present")
1431 }
1432
1433+ if !haptic.acc.Vibrate() {
1434+ haptic.log.Debugf("[%s] vibrate disabled by user.", nid)
1435+ return false
1436+ }
1437+
1438 vib := notification.Vibration(haptic.fallback)
1439 if vib == nil {
1440 haptic.log.Debugf("[%s] notification has no Vibrate.", nid)
1441
1442=== modified file 'bus/haptic/haptic_test.go'
1443--- bus/haptic/haptic_test.go 2014-08-08 01:07:38 +0000
1444+++ bus/haptic/haptic_test.go 2015-03-26 16:42:21 +0000
1445@@ -35,20 +35,36 @@
1446 type hapticSuite struct {
1447 log *helpers.TestLogger
1448 app *click.AppId
1449-}
1450+ acc *mockAccounts
1451+}
1452+
1453+type mockAccounts struct {
1454+ vib bool
1455+ sil bool
1456+ snd string
1457+ err error
1458+}
1459+
1460+func (m *mockAccounts) Start() error { return m.err }
1461+func (m *mockAccounts) Cancel() error { return m.err }
1462+func (m *mockAccounts) SilentMode() bool { return m.sil }
1463+func (m *mockAccounts) Vibrate() bool { return m.vib }
1464+func (m *mockAccounts) MessageSoundFile() string { return m.snd }
1465+func (m *mockAccounts) String() string { return "<mockAccounts>" }
1466
1467 var _ = Suite(&hapticSuite{})
1468
1469 func (hs *hapticSuite) SetUpTest(c *C) {
1470 hs.log = helpers.NewTestLogger(c, "debug")
1471 hs.app = clickhelp.MustParseAppId("com.example.test_test-app_0")
1472+ hs.acc = &mockAccounts{true, false, "xyzzy", nil}
1473 }
1474
1475 // checks that Present() actually calls VibratePattern
1476 func (hs *hapticSuite) TestPresentPresents(c *C) {
1477 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
1478
1479- ec := New(endp, hs.log, nil)
1480+ ec := New(endp, hs.log, hs.acc, nil)
1481 notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100], "repeat": 2}`)}
1482 c.Check(ec.Present(hs.app, "nid", &notif), Equals, true)
1483 callArgs := testibus.GetCallArgs(endp)
1484@@ -61,7 +77,7 @@
1485 func (hs *hapticSuite) TestPresentDefaultsRepeatTo1(c *C) {
1486 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
1487
1488- ec := New(endp, hs.log, nil)
1489+ ec := New(endp, hs.log, hs.acc, nil)
1490 // note: no Repeat:
1491 notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100]}`)}
1492 c.Check(ec.Present(hs.app, "nid", &notif), Equals, true)
1493@@ -76,7 +92,7 @@
1494 func (hs *hapticSuite) TestSkipIfMissing(c *C) {
1495 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
1496
1497- ec := New(endp, hs.log, nil)
1498+ ec := New(endp, hs.log, hs.acc, nil)
1499 // no Vibration in the notificaton
1500 c.Check(ec.Present(hs.app, "", &launch_helper.Notification{}), Equals, false)
1501 // empty Vibration
1502@@ -85,11 +101,24 @@
1503 c.Check(ec.Present(hs.app, "", &launch_helper.Notification{RawVibration: json.RawMessage(`{}`)}), Equals, false)
1504 }
1505
1506+// check that Present() does not present if the accounts' Vibrate() returns false
1507+func (hs *hapticSuite) TestPresentSkipsIfVibrateDisabled(c *C) {
1508+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
1509+ fallback := &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2}
1510+
1511+ ec := New(endp, hs.log, hs.acc, fallback)
1512+ notif := launch_helper.Notification{RawVibration: json.RawMessage(`true`)}
1513+ c.Assert(ec.Present(hs.app, "nid", &notif), Equals, true)
1514+ // ok!
1515+ hs.acc.vib = false
1516+ c.Check(ec.Present(hs.app, "nid", &notif), Equals, false)
1517+}
1518+
1519 // check that Present() panics if the notification is nil
1520 func (hs *hapticSuite) TestPanicsIfNil(c *C) {
1521 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
1522
1523- ec := New(endp, hs.log, nil)
1524+ ec := New(endp, hs.log, hs.acc, nil)
1525 // no notification at all
1526 c.Check(func() { ec.Present(hs.app, "", nil) }, Panics, `please check notification is not nil before calling present`)
1527 }
1528@@ -99,7 +128,7 @@
1529 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
1530 fallback := &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2}
1531
1532- ec := New(endp, hs.log, fallback)
1533+ ec := New(endp, hs.log, hs.acc, fallback)
1534 notif := launch_helper.Notification{RawVibration: json.RawMessage(`false`)}
1535 c.Check(ec.Present(hs.app, "nid", &notif), Equals, false)
1536 notif = launch_helper.Notification{RawVibration: json.RawMessage(`true`)}
1537
1538=== modified file 'bus/networkmanager/networkmanager.go'
1539--- bus/networkmanager/networkmanager.go 2015-01-22 17:34:18 +0000
1540+++ bus/networkmanager/networkmanager.go 2015-03-26 16:42:21 +0000
1541@@ -42,13 +42,13 @@
1542 GetState() State
1543 // WatchState listens for changes to NetworkManager's state, and sends
1544 // them out over the channel returned.
1545- WatchState() (<-chan State, error)
1546+ WatchState() (<-chan State, bus.Cancellable, error)
1547 // GetPrimaryConnection fetches and returns NetworkManager's current
1548 // primary connection.
1549 GetPrimaryConnection() string
1550 // WatchPrimaryConnection listens for changes of NetworkManager's
1551 // Primary Connection, and sends it out over the channel returned.
1552- WatchPrimaryConnection() (<-chan string, error)
1553+ WatchPrimaryConnection() (<-chan string, bus.Cancellable, error)
1554 }
1555
1556 type networkManager struct {
1557@@ -85,9 +85,9 @@
1558 return State(v)
1559 }
1560
1561-func (nm *networkManager) WatchState() (<-chan State, error) {
1562+func (nm *networkManager) WatchState() (<-chan State, bus.Cancellable, error) {
1563 ch := make(chan State)
1564- err := nm.bus.WatchSignal("StateChanged",
1565+ w, err := nm.bus.WatchSignal("StateChanged",
1566 func(ns ...interface{}) {
1567 stint, ok := ns[0].(uint32)
1568 if !ok {
1569@@ -101,10 +101,10 @@
1570 func() { close(ch) })
1571 if err != nil {
1572 nm.log.Debugf("Failed to set up the watch: %s", err)
1573- return nil, err
1574+ return nil, nil, err
1575 }
1576
1577- return ch, nil
1578+ return ch, w, nil
1579 }
1580
1581 func (nm *networkManager) GetPrimaryConnection() string {
1582@@ -124,9 +124,9 @@
1583 return string(v)
1584 }
1585
1586-func (nm *networkManager) WatchPrimaryConnection() (<-chan string, error) {
1587+func (nm *networkManager) WatchPrimaryConnection() (<-chan string, bus.Cancellable, error) {
1588 ch := make(chan string)
1589- err := nm.bus.WatchSignal("PropertiesChanged",
1590+ w, err := nm.bus.WatchSignal("PropertiesChanged",
1591 func(ppsi ...interface{}) {
1592 pps, ok := ppsi[0].(map[string]dbus.Variant)
1593 if !ok {
1594@@ -147,8 +147,8 @@
1595 }, func() { close(ch) })
1596 if err != nil {
1597 nm.log.Debugf("failed to set up the watch: %s", err)
1598- return nil, err
1599+ return nil, nil, err
1600 }
1601
1602- return ch, nil
1603+ return ch, w, nil
1604 }
1605
1606=== modified file 'bus/networkmanager/networkmanager_test.go'
1607--- bus/networkmanager/networkmanager_test.go 2014-04-04 12:01:42 +0000
1608+++ bus/networkmanager/networkmanager_test.go 2015-03-26 16:42:21 +0000
1609@@ -90,8 +90,9 @@
1610 func (s *NMSuite) TestWatchState(c *C) {
1611 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), uint32(Unknown), uint32(Asleep), uint32(ConnectedGlobal))
1612 nm := New(tc, s.log)
1613- ch, err := nm.WatchState()
1614- c.Check(err, IsNil)
1615+ ch, w, err := nm.WatchState()
1616+ c.Assert(err, IsNil)
1617+ defer w.Cancel()
1618 l := []State{<-ch, <-ch, <-ch}
1619 c.Check(l, DeepEquals, []State{Unknown, Asleep, ConnectedGlobal})
1620 }
1621@@ -99,7 +100,7 @@
1622 // WatchState returns on error if the dbus call fails
1623 func (s *NMSuite) TestWatchStateFails(c *C) {
1624 nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log)
1625- _, err := nm.WatchState()
1626+ _, _, err := nm.WatchState()
1627 c.Check(err, NotNil)
1628 }
1629
1630@@ -107,8 +108,9 @@
1631 func (s *NMSuite) TestWatchStateClosesOnWatchBail(c *C) {
1632 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true))
1633 nm := New(tc, s.log)
1634- ch, err := nm.WatchState()
1635- c.Check(err, IsNil)
1636+ ch, w, err := nm.WatchState()
1637+ c.Assert(err, IsNil)
1638+ defer w.Cancel()
1639 _, ok := <-ch
1640 c.Check(ok, Equals, false)
1641 }
1642@@ -117,8 +119,9 @@
1643 func (s *NMSuite) TestWatchStateSurvivesRubbishValues(c *C) {
1644 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "a")
1645 nm := New(tc, s.log)
1646- ch, err := nm.WatchState()
1647- c.Check(err, IsNil)
1648+ ch, w, err := nm.WatchState()
1649+ c.Assert(err, IsNil)
1650+ defer w.Cancel()
1651 _, ok := <-ch
1652 c.Check(ok, Equals, false)
1653 }
1654@@ -164,8 +167,9 @@
1655 mkPriConMap("/b/2"),
1656 mkPriConMap("/c/3"))
1657 nm := New(tc, s.log)
1658- ch, err := nm.WatchPrimaryConnection()
1659- c.Check(err, IsNil)
1660+ ch, w, err := nm.WatchPrimaryConnection()
1661+ c.Assert(err, IsNil)
1662+ defer w.Cancel()
1663 l := []string{<-ch, <-ch, <-ch}
1664 c.Check(l, DeepEquals, []string{"/a/1", "/b/2", "/c/3"})
1665 }
1666@@ -173,7 +177,7 @@
1667 // WatchPrimaryConnection returns on error if the dbus call fails
1668 func (s *NMSuite) TestWatchPrimaryConnectionFails(c *C) {
1669 nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log)
1670- _, err := nm.WatchPrimaryConnection()
1671+ _, _, err := nm.WatchPrimaryConnection()
1672 c.Check(err, NotNil)
1673 }
1674
1675@@ -181,8 +185,9 @@
1676 func (s *NMSuite) TestWatchPrimaryConnectionClosesOnWatchBail(c *C) {
1677 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true))
1678 nm := New(tc, s.log)
1679- ch, err := nm.WatchPrimaryConnection()
1680- c.Check(err, IsNil)
1681+ ch, w, err := nm.WatchPrimaryConnection()
1682+ c.Assert(err, IsNil)
1683+ defer w.Cancel()
1684 _, ok := <-ch
1685 c.Check(ok, Equals, false)
1686 }
1687@@ -191,8 +196,9 @@
1688 func (s *NMSuite) TestWatchPrimaryConnectionSurvivesRubbishValues(c *C) {
1689 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "a")
1690 nm := New(tc, s.log)
1691- ch, err := nm.WatchPrimaryConnection()
1692+ ch, w, err := nm.WatchPrimaryConnection()
1693 c.Assert(err, IsNil)
1694+ defer w.Cancel()
1695 _, ok := <-ch
1696 c.Check(ok, Equals, false)
1697 }
1698@@ -204,8 +210,9 @@
1699 map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("42")}},
1700 )
1701 nm := New(tc, s.log)
1702- ch, err := nm.WatchPrimaryConnection()
1703+ ch, w, err := nm.WatchPrimaryConnection()
1704 c.Assert(err, IsNil)
1705+ defer w.Cancel()
1706 v, ok := <-ch
1707 c.Check(ok, Equals, true)
1708 c.Check(v, Equals, "42")
1709@@ -218,8 +225,9 @@
1710 map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("42")}},
1711 )
1712 nm := New(tc, s.log)
1713- ch, err := nm.WatchPrimaryConnection()
1714+ ch, w, err := nm.WatchPrimaryConnection()
1715 c.Assert(err, IsNil)
1716+ defer w.Cancel()
1717 v, ok := <-ch
1718 c.Check(ok, Equals, true)
1719 c.Check(v, Equals, "42")
1720
1721=== modified file 'bus/notifications/raw.go'
1722--- bus/notifications/raw.go 2015-01-22 17:34:18 +0000
1723+++ bus/notifications/raw.go 2015-03-26 16:42:21 +0000
1724@@ -93,7 +93,7 @@
1725 // and sends them over the channel provided
1726 func (raw *RawNotifications) WatchActions() (<-chan *RawAction, error) {
1727 ch := make(chan *RawAction)
1728- err := raw.bus.WatchSignal("ActionInvoked",
1729+ _, err := raw.bus.WatchSignal("ActionInvoked",
1730 func(ns ...interface{}) {
1731 if len(ns) != 2 {
1732 raw.log.Debugf("ActionInvoked delivered %d things instead of 2", len(ns))
1733
1734=== modified file 'bus/notifications/raw_test.go'
1735--- bus/notifications/raw_test.go 2014-08-15 10:33:04 +0000
1736+++ bus/notifications/raw_test.go 2015-03-26 16:42:21 +0000
1737@@ -111,14 +111,16 @@
1738 errstr string
1739 endp bus.Endpoint
1740 works bool
1741+ src chan []interface{}
1742 }
1743
1744 func (s *RawSuite) TestWatchActionsToleratesDBusWeirdness(c *C) {
1745 X := func(errstr string, args ...interface{}) tst {
1746- endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), args)
1747- // stop the endpoint from closing the channel:
1748- testibus.SetWatchTicker(endp, make(chan bool))
1749- return tst{errstr, endp, errstr == ""}
1750+ endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true))
1751+ src := make(chan []interface{}, 1)
1752+ testibus.SetWatchSource(endp, "ActionInvoked", src)
1753+ src <- args
1754+ return tst{errstr, endp, errstr == "", src}
1755 }
1756
1757 ts := []tst{
1758@@ -146,6 +148,7 @@
1759 }
1760 c.Check(s.log.Captured(), Matches, `(?ms).*`+t.errstr+`.*`)
1761 s.log.ResetCapture()
1762+ close(t.src)
1763 }
1764
1765 }
1766
1767=== modified file 'bus/testing/testing_endpoint.go'
1768--- bus/testing/testing_endpoint.go 2014-07-04 23:00:42 +0000
1769+++ bus/testing/testing_endpoint.go 2015-03-26 16:42:21 +0000
1770@@ -36,13 +36,15 @@
1771 }
1772
1773 type testingEndpoint struct {
1774- dialCond condition.Interface
1775- callCond condition.Interface
1776- retvals [][]interface{}
1777- watchTicker chan bool
1778- watchLck sync.RWMutex
1779- callArgs []callArgs
1780- callArgsLck sync.RWMutex
1781+ dialCond condition.Interface
1782+ callCond condition.Interface
1783+ usedLck sync.Mutex
1784+ used int
1785+ retvals [][]interface{}
1786+ watchSources map[string]chan []interface{}
1787+ watchLck sync.RWMutex
1788+ callArgs []callArgs
1789+ callArgsLck sync.RWMutex
1790 }
1791
1792 // Build a bus.Endpoint that calls OK() on its condition before returning
1793@@ -51,7 +53,7 @@
1794 // NOTE: Call() always returns the first return value; Watch() will provide
1795 // each of them in turn, irrespective of whether Call has been called.
1796 func NewMultiValuedTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvalses ...[]interface{}) bus.Endpoint {
1797- return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses}
1798+ return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses, watchSources: make(map[string]chan []interface{})}
1799 }
1800
1801 func NewTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvals ...interface{}) bus.Endpoint {
1802@@ -59,15 +61,15 @@
1803 for i, x := range retvals {
1804 retvalses[i] = []interface{}{x}
1805 }
1806- return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses}
1807+ return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses, watchSources: make(map[string]chan []interface{})}
1808 }
1809
1810-// If SetWatchTicker is called with a non-nil watchTicker, it is used
1811-// instead of the default timeout to wait while sending values over
1812-// WatchSignal. Set it to nil again to restore default behaviour.
1813-func SetWatchTicker(tc bus.Endpoint, watchTicker chan bool) {
1814+// If SetWatchSource is called with a non-nil watchSource, it is used
1815+// instead of the default timeout and retvals to get values to send
1816+// over WatchSignal. Set it to nil again to restore default behaviour.
1817+func SetWatchSource(tc bus.Endpoint, member string, watchSource chan []interface{}) {
1818 tc.(*testingEndpoint).watchLck.Lock()
1819- tc.(*testingEndpoint).watchTicker = watchTicker
1820+ tc.(*testingEndpoint).watchSources[member] = watchSource
1821 tc.(*testingEndpoint).watchLck.Unlock()
1822 }
1823
1824@@ -78,27 +80,77 @@
1825 return tc.(*testingEndpoint).callArgs
1826 }
1827
1828+type watchCancel struct {
1829+ done chan struct{}
1830+ cancelled chan struct{}
1831+ lck sync.Mutex
1832+ member string
1833+}
1834+
1835+// this waits for actual cancelllation for test convenience
1836+func (wc *watchCancel) Cancel() error {
1837+ wc.lck.Lock()
1838+ defer wc.lck.Unlock()
1839+ if wc.cancelled != nil {
1840+ close(wc.cancelled)
1841+ wc.cancelled = nil
1842+ <-wc.done
1843+ }
1844+ return nil
1845+}
1846+
1847 // See Endpoint's WatchSignal. This WatchSignal will check its condition to
1848 // decide whether to return an error, or provide each of its return values
1849-func (tc *testingEndpoint) WatchSignal(member string, f func(...interface{}), d func()) error {
1850+// or values from the previously set watchSource for member.
1851+func (tc *testingEndpoint) WatchSignal(member string, f func(...interface{}), d func()) (bus.Cancellable, error) {
1852 if tc.callCond.OK() {
1853+ cancelled := make(chan struct{})
1854+ done := make(chan struct{})
1855 go func() {
1856- for _, v := range tc.retvals {
1857- f(v...)
1858- tc.watchLck.RLock()
1859- ticker := tc.watchTicker
1860- tc.watchLck.RUnlock()
1861- if ticker != nil {
1862- <-ticker
1863- } else {
1864- time.Sleep(10 * time.Millisecond)
1865+ tc.watchLck.RLock()
1866+ source := tc.watchSources[member]
1867+ tc.watchLck.RUnlock()
1868+ if source == nil {
1869+ tc.usedLck.Lock()
1870+ idx := tc.used
1871+ tc.used++
1872+ tc.usedLck.Unlock()
1873+ source = make(chan []interface{})
1874+ go func() {
1875+ Feed:
1876+ for _, v := range tc.retvals[idx:] {
1877+ select {
1878+ case source <- v:
1879+ case <-cancelled:
1880+ break Feed
1881+ }
1882+ select {
1883+ case <-time.After(10 * time.Millisecond):
1884+ case <-cancelled:
1885+ break Feed
1886+ }
1887+ }
1888+ close(source)
1889+ }()
1890+ }
1891+ Receive:
1892+ for {
1893+ select {
1894+ case v, ok := <-source:
1895+ if !ok {
1896+ break Receive
1897+ }
1898+ f(v...)
1899+ case <-cancelled:
1900+ break Receive
1901 }
1902 }
1903 d()
1904+ close(done)
1905 }()
1906- return nil
1907+ return &watchCancel{cancelled: cancelled, done: done, member: member}, nil
1908 } else {
1909- return errors.New("no way")
1910+ return nil, errors.New("no way")
1911 }
1912 }
1913
1914@@ -112,20 +164,24 @@
1915 if tc.callCond.OK() {
1916 expected := len(rvs)
1917 var provided int
1918- if len(tc.retvals) == 0 {
1919+ tc.usedLck.Lock()
1920+ idx := tc.used
1921+ tc.used++
1922+ tc.usedLck.Unlock()
1923+ if len(tc.retvals) <= idx {
1924 if expected != 0 {
1925 panic("No return values provided!")
1926 }
1927 provided = 0
1928 } else {
1929- provided = len(tc.retvals[0])
1930+ provided = len(tc.retvals[idx])
1931 }
1932 if provided != expected {
1933 return errors.New("provided/expected return vals mismatch")
1934 }
1935 if provided != 0 {
1936 x := dbus.NewMethodCallMessage("", "", "", "")
1937- err := x.AppendArgs(tc.retvals[0]...)
1938+ err := x.AppendArgs(tc.retvals[idx]...)
1939 if err != nil {
1940 return err
1941 }
1942
1943=== modified file 'bus/testing/testing_endpoint_test.go'
1944--- bus/testing/testing_endpoint_test.go 2014-07-04 23:00:42 +0000
1945+++ bus/testing/testing_endpoint_test.go 2015-03-26 16:42:21 +0000
1946@@ -17,11 +17,13 @@
1947 package testing
1948
1949 import (
1950+ "testing"
1951+ "time"
1952+
1953 . "launchpad.net/gocheck"
1954+
1955 "launchpad.net/ubuntu-push/bus"
1956 "launchpad.net/ubuntu-push/testing/condition"
1957- "testing"
1958- "time"
1959 )
1960
1961 // hook up gocheck
1962@@ -100,8 +102,9 @@
1963 var m, n uint32 = 42, 17
1964 endp := NewTestingEndpoint(nil, condition.Work(true), m, n)
1965 ch := make(chan uint32)
1966- e := endp.WatchSignal("what", func(us ...interface{}) { ch <- us[0].(uint32) }, func() { close(ch) })
1967- c.Check(e, IsNil)
1968+ w, e := endp.WatchSignal("which", func(us ...interface{}) { ch <- us[0].(uint32) }, func() { close(ch) })
1969+ c.Assert(e, IsNil)
1970+ defer w.Cancel()
1971 c.Check(<-ch, Equals, m)
1972 c.Check(<-ch, Equals, n)
1973 }
1974@@ -110,8 +113,9 @@
1975 func (s *TestingEndpointSuite) TestWatchDestructor(c *C) {
1976 endp := NewTestingEndpoint(nil, condition.Work(true))
1977 ch := make(chan uint32)
1978- e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
1979- c.Check(e, IsNil)
1980+ w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
1981+ c.Assert(e, IsNil)
1982+ defer w.Cancel()
1983 _, ok := <-ch
1984 c.Check(ok, Equals, false)
1985 }
1986@@ -130,25 +134,28 @@
1987 // Test that WatchSignal() with a negative condition returns an error.
1988 func (s *TestingEndpointSuite) TestWatchFails(c *C) {
1989 endp := NewTestingEndpoint(nil, condition.Work(false))
1990- e := endp.WatchSignal("what", func(us ...interface{}) {}, func() {})
1991+ w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() {})
1992 c.Check(e, NotNil)
1993+ c.Check(w, IsNil)
1994 }
1995
1996-// Test WatchSignal can use the WatchTicker instead of a timeout (if
1997+// Test WatchSignal can use a watchSource instead of a timeout and retvals (if
1998 // the former is not nil)
1999-func (s *TestingEndpointSuite) TestWatchTicker(c *C) {
2000- watchTicker := make(chan bool, 3)
2001- watchTicker <- true
2002- watchTicker <- true
2003- watchTicker <- true
2004+func (s *TestingEndpointSuite) TestWatchSources(c *C) {
2005+ watchTicker := make(chan []interface{}, 3)
2006+ watchTicker <- []interface{}{true}
2007+ watchTicker <- []interface{}{true}
2008+ watchTicker <- []interface{}{true}
2009 c.Assert(len(watchTicker), Equals, 3)
2010
2011 endp := NewTestingEndpoint(nil, condition.Work(true), 0, 0)
2012- SetWatchTicker(endp, watchTicker)
2013+ SetWatchSource(endp, "what", watchTicker)
2014 ch := make(chan int)
2015- e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
2016- c.Check(e, IsNil)
2017+ w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
2018+ c.Assert(e, IsNil)
2019+ defer w.Cancel()
2020
2021+ close(watchTicker)
2022 // wait for the destructor to be called
2023 select {
2024 case <-time.Tick(10 * time.Millisecond):
2025@@ -156,8 +163,21 @@
2026 case <-ch:
2027 }
2028
2029- // now if all went well, the ticker will have been tuck twice.
2030- c.Assert(len(watchTicker), Equals, 1)
2031+ // now if all went well, the ticker will have been exhausted.
2032+ c.Assert(len(watchTicker), Equals, 0)
2033+}
2034+
2035+// Test that WatchSignal() calls the destructor callback when canceled.
2036+func (s *TestingEndpointSuite) TestWatchCancel(c *C) {
2037+ endp := NewTestingEndpoint(nil, condition.Work(true))
2038+ ch := make(chan uint32)
2039+ w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
2040+ c.Assert(e, IsNil)
2041+ defer w.Cancel()
2042+ SetWatchSource(endp, "what", make(chan []interface{}))
2043+ w.Cancel()
2044+ _, ok := <-ch
2045+ c.Check(ok, Equals, false)
2046 }
2047
2048 // Tests that GetProperty() works
2049
2050=== modified file 'click/cappinfo/cappinfo.go'
2051--- click/cappinfo/cappinfo.go 2014-07-08 23:23:13 +0000
2052+++ click/cappinfo/cappinfo.go 2015-03-26 16:42:21 +0000
2053@@ -37,6 +37,26 @@
2054 g_free (desktop_id);
2055 return filename;
2056 }
2057+
2058+gchar* app_symbolic_icon_from_desktop_id (gchar* desktop_id) {
2059+ gchar* x_symbolic_icon;
2060+ GIcon* symbolic_icon;
2061+ GDesktopAppInfo* app_info = g_desktop_app_info_new (desktop_id);
2062+ if (app_info != NULL) {
2063+ if((x_symbolic_icon = g_desktop_app_info_get_string(app_info, "X-Ubuntu-SymbolicIcon"))) {
2064+ GFile *file;
2065+ file = g_file_new_for_path(x_symbolic_icon);
2066+ symbolic_icon = g_file_icon_new (file);
2067+ g_object_unref (file);
2068+ g_free(x_symbolic_icon);
2069+ g_object_unref (app_info);
2070+ return g_icon_to_string(symbolic_icon);
2071+ }
2072+ g_object_unref (app_info);
2073+ }
2074+ g_free (desktop_id);
2075+ return NULL;
2076+}
2077 */
2078 import "C"
2079
2080@@ -45,3 +65,14 @@
2081 defer C.g_free((C.gpointer)(name))
2082 return C.GoString((*C.char)(name))
2083 }
2084+
2085+func appSymbolicIconFromDesktopId(desktopId string) string {
2086+ name := C.app_symbolic_icon_from_desktop_id((*C.gchar)(C.CString(desktopId)))
2087+ if name == nil {
2088+ return ""
2089+ }
2090+ defer C.g_free((C.gpointer)(name))
2091+ return C.GoString((*C.char)(name))
2092+}
2093+
2094+var AppSymbolicIconFromDesktopId = appSymbolicIconFromDesktopId
2095
2096=== modified file 'click/cclick/cclick.go'
2097--- click/cclick/cclick.go 2014-07-07 22:04:30 +0000
2098+++ click/cclick/cclick.go 2015-03-26 16:42:21 +0000
2099@@ -51,6 +51,7 @@
2100 }
2101 ccu.cref = cref
2102 runtime.SetFinalizer(holder, func(interface{}) {
2103+ ccu.cref = nil // 1.3 gc gets confused otherwise
2104 C.g_object_unref((C.gpointer)(cref))
2105 })
2106 return nil
2107
2108=== modified file 'click/click.go'
2109--- click/click.go 2014-08-15 10:32:51 +0000
2110+++ click/click.go 2015-03-26 16:42:21 +0000
2111@@ -146,6 +146,10 @@
2112 var symbolic = _symbolic
2113
2114 func (app *AppId) SymbolicIcon() string {
2115+ symbolicIcon := cappinfo.AppSymbolicIconFromDesktopId(app.DesktopId())
2116+ if symbolicIcon != "" {
2117+ return symbolicIcon
2118+ }
2119 return symbolic(app.Icon())
2120 }
2121
2122
2123=== modified file 'click/click_test.go'
2124--- click/click_test.go 2014-08-15 10:32:51 +0000
2125+++ click/click_test.go 2015-03-26 16:42:21 +0000
2126@@ -22,6 +22,8 @@
2127 "testing"
2128
2129 . "launchpad.net/gocheck"
2130+
2131+ "launchpad.net/ubuntu-push/click/cappinfo"
2132 )
2133
2134 func TestClick(t *testing.T) { TestingT(t) }
2135@@ -200,3 +202,15 @@
2136 c.Assert(err, IsNil)
2137 c.Check(app.SymbolicIcon(), Equals, "xyzzy")
2138 }
2139+
2140+func (s *clickSuite) TestSymbolicFromDesktopFile(c *C) {
2141+ orig := cappinfo.AppSymbolicIconFromDesktopId
2142+ cappinfo.AppSymbolicIconFromDesktopId = func(desktopId string) string {
2143+ return "/foo/symbolic"
2144+ }
2145+ defer func() {
2146+ cappinfo.AppSymbolicIconFromDesktopId = orig
2147+ }()
2148+ app, _ := ParseAppId("com.ubuntu.clock_clock_1.2")
2149+ c.Check(app.SymbolicIcon(), Equals, "/foo/symbolic")
2150+}
2151
2152=== modified file 'client/client.go'
2153--- client/client.go 2015-01-22 17:34:18 +0000
2154+++ client/client.go 2015-03-26 16:42:21 +0000
2155@@ -115,8 +115,7 @@
2156 systemImageEndp bus.Endpoint
2157 systemImageInfo *systemimage.InfoResult
2158 connCh chan bool
2159- hasConnectivity bool
2160- session *session.ClientSession
2161+ session session.ClientSession
2162 sessionConnectedCh chan uint32
2163 pushService PushService
2164 postalService PostalService
2165@@ -125,16 +124,20 @@
2166 installedChecker click.InstalledChecker
2167 poller poller.Poller
2168 accountsCh <-chan accounts.Changed
2169+ // session-side channels
2170+ broadcastCh chan *session.BroadcastNotification
2171+ notificationsCh chan session.AddressedNotification
2172 }
2173
2174 // Creates a new Ubuntu Push Notifications client-side daemon that will use
2175 // the given configuration file.
2176 func NewPushClient(configPath string, leveldbPath string) *PushClient {
2177- client := new(PushClient)
2178- client.configPath = configPath
2179- client.leveldbPath = leveldbPath
2180-
2181- return client
2182+ return &PushClient{
2183+ configPath: configPath,
2184+ leveldbPath: leveldbPath,
2185+ broadcastCh: make(chan *session.BroadcastNotification),
2186+ notificationsCh: make(chan session.AddressedNotification),
2187+ }
2188 }
2189
2190 var newIdentifier = identifier.New
2191@@ -206,6 +209,8 @@
2192 AuthGetter: client.getAuthorization,
2193 AuthURL: client.config.SessionURL,
2194 AddresseeChecker: client,
2195+ BroadcastCh: client.broadcastCh,
2196+ NotificationsCh: client.notificationsCh,
2197 }
2198 }
2199
2200@@ -280,8 +285,10 @@
2201
2202 // takeTheBus starts the connection(s) to D-Bus and sets up associated event channels
2203 func (client *PushClient) takeTheBus() error {
2204- go connectivity.ConnectedState(client.connectivityEndp,
2205- client.config.ConnectivityConfig, client.log, client.connCh)
2206+ fmt.Println("FOO")
2207+ cs := connectivity.New(client.connectivityEndp,
2208+ client.config.ConnectivityConfig, client.log)
2209+ go cs.Track(client.connCh)
2210 util.NewAutoRedialer(client.systemImageEndp).Redial()
2211 sysimg := systemimage.New(client.systemImageEndp, client.log)
2212 info, err := sysimg.Info()
2213@@ -306,6 +313,7 @@
2214 return err
2215 }
2216 client.session = sess
2217+ sess.KeepConnection()
2218 client.poller = poller.New(client.derivePollerSetup())
2219 return nil
2220 }
2221@@ -374,29 +382,6 @@
2222 }
2223 }
2224
2225-// handleConnState deals with connectivity events
2226-func (client *PushClient) handleConnState(hasConnectivity bool) {
2227- client.log.Debugf("handleConnState: %v", hasConnectivity)
2228- if client.hasConnectivity == hasConnectivity {
2229- // nothing to do!
2230- return
2231- }
2232- client.hasConnectivity = hasConnectivity
2233- client.session.Close()
2234- if hasConnectivity {
2235- client.session.AutoRedial(client.sessionConnectedCh)
2236- }
2237-}
2238-
2239-// handleErr deals with the session erroring out of its loop
2240-func (client *PushClient) handleErr(err error) {
2241- // if we're not connected, we don't really care
2242- client.log.Errorf("session exited: %s", err)
2243- if client.hasConnectivity {
2244- client.session.AutoRedial(client.sessionConnectedCh)
2245- }
2246-}
2247-
2248 // filterBroadcastNotification finds out if the notification is about an actual
2249 // upgrade for the device. It expects msg.Decoded entries to look
2250 // like:
2251@@ -467,28 +452,18 @@
2252 return nil
2253 }
2254
2255-// handleAccountsChange deals with the user adding or removing (or
2256-// changing) the u1 account used to auth
2257-func (client *PushClient) handleAccountsChange() {
2258- client.log.Infof("U1 account changed; restarting session")
2259- client.session.ClearCookie()
2260- client.session.Close()
2261-}
2262-
2263 // doLoop connects events with their handlers
2264-func (client *PushClient) doLoop(connhandler func(bool), bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(session.AddressedNotification) error, errhandler func(error), unregisterhandler func(*click.AppId), accountshandler func()) {
2265+func (client *PushClient) doLoop(connhandler func(bool), bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(session.AddressedNotification) error, unregisterhandler func(*click.AppId), accountshandler func()) {
2266 for {
2267 select {
2268 case <-client.accountsCh:
2269 accountshandler()
2270 case state := <-client.connCh:
2271 connhandler(state)
2272- case bcast := <-client.session.BroadcastCh:
2273+ case bcast := <-client.broadcastCh:
2274 bcasthandler(bcast)
2275- case aucast := <-client.session.NotificationsCh:
2276+ case aucast := <-client.notificationsCh:
2277 ucasthandler(aucast)
2278- case err := <-client.session.ErrCh:
2279- errhandler(err)
2280 case count := <-client.sessionConnectedCh:
2281 client.log.Debugf("session connected after %d attempts", count)
2282 case app := <-client.unregisterCh:
2283@@ -510,12 +485,11 @@
2284
2285 // Loop calls doLoop with the "real" handlers
2286 func (client *PushClient) Loop() {
2287- client.doLoop(client.handleConnState,
2288+ client.doLoop(client.session.HasConnectivity,
2289 client.handleBroadcastNotification,
2290 client.handleUnicastNotification,
2291- client.handleErr,
2292 client.handleUnregister,
2293- client.handleAccountsChange,
2294+ client.session.ResetCookie,
2295 )
2296 }
2297
2298
2299=== modified file 'client/client_test.go'
2300--- client/client_test.go 2015-01-22 17:34:18 +0000
2301+++ client/client_test.go 2015-03-26 16:42:21 +0000
2302@@ -27,9 +27,11 @@
2303 "os"
2304 "path/filepath"
2305 "reflect"
2306+ //"runtime"
2307 "testing"
2308 "time"
2309
2310+ "launchpad.net/go-dbus/v1"
2311 . "launchpad.net/gocheck"
2312
2313 "launchpad.net/ubuntu-push/accounts"
2314@@ -41,7 +43,6 @@
2315 clickhelp "launchpad.net/ubuntu-push/click/testing"
2316 "launchpad.net/ubuntu-push/client/service"
2317 "launchpad.net/ubuntu-push/client/session"
2318- "launchpad.net/ubuntu-push/client/session/seenstate"
2319 "launchpad.net/ubuntu-push/config"
2320 "launchpad.net/ubuntu-push/identifier"
2321 idtesting "launchpad.net/ubuntu-push/identifier/testing"
2322@@ -203,6 +204,10 @@
2323 cs.writeTestConfig(nil)
2324 }
2325
2326+func (cs *clientSuite) TearDownTest(c *C) {
2327+ //helpers.DumpGoroutines()
2328+}
2329+
2330 type sqlientSuite struct{ clientSuite }
2331
2332 func (s *sqlientSuite) SetUpSuite(c *C) {
2333@@ -421,6 +426,8 @@
2334 AuthGetter: func(string) string { return "" },
2335 AuthURL: "xyzzy://",
2336 AddresseeChecker: cli,
2337+ BroadcastCh: make(chan *session.BroadcastNotification),
2338+ NotificationsCh: make(chan session.AddressedNotification),
2339 }
2340 // sanity check that we are looking at all fields
2341 vExpected := reflect.ValueOf(expected)
2342@@ -434,6 +441,11 @@
2343 conf := cli.deriveSessionConfig(info)
2344 // compare authGetter by string
2345 c.Check(fmt.Sprintf("%#v", conf.AuthGetter), Equals, fmt.Sprintf("%#v", cli.getAuthorization))
2346+ // channels are ok as long as non-nil
2347+ conf.BroadcastCh = nil
2348+ conf.NotificationsCh = nil
2349+ expected.BroadcastCh = nil
2350+ expected.NotificationsCh = nil
2351 // and set it to nil
2352 conf.AuthGetter = nil
2353 expected.AuthGetter = nil
2354@@ -515,10 +527,18 @@
2355 /*****************************************************************
2356 derivePollerSetup tests
2357 ******************************************************************/
2358+type derivePollerSession struct{}
2359+
2360+func (s *derivePollerSession) ResetCookie() {}
2361+func (s *derivePollerSession) State() session.ClientSessionState { return session.Unknown }
2362+func (s *derivePollerSession) HasConnectivity(bool) {}
2363+func (s *derivePollerSession) KeepConnection() error { return nil }
2364+func (s *derivePollerSession) StopKeepConnection() {}
2365+
2366 func (cs *clientSuite) TestDerivePollerSetup(c *C) {
2367 cs.writeTestConfig(map[string]interface{}{})
2368 cli := NewPushClient(cs.configPath, cs.leveldbPath)
2369- cli.session = new(session.ClientSession)
2370+ cli.session = new(derivePollerSession)
2371 err := cli.configure()
2372 c.Assert(err, IsNil)
2373 expected := &poller.PollerSetup{
2374@@ -647,11 +667,19 @@
2375 // testing endpoints
2376 cCond := condition.Fail2Work(7)
2377 cEndp := testibus.NewTestingEndpoint(cCond, condition.Work(true),
2378- uint32(networkmanager.ConnectedGlobal),
2379+ uint32(networkmanager.Connecting),
2380+ dbus.ObjectPath("hello"),
2381+ uint32(networkmanager.Connecting),
2382+ dbus.ObjectPath("hello"),
2383 )
2384 siCond := condition.Fail2Work(2)
2385 siEndp := testibus.NewMultiValuedTestingEndpoint(siCond, condition.Work(true), []interface{}{int32(101), "mako", "daily", "Unknown", map[string]string{}})
2386- testibus.SetWatchTicker(cEndp, make(chan bool))
2387+ tickerCh := make(chan []interface{})
2388+ nopTickerCh := make(chan []interface{})
2389+ testibus.SetWatchSource(cEndp, "StateChanged", tickerCh)
2390+ testibus.SetWatchSource(cEndp, "PropertiesChanged", nopTickerCh)
2391+ defer close(tickerCh)
2392+ defer close(nopTickerCh)
2393 // ok, create the thing
2394 cli := NewPushClient(cs.configPath, cs.leveldbPath)
2395 cli.log = cs.log
2396@@ -667,6 +695,7 @@
2397 c.Assert(cli.takeTheBus(), IsNil)
2398
2399 c.Check(takeNextBool(cli.connCh), Equals, false)
2400+ tickerCh <- []interface{}{uint32(networkmanager.ConnectedGlobal)}
2401 c.Check(takeNextBool(cli.connCh), Equals, true)
2402 // the connectivity endpoint retried until connected
2403 c.Check(cCond.OK(), Equals, true)
2404@@ -690,21 +719,6 @@
2405 }
2406
2407 /*****************************************************************
2408- handleErr tests
2409-******************************************************************/
2410-
2411-func (cs *clientSuite) TestHandleErr(c *C) {
2412- cli := NewPushClient(cs.configPath, cs.leveldbPath)
2413- cli.log = cs.log
2414- cli.systemImageInfo = siInfoRes
2415- c.Assert(cli.initSessionAndPoller(), IsNil)
2416- cs.log.ResetCapture()
2417- cli.hasConnectivity = true
2418- cli.handleErr(errors.New("bananas"))
2419- c.Check(cs.log.Captured(), Matches, ".*session exited.*bananas\n")
2420-}
2421-
2422-/*****************************************************************
2423 seenStateFactory tests
2424 ******************************************************************/
2425
2426@@ -712,6 +726,7 @@
2427 cli := NewPushClient(cs.configPath, "")
2428 ln, err := cli.seenStateFactory()
2429 c.Assert(err, IsNil)
2430+ defer ln.Close()
2431 c.Check(fmt.Sprintf("%T", ln), Equals, "*seenstate.memSeenState")
2432 }
2433
2434@@ -719,63 +734,11 @@
2435 cli := NewPushClient(cs.configPath, ":memory:")
2436 ln, err := cli.seenStateFactory()
2437 c.Assert(err, IsNil)
2438+ defer ln.Close()
2439 c.Check(fmt.Sprintf("%T", ln), Equals, "*seenstate.sqliteSeenState")
2440 }
2441
2442 /*****************************************************************
2443- handleConnState tests
2444-******************************************************************/
2445-
2446-func (cs *clientSuite) TestHandleConnStateD2C(c *C) {
2447- cli := NewPushClient(cs.configPath, cs.leveldbPath)
2448- cli.log = cs.log
2449- cli.systemImageInfo = siInfoRes
2450- c.Assert(cli.initSessionAndPoller(), IsNil)
2451-
2452- c.Assert(cli.hasConnectivity, Equals, false)
2453- cli.handleConnState(true)
2454- c.Check(cli.hasConnectivity, Equals, true)
2455- c.Assert(cli.session, NotNil)
2456-}
2457-
2458-func (cs *clientSuite) TestHandleConnStateSame(c *C) {
2459- cli := NewPushClient(cs.configPath, cs.leveldbPath)
2460- cli.log = cs.log
2461- // here we want to check that we don't do anything
2462- c.Assert(cli.session, IsNil)
2463- c.Assert(cli.hasConnectivity, Equals, false)
2464- cli.handleConnState(false)
2465- c.Check(cli.session, IsNil)
2466-
2467- cli.hasConnectivity = true
2468- cli.handleConnState(true)
2469- c.Check(cli.session, IsNil)
2470-}
2471-
2472-func (cs *clientSuite) TestHandleConnStateC2D(c *C) {
2473- cli := NewPushClient(cs.configPath, cs.leveldbPath)
2474- cli.log = cs.log
2475- cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(nil), cli.deviceId, seenstate.NewSeenState, cs.log)
2476- cli.session.Dial()
2477- cli.hasConnectivity = true
2478-
2479- // cli.session.State() will be "Error" here, for now at least
2480- c.Check(cli.session.State(), Not(Equals), session.Disconnected)
2481- cli.handleConnState(false)
2482- c.Check(cli.session.State(), Equals, session.Disconnected)
2483-}
2484-
2485-func (cs *clientSuite) TestHandleConnStateC2DPending(c *C) {
2486- cli := NewPushClient(cs.configPath, cs.leveldbPath)
2487- cli.log = cs.log
2488- cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(nil), cli.deviceId, seenstate.NewSeenState, cs.log)
2489- cli.hasConnectivity = true
2490-
2491- cli.handleConnState(false)
2492- c.Check(cli.session.State(), Equals, session.Disconnected)
2493-}
2494-
2495-/*****************************************************************
2496 filterBroadcastNotification tests
2497 ******************************************************************/
2498
2499@@ -993,7 +956,6 @@
2500 var nopConn = func(bool) {}
2501 var nopBcast = func(*session.BroadcastNotification) error { return nil }
2502 var nopUcast = func(session.AddressedNotification) error { return nil }
2503-var nopError = func(error) {}
2504 var nopUnregister = func(*click.AppId) {}
2505 var nopAcct = func() {}
2506
2507@@ -1006,7 +968,7 @@
2508 c.Assert(cli.initSessionAndPoller(), IsNil)
2509
2510 ch := make(chan bool, 1)
2511- go cli.doLoop(func(bool) { ch <- true }, nopBcast, nopUcast, nopError, nopUnregister, nopAcct)
2512+ go cli.doLoop(func(bool) { ch <- true }, nopBcast, nopUcast, nopUnregister, nopAcct)
2513 c.Check(takeNextBool(ch), Equals, true)
2514 }
2515
2516@@ -1015,11 +977,11 @@
2517 cli.log = cs.log
2518 cli.systemImageInfo = siInfoRes
2519 c.Assert(cli.initSessionAndPoller(), IsNil)
2520- cli.session.BroadcastCh = make(chan *session.BroadcastNotification, 1)
2521- cli.session.BroadcastCh <- &session.BroadcastNotification{}
2522+ cli.broadcastCh = make(chan *session.BroadcastNotification, 1)
2523+ cli.broadcastCh <- &session.BroadcastNotification{}
2524
2525 ch := make(chan bool, 1)
2526- go cli.doLoop(nopConn, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopError, nopUnregister, nopAcct)
2527+ go cli.doLoop(nopConn, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopUnregister, nopAcct)
2528 c.Check(takeNextBool(ch), Equals, true)
2529 }
2530
2531@@ -1028,24 +990,11 @@
2532 cli.log = cs.log
2533 cli.systemImageInfo = siInfoRes
2534 c.Assert(cli.initSessionAndPoller(), IsNil)
2535- cli.session.NotificationsCh = make(chan session.AddressedNotification, 1)
2536- cli.session.NotificationsCh <- session.AddressedNotification{}
2537-
2538- ch := make(chan bool, 1)
2539- go cli.doLoop(nopConn, nopBcast, func(session.AddressedNotification) error { ch <- true; return nil }, nopError, nopUnregister, nopAcct)
2540- c.Check(takeNextBool(ch), Equals, true)
2541-}
2542-
2543-func (cs *clientSuite) TestDoLoopErr(c *C) {
2544- cli := NewPushClient(cs.configPath, cs.leveldbPath)
2545- cli.log = cs.log
2546- cli.systemImageInfo = siInfoRes
2547- c.Assert(cli.initSessionAndPoller(), IsNil)
2548- cli.session.ErrCh = make(chan error, 1)
2549- cli.session.ErrCh <- nil
2550-
2551- ch := make(chan bool, 1)
2552- go cli.doLoop(nopConn, nopBcast, nopUcast, func(error) { ch <- true }, nopUnregister, nopAcct)
2553+ cli.notificationsCh = make(chan session.AddressedNotification, 1)
2554+ cli.notificationsCh <- session.AddressedNotification{}
2555+
2556+ ch := make(chan bool, 1)
2557+ go cli.doLoop(nopConn, nopBcast, func(session.AddressedNotification) error { ch <- true; return nil }, nopUnregister, nopAcct)
2558 c.Check(takeNextBool(ch), Equals, true)
2559 }
2560
2561@@ -1058,7 +1007,7 @@
2562 cli.unregisterCh <- app1
2563
2564 ch := make(chan bool, 1)
2565- go cli.doLoop(nopConn, nopBcast, nopUcast, nopError, func(app *click.AppId) { c.Check(app.Original(), Equals, appId1); ch <- true }, nopAcct)
2566+ go cli.doLoop(nopConn, nopBcast, nopUcast, func(app *click.AppId) { c.Check(app.Original(), Equals, appId1); ch <- true }, nopAcct)
2567 c.Check(takeNextBool(ch), Equals, true)
2568 }
2569
2570@@ -1072,7 +1021,7 @@
2571 cli.accountsCh = acctCh
2572
2573 ch := make(chan bool, 1)
2574- go cli.doLoop(nopConn, nopBcast, nopUcast, nopError, nopUnregister, func() { ch <- true })
2575+ go cli.doLoop(nopConn, nopBcast, nopUcast, nopUnregister, func() { ch <- true })
2576 c.Check(takeNextBool(ch), Equals, true)
2577 }
2578
2579@@ -1107,6 +1056,20 @@
2580 Loop() tests
2581 ******************************************************************/
2582
2583+type loopSession struct{ hasConn bool }
2584+
2585+func (s *loopSession) ResetCookie() {}
2586+func (s *loopSession) State() session.ClientSessionState {
2587+ if s.hasConn {
2588+ return session.Connected
2589+ } else {
2590+ return session.Disconnected
2591+ }
2592+}
2593+func (s *loopSession) HasConnectivity(hasConn bool) { s.hasConn = hasConn }
2594+func (s *loopSession) KeepConnection() error { return nil }
2595+func (s *loopSession) StopKeepConnection() {}
2596+
2597 func (cs *clientSuite) TestLoop(c *C) {
2598 cli := NewPushClient(cs.configPath, cs.leveldbPath)
2599 cli.connCh = make(chan bool)
2600@@ -1121,8 +1084,7 @@
2601
2602 c.Assert(cli.initSessionAndPoller(), IsNil)
2603
2604- cli.session.BroadcastCh = make(chan *session.BroadcastNotification)
2605- cli.session.ErrCh = make(chan error)
2606+ cli.broadcastCh = make(chan *session.BroadcastNotification)
2607
2608 // we use tick() to make sure things have been through the
2609 // event loop at least once before looking at things;
2610@@ -1130,6 +1092,10 @@
2611 // at and the loop itself.
2612 tick := func() { cli.sessionConnectedCh <- 42 }
2613
2614+ c.Assert(cli.session, NotNil)
2615+ cli.session.StopKeepConnection()
2616+ cli.session = &loopSession{}
2617+
2618 go cli.Loop()
2619
2620 // sessionConnectedCh to nothing in particular, but it'll help sync this test
2621@@ -1139,24 +1105,19 @@
2622
2623 // loop() should have connected:
2624 // * connCh to the connectivity checker
2625- c.Check(cli.hasConnectivity, Equals, false)
2626+ c.Check(cli.session.State(), Equals, session.Disconnected)
2627 cli.connCh <- true
2628 tick()
2629- c.Check(cli.hasConnectivity, Equals, true)
2630+ c.Check(cli.session.State(), Equals, session.Connected)
2631 cli.connCh <- false
2632 tick()
2633- c.Check(cli.hasConnectivity, Equals, false)
2634+ c.Check(cli.session.State(), Equals, session.Disconnected)
2635
2636 // * session.BroadcastCh to the notifications handler
2637 c.Check(d.bcastCount, Equals, 0)
2638- cli.session.BroadcastCh <- positiveBroadcastNotification
2639+ cli.broadcastCh <- positiveBroadcastNotification
2640 tick()
2641 c.Check(d.bcastCount, Equals, 1)
2642-
2643- // * session.ErrCh to the error handler
2644- cli.session.ErrCh <- nil
2645- tick()
2646- c.Check(cs.log.Captured(), Matches, "(?ms).*session exited.*")
2647 }
2648
2649 /*****************************************************************
2650
2651=== modified file 'client/service/postal.go'
2652--- client/service/postal.go 2015-01-22 17:34:18 +0000
2653+++ client/service/postal.go 2015-03-26 16:42:21 +0000
2654@@ -24,6 +24,7 @@
2655 "code.google.com/p/go-uuid/uuid"
2656
2657 "launchpad.net/ubuntu-push/bus"
2658+ "launchpad.net/ubuntu-push/bus/accounts"
2659 "launchpad.net/ubuntu-push/bus/emblemcounter"
2660 "launchpad.net/ubuntu-push/bus/haptic"
2661 "launchpad.net/ubuntu-push/bus/notifications"
2662@@ -75,6 +76,7 @@
2663 // the endpoints are only exposed for testing from client
2664 // XXX: uncouple some more so this isn't necessary
2665 EmblemCounterEndp bus.Endpoint
2666+ AccountsEndp bus.Endpoint
2667 HapticEndp bus.Endpoint
2668 NotificationsEndp bus.Endpoint
2669 UnityGreeterEndp bus.Endpoint
2670@@ -82,6 +84,7 @@
2671 // presenters:
2672 Presenters []Presenter
2673 emblemCounter *emblemcounter.EmblemCounter
2674+ accounts accounts.Accounts
2675 haptic *haptic.Haptic
2676 notifications *notifications.RawNotifications
2677 sound *sounds.Sound
2678@@ -117,6 +120,7 @@
2679 svc.fallbackSound = setup.FallbackSound
2680 svc.NotificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, log)
2681 svc.EmblemCounterEndp = bus.SessionBus.Endpoint(emblemcounter.BusAddress, log)
2682+ svc.AccountsEndp = bus.SystemBus.Endpoint(accounts.BusAddress, log)
2683 svc.HapticEndp = bus.SessionBus.Endpoint(haptic.BusAddress, log)
2684 svc.UnityGreeterEndp = bus.SessionBus.Endpoint(unitygreeter.BusAddress, log)
2685 svc.WindowStackEndp = bus.SessionBus.Endpoint(windowstack.BusAddress, log)
2686@@ -158,8 +162,13 @@
2687 svc.urlDispatcher = urldispatcher.New(svc.Log)
2688 svc.notifications = notifications.Raw(svc.NotificationsEndp, svc.Log)
2689 svc.emblemCounter = emblemcounter.New(svc.EmblemCounterEndp, svc.Log)
2690- svc.haptic = haptic.New(svc.HapticEndp, svc.Log, svc.fallbackVibration)
2691- svc.sound = sounds.New(svc.Log, svc.fallbackSound)
2692+ svc.accounts = accounts.New(svc.AccountsEndp, svc.Log)
2693+ err = svc.accounts.Start()
2694+ if err != nil {
2695+ return err
2696+ }
2697+ svc.haptic = haptic.New(svc.HapticEndp, svc.Log, svc.accounts, svc.fallbackVibration)
2698+ svc.sound = sounds.New(svc.Log, svc.accounts, svc.fallbackSound)
2699 svc.messagingMenu = messaging.New(svc.Log)
2700 svc.Presenters = []Presenter{
2701 svc.notifications,
2702@@ -228,6 +237,7 @@
2703 }{
2704 {"notifications", svc.NotificationsEndp},
2705 {"emblemcounter", svc.EmblemCounterEndp},
2706+ {"accounts", svc.AccountsEndp},
2707 {"haptic", svc.HapticEndp},
2708 {"unitygreeter", svc.UnityGreeterEndp},
2709 {"windowstack", svc.WindowStackEndp},
2710
2711=== modified file 'client/service/postal_test.go'
2712--- client/service/postal_test.go 2014-09-09 22:54:04 +0000
2713+++ client/service/postal_test.go 2015-03-26 16:42:21 +0000
2714@@ -169,6 +169,8 @@
2715 hapticBus bus.Endpoint
2716 unityGreeterBus bus.Endpoint
2717 winStackBus bus.Endpoint
2718+ accountsBus bus.Endpoint
2719+ accountsCh chan []interface{}
2720 fakeLauncher *fakeHelperLauncher
2721 getTempDir func(string) (string, error)
2722 oldIsBlisted func(*click.AppId) bool
2723@@ -194,6 +196,7 @@
2724 ps.bus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
2725 ps.notifBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
2726 ps.counterBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
2727+ ps.accountsBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), map[string]dbus.Variant{"IncomingMessageVibrate": dbus.Variant{true}})
2728 ps.hapticBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
2729 ps.unityGreeterBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), false)
2730 ps.winStackBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{})
2731@@ -206,11 +209,15 @@
2732 tmpDir := filepath.Join(d, pkgName)
2733 return tmpDir, os.MkdirAll(tmpDir, 0700)
2734 }
2735+
2736+ ps.accountsCh = make(chan []interface{})
2737+ testibus.SetWatchSource(ps.accountsBus, "PropertiesChanged", ps.accountsCh)
2738 }
2739
2740 func (ps *postalSuite) TearDownTest(c *C) {
2741 isBlacklisted = ps.oldIsBlisted
2742 launch_helper.GetTempDir = ps.getTempDir
2743+ close(ps.accountsCh)
2744 }
2745
2746 func (ts *trivialPostalSuite) SetUpTest(c *C) {
2747@@ -227,6 +234,7 @@
2748 pst.Bus = ps.bus
2749 pst.NotificationsEndp = ps.notifBus
2750 pst.EmblemCounterEndp = ps.counterBus
2751+ pst.AccountsEndp = ps.accountsBus
2752 pst.HapticEndp = ps.hapticBus
2753 pst.UnityGreeterEndp = ps.unityGreeterBus
2754 pst.WindowStackEndp = ps.winStackBus
2755@@ -544,6 +552,7 @@
2756 svc := NewPostalService(ps.cfg, ps.log)
2757 svc.Bus = endp
2758 svc.EmblemCounterEndp = endp
2759+ svc.AccountsEndp = ps.accountsBus
2760 svc.HapticEndp = endp
2761 svc.NotificationsEndp = endp
2762 svc.UnityGreeterEndp = ps.unityGreeterBus
2763@@ -552,6 +561,10 @@
2764 svc.fallbackVibration = &launch_helper.Vibration{Pattern: []uint32{1}}
2765 c.Assert(svc.Start(), IsNil)
2766
2767+ nopTicker := make(chan []interface{})
2768+ testibus.SetWatchSource(endp, "ActionInvoked", nopTicker)
2769+ defer close(nopTicker)
2770+
2771 // Persist is false so we just check the log
2772 card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true, Persist: false}
2773 vib := json.RawMessage(`true`)
2774@@ -837,6 +850,10 @@
2775 }
2776
2777 func (ps *postalSuite) TestBlacklisted(c *C) {
2778+ ps.winStackBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{},
2779+ []windowstack.WindowsInfo{},
2780+ []windowstack.WindowsInfo{},
2781+ []windowstack.WindowsInfo{})
2782 svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
2783 svc.Start()
2784 ps.blacklisted = false
2785
2786=== modified file 'client/service/service.go'
2787--- client/service/service.go 2015-01-22 17:34:18 +0000
2788+++ client/service/service.go 2015-03-26 16:42:21 +0000
2789@@ -140,6 +140,8 @@
2790 case resp.StatusCode >= http.StatusInternalServerError:
2791 // XXX retry on 503
2792 return nil, ErrBadServer
2793+ case resp.StatusCode == http.StatusUnauthorized:
2794+ return nil, ErrBadAuth
2795 default:
2796 return nil, ErrBadRequest
2797 }
2798
2799=== modified file 'client/service/service_test.go'
2800--- client/service/service_test.go 2014-08-06 09:01:59 +0000
2801+++ client/service/service_test.go 2015-03-26 16:42:21 +0000
2802@@ -19,6 +19,7 @@
2803 import (
2804 "encoding/json"
2805 "fmt"
2806+ "io"
2807 "net/http"
2808 "net/http/httptest"
2809 "os"
2810@@ -179,7 +180,8 @@
2811 func (ss *serviceSuite) TestRegistrationWorks(c *C) {
2812 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2813 buf := make([]byte, 256)
2814- n, e := r.Body.Read(buf)
2815+ n := r.ContentLength
2816+ _, e := io.ReadFull(r.Body, buf[:n])
2817 c.Assert(e, IsNil)
2818 req := registrationRequest{}
2819 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
2820@@ -240,6 +242,23 @@
2821 c.Check(err, ErrorMatches, "unable to request registration: .*")
2822 }
2823
2824+func (ss *serviceSuite) TestManageRegFailsOn401(c *C) {
2825+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2826+ http.Error(w, "Unauthorized", 401)
2827+ }))
2828+ defer ts.Close()
2829+ setup := &PushServiceSetup{
2830+ DeviceId: "fake-device-id",
2831+ RegURL: helpers.ParseURL(ts.URL),
2832+ AuthGetter: func(string) string { return "tok" },
2833+ }
2834+ svc := NewPushService(setup, ss.log)
2835+ svc.Bus = ss.bus
2836+ reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil)
2837+ c.Check(err, Equals, ErrBadAuth)
2838+ c.Check(reg, IsNil)
2839+}
2840+
2841 func (ss *serviceSuite) TestManageRegFailsOn40x(c *C) {
2842 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2843 http.Error(w, "I'm a teapot", 418)
2844@@ -277,7 +296,8 @@
2845 func (ss *serviceSuite) TestManageRegFailsOnBadJSON(c *C) {
2846 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2847 buf := make([]byte, 256)
2848- n, e := r.Body.Read(buf)
2849+ n := r.ContentLength
2850+ _, e := io.ReadFull(r.Body, buf[:n])
2851 c.Assert(e, IsNil)
2852 req := registrationRequest{}
2853 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
2854@@ -303,7 +323,8 @@
2855 func (ss *serviceSuite) TestManageRegFailsOnBadJSONDocument(c *C) {
2856 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2857 buf := make([]byte, 256)
2858- n, e := r.Body.Read(buf)
2859+ n := r.ContentLength
2860+ _, e := io.ReadFull(r.Body, buf[:n])
2861 c.Assert(e, IsNil)
2862 req := registrationRequest{}
2863 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
2864@@ -329,7 +350,8 @@
2865 func (ss *serviceSuite) TestDBusUnregisterWorks(c *C) {
2866 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2867 buf := make([]byte, 256)
2868- n, e := r.Body.Read(buf)
2869+ n := r.ContentLength
2870+ _, e := io.ReadFull(r.Body, buf[:n])
2871 c.Assert(e, IsNil)
2872 req := registrationRequest{}
2873 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
2874@@ -356,7 +378,8 @@
2875 invoked := make(chan bool, 1)
2876 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2877 buf := make([]byte, 256)
2878- n, e := r.Body.Read(buf)
2879+ n := r.ContentLength
2880+ _, e := io.ReadFull(r.Body, buf[:n])
2881 c.Assert(e, IsNil)
2882 req := registrationRequest{}
2883 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
2884
2885=== modified file 'client/session/seenstate/seenstate.go'
2886--- client/session/seenstate/seenstate.go 2014-05-14 17:42:24 +0000
2887+++ client/session/seenstate/seenstate.go 2015-03-26 16:42:21 +0000
2888@@ -28,8 +28,10 @@
2889 // GetAll() returns a "simple" map of the current levels.
2890 GetAllLevels() (map[string]int64, error)
2891 // FilterBySeen filters notifications already seen, keep track
2892- // of them as well
2893+ // of them as well.
2894 FilterBySeen([]protocol.Notification) ([]protocol.Notification, error)
2895+ // Close closes state.
2896+ Close()
2897 }
2898
2899 type memSeenState struct {
2900@@ -58,6 +60,9 @@
2901 return acc, nil
2902 }
2903
2904+func (m *memSeenState) Close() {
2905+}
2906+
2907 var _ SeenState = (*memSeenState)(nil)
2908
2909 // NewSeenState returns an implementation of SeenState that is memory-based and
2910
2911=== modified file 'client/session/seenstate/sqlseenstate.go'
2912--- client/session/seenstate/sqlseenstate.go 2014-05-14 17:42:24 +0000
2913+++ client/session/seenstate/sqlseenstate.go 2015-03-26 16:42:21 +0000
2914@@ -48,6 +48,11 @@
2915 return &sqliteSeenState{db}, nil
2916 }
2917
2918+// Closes closes the underlying db.
2919+func (ps *sqliteSeenState) Close() {
2920+ ps.db.Close()
2921+}
2922+
2923 func (ps *sqliteSeenState) SetLevel(level string, top int64) error {
2924 _, err := ps.db.Exec("REPLACE INTO level_map (level, top) VALUES (?, ?)", level, top)
2925 if err != nil {
2926
2927=== modified file 'client/session/seenstate/sqlseenstate_test.go'
2928--- client/session/seenstate/sqlseenstate_test.go 2014-05-14 17:42:24 +0000
2929+++ client/session/seenstate/sqlseenstate_test.go 2015-03-26 16:42:21 +0000
2930@@ -112,6 +112,15 @@
2931 c.Check(err, ErrorMatches, "cannot insert .*")
2932 }
2933
2934+func (s *sqlsSuite) TestClose(c *C) {
2935+ dir := c.MkDir()
2936+ filename := dir + "test.db"
2937+ sqls, err := NewSqliteSeenState(filename)
2938+ c.Check(err, IsNil)
2939+ c.Assert(sqls, NotNil)
2940+ sqls.Close()
2941+}
2942+
2943 func (s *sqlsSuite) TestDropPrevThan(c *C) {
2944 dir := c.MkDir()
2945 filename := dir + "test.db"
2946
2947=== modified file 'client/session/session.go'
2948--- client/session/session.go 2015-01-22 17:34:18 +0000
2949+++ client/session/session.go 2015-03-26 16:42:21 +0000
2950@@ -39,6 +39,14 @@
2951 "launchpad.net/ubuntu-push/util"
2952 )
2953
2954+type sessCmd uint8
2955+
2956+const (
2957+ cmdDisconnect sessCmd = iota
2958+ cmdConnect
2959+ cmdResetCookie
2960+)
2961+
2962 var (
2963 wireVersionBytes = []byte{protocol.ProtocolWireVersion}
2964 )
2965@@ -70,10 +78,12 @@
2966
2967 const (
2968 Error ClientSessionState = iota
2969+ Pristine
2970 Disconnected
2971 Connected
2972 Started
2973 Running
2974+ Shutdown
2975 Unknown
2976 )
2977
2978@@ -83,10 +93,12 @@
2979 }
2980 return [Unknown]string{
2981 "Error",
2982+ "Pristine",
2983 "Disconnected",
2984 "Connected",
2985 "Started",
2986 "Running",
2987+ "Shutdown",
2988 }[s]
2989 }
2990
2991@@ -118,10 +130,20 @@
2992 AuthGetter func(string) string
2993 AuthURL string
2994 AddresseeChecker AddresseeChecking
2995+ BroadcastCh chan *BroadcastNotification
2996+ NotificationsCh chan AddressedNotification
2997 }
2998
2999 // ClientSession holds a client<->server session and its configuration.
3000-type ClientSession struct {
3001+type ClientSession interface {
3002+ ResetCookie()
3003+ State() ClientSessionState
3004+ HasConnectivity(bool)
3005+ KeepConnection() error
3006+ StopKeepConnection()
3007+}
3008+
3009+type clientSession struct {
3010 // configuration
3011 DeviceId string
3012 ClientSessionConfig
3013@@ -145,25 +167,36 @@
3014 proto protocol.Protocol
3015 pingInterval time.Duration
3016 retrier util.AutoRedialer
3017- retrierLock sync.Mutex
3018 cookie string
3019 // status
3020- stateP *uint32
3021- ErrCh chan error
3022- BroadcastCh chan *BroadcastNotification
3023- NotificationsCh chan AddressedNotification
3024+ stateLock sync.RWMutex
3025+ state ClientSessionState
3026 // authorization
3027 auth string
3028 // autoredial knobs
3029 shouldDelayP *uint32
3030 lastAutoRedial time.Time
3031- redialDelay func(*ClientSession) time.Duration
3032+ redialDelay func(*clientSession) time.Duration
3033 redialJitter func(time.Duration) time.Duration
3034 redialDelays []time.Duration
3035 redialDelaysIdx int
3036+ // connection events, and cookie reset requests, come in over here
3037+ cmdCh chan sessCmd
3038+ // last seen connection event is here
3039+ lastConn bool
3040+ // connection events are handled by this
3041+ connHandler func(bool)
3042+ // autoredial goes over here (xxx spurious goroutine involved)
3043+ doneCh chan uint32
3044+ // main loop errors out through here (possibly another spurious goroutine)
3045+ errCh chan error
3046+ // main loop errors are handled by this
3047+ errHandler func(error)
3048+ // look, a stopper!
3049+ stopCh chan struct{}
3050 }
3051
3052-func redialDelay(sess *ClientSession) time.Duration {
3053+func redialDelay(sess *clientSession) time.Duration {
3054 if sess.ShouldDelay() {
3055 t := sess.redialDelays[sess.redialDelaysIdx]
3056 if len(sess.redialDelays) > sess.redialDelaysIdx+1 {
3057@@ -178,8 +211,7 @@
3058
3059 func NewSession(serverAddrSpec string, conf ClientSessionConfig,
3060 deviceId string, seenStateFactory func() (seenstate.SeenState, error),
3061- log logger.Logger) (*ClientSession, error) {
3062- state := uint32(Disconnected)
3063+ log logger.Logger) (*clientSession, error) {
3064 seenState, err := seenStateFactory()
3065 if err != nil {
3066 return nil, err
3067@@ -191,7 +223,7 @@
3068 getHost = gethosts.New(deviceId, hostsEndpoint, conf.ExchangeTimeout)
3069 }
3070 var shouldDelay uint32 = 0
3071- sess := &ClientSession{
3072+ sess := &clientSession{
3073 ClientSessionConfig: conf,
3074 getHost: getHost,
3075 fallbackHosts: fallbackHosts,
3076@@ -200,10 +232,10 @@
3077 Protocolator: protocol.NewProtocol0,
3078 SeenState: seenState,
3079 TLS: &tls.Config{},
3080- stateP: &state,
3081+ state: Pristine,
3082 timeSince: time.Since,
3083 shouldDelayP: &shouldDelay,
3084- redialDelay: redialDelay,
3085+ redialDelay: redialDelay, // NOTE there are tests that use calling sess.redialDelay as an indication of calling autoRedial!
3086 redialDelays: util.Timeouts(),
3087 }
3088 sess.redialJitter = sess.Jitter
3089@@ -215,62 +247,78 @@
3090 }
3091 sess.TLS.RootCAs = cp
3092 }
3093+ sess.doneCh = make(chan uint32, 1)
3094+ sess.stopCh = make(chan struct{})
3095+ sess.cmdCh = make(chan sessCmd)
3096+ sess.errCh = make(chan error, 1)
3097+
3098+ // to be overridden by tests
3099+ sess.connHandler = sess.handleConn
3100+ sess.errHandler = sess.handleErr
3101+
3102 return sess, nil
3103 }
3104
3105-func (sess *ClientSession) ShouldDelay() bool {
3106+func (sess *clientSession) ShouldDelay() bool {
3107 return atomic.LoadUint32(sess.shouldDelayP) != 0
3108 }
3109
3110-func (sess *ClientSession) setShouldDelay() {
3111+func (sess *clientSession) setShouldDelay() {
3112 atomic.StoreUint32(sess.shouldDelayP, uint32(1))
3113 }
3114
3115-func (sess *ClientSession) clearShouldDelay() {
3116+func (sess *clientSession) clearShouldDelay() {
3117 atomic.StoreUint32(sess.shouldDelayP, uint32(0))
3118 }
3119
3120-func (sess *ClientSession) State() ClientSessionState {
3121- return ClientSessionState(atomic.LoadUint32(sess.stateP))
3122-}
3123-
3124-func (sess *ClientSession) setState(state ClientSessionState) {
3125- sess.Log.Debugf("session.setState: %s -> %s", ClientSessionState(atomic.LoadUint32(sess.stateP)), state)
3126- atomic.StoreUint32(sess.stateP, uint32(state))
3127-}
3128-
3129-func (sess *ClientSession) setConnection(conn net.Conn) {
3130+func (sess *clientSession) State() ClientSessionState {
3131+ sess.stateLock.RLock()
3132+ defer sess.stateLock.RUnlock()
3133+ return sess.state
3134+}
3135+
3136+func (sess *clientSession) setState(state ClientSessionState) {
3137+ sess.stateLock.Lock()
3138+ defer sess.stateLock.Unlock()
3139+ sess.Log.Debugf("session.setState: %s -> %s", sess.state, state)
3140+ sess.state = state
3141+}
3142+
3143+func (sess *clientSession) setConnection(conn net.Conn) {
3144 sess.connLock.Lock()
3145 defer sess.connLock.Unlock()
3146 sess.Connection = conn
3147 }
3148
3149-func (sess *ClientSession) getConnection() net.Conn {
3150+func (sess *clientSession) getConnection() net.Conn {
3151 sess.connLock.RLock()
3152 defer sess.connLock.RUnlock()
3153 return sess.Connection
3154 }
3155
3156-func (sess *ClientSession) setCookie(cookie string) {
3157+func (sess *clientSession) setCookie(cookie string) {
3158 sess.connLock.Lock()
3159 defer sess.connLock.Unlock()
3160 sess.cookie = cookie
3161 }
3162
3163-func (sess *ClientSession) getCookie() string {
3164+func (sess *clientSession) getCookie() string {
3165 sess.connLock.RLock()
3166 defer sess.connLock.RUnlock()
3167 return sess.cookie
3168 }
3169
3170-func (sess *ClientSession) ClearCookie() {
3171- sess.connLock.Lock()
3172- defer sess.connLock.Unlock()
3173- sess.cookie = ""
3174+func (sess *clientSession) ResetCookie() {
3175+ sess.cmdCh <- cmdResetCookie
3176+}
3177+
3178+func (sess *clientSession) resetCookie() {
3179+ sess.stopRedial()
3180+ sess.doClose(true)
3181 }
3182
3183 // getHosts sets deliveryHosts possibly querying a remote endpoint
3184-func (sess *ClientSession) getHosts() error {
3185+func (sess *clientSession) getHosts() error {
3186 if sess.getHost != nil {
3187 if sess.deliveryHosts != nil && sess.timeSince(sess.deliveryHostsTimestamp) < sess.HostsCachingExpiryTime {
3188 return nil
3189@@ -294,7 +342,7 @@
3190
3191 // addAuthorization gets the authorization blob to send to the server
3192 // and adds it to the session.
3193-func (sess *ClientSession) addAuthorization() error {
3194+func (sess *clientSession) addAuthorization() error {
3195 if sess.AuthGetter != nil {
3196 sess.Log.Debugf("adding authorization")
3197 sess.auth = sess.AuthGetter(sess.AuthURL)
3198@@ -302,13 +350,13 @@
3199 return nil
3200 }
3201
3202-func (sess *ClientSession) resetHosts() {
3203+func (sess *clientSession) resetHosts() {
3204 sess.deliveryHosts = nil
3205 }
3206
3207 // startConnectionAttempt/nextHostToTry help connect iterating over candidate hosts
3208
3209-func (sess *ClientSession) startConnectionAttempt() {
3210+func (sess *clientSession) startConnectionAttempt() {
3211 if sess.timeSince(sess.lastAttemptTimestamp) > sess.ExpectAllRepairedTime {
3212 sess.tryHost = 0
3213 }
3214@@ -319,7 +367,7 @@
3215 sess.lastAttemptTimestamp = time.Now()
3216 }
3217
3218-func (sess *ClientSession) nextHostToTry() string {
3219+func (sess *clientSession) nextHostToTry() string {
3220 if sess.leftToTry == 0 {
3221 return ""
3222 }
3223@@ -331,7 +379,7 @@
3224
3225 // we reached the Started state, we can retry with the same host if we
3226 // have to retry again
3227-func (sess *ClientSession) started() {
3228+func (sess *clientSession) started() {
3229 sess.tryHost--
3230 if sess.tryHost == -1 {
3231 sess.tryHost = len(sess.deliveryHosts) - 1
3232@@ -341,7 +389,7 @@
3233
3234 // connect to a server using the configuration in the ClientSession
3235 // and set up the connection.
3236-func (sess *ClientSession) connect() error {
3237+func (sess *clientSession) connect() error {
3238 sess.setShouldDelay()
3239 sess.startConnectionAttempt()
3240 var err error
3241@@ -363,49 +411,47 @@
3242 return nil
3243 }
3244
3245-func (sess *ClientSession) stopRedial() {
3246- sess.retrierLock.Lock()
3247- defer sess.retrierLock.Unlock()
3248+func (sess *clientSession) stopRedial() {
3249 if sess.retrier != nil {
3250 sess.retrier.Stop()
3251 sess.retrier = nil
3252 }
3253 }
3254
3255-func (sess *ClientSession) AutoRedial(doneCh chan uint32) {
3256+func (sess *clientSession) autoRedial() {
3257 sess.stopRedial()
3258 if time.Since(sess.lastAutoRedial) < 2*time.Second {
3259 sess.setShouldDelay()
3260 }
3261- time.Sleep(sess.redialDelay(sess))
3262- sess.retrierLock.Lock()
3263- defer sess.retrierLock.Unlock()
3264+ // xxx should we really wait on the caller goroutine?
3265+ delay := sess.redialDelay(sess)
3266+ sess.Log.Debugf("session redial delay: %v, wait", delay)
3267+ time.Sleep(delay)
3268+ sess.Log.Debugf("session redial delay: %v, cont", delay)
3269 if sess.retrier != nil {
3270 panic("session AutoRedial: unexpected non-nil retrier.")
3271 }
3272 sess.retrier = util.NewAutoRedialer(sess)
3273 sess.lastAutoRedial = time.Now()
3274- go func() {
3275- sess.retrierLock.Lock()
3276- retrier := sess.retrier
3277- sess.retrierLock.Unlock()
3278- if retrier == nil {
3279- sess.Log.Debugf("session autoredialer skipping retry: retrier has been set to nil.")
3280- return
3281- }
3282+ go func(retrier util.AutoRedialer) {
3283 sess.Log.Debugf("session autoredialier launching Redial goroutine")
3284- doneCh <- retrier.Redial()
3285- }()
3286-}
3287-
3288-func (sess *ClientSession) Close() {
3289- sess.stopRedial()
3290- sess.doClose()
3291-}
3292-
3293-func (sess *ClientSession) doClose() {
3294+ // if the redialer has been stopped before calling Redial(), it'll return 0.
3295+ sess.doneCh <- retrier.Redial()
3296+ }(sess.retrier)
3297+}
3298+
3299+func (sess *clientSession) doClose(resetCookie bool) {
3300 sess.connLock.Lock()
3301 defer sess.connLock.Unlock()
3302+ if resetCookie {
3303+ sess.cookie = ""
3304+ }
3305+ sess.closeConnection()
3306+ sess.setState(Disconnected)
3307+}
3308+
3309+func (sess *clientSession) closeConnection() {
3310+ // *must be called with connLock held*
3311 if sess.Connection != nil {
3312 sess.Connection.Close()
3313 // we ignore Close errors, on purpose (the thinking being that
3314@@ -413,11 +459,10 @@
3315 // you could do to recover at this stage).
3316 sess.Connection = nil
3317 }
3318- sess.setState(Disconnected)
3319 }
3320
3321 // handle "ping" messages
3322-func (sess *ClientSession) handlePing() error {
3323+func (sess *clientSession) handlePing() error {
3324 err := sess.proto.WriteMessage(protocol.PingPongMsg{Type: "pong"})
3325 if err == nil {
3326 sess.Log.Debugf("ping.")
3327@@ -429,7 +474,7 @@
3328 return err
3329 }
3330
3331-func (sess *ClientSession) decodeBroadcast(bcast *serverMsg) *BroadcastNotification {
3332+func (sess *clientSession) decodeBroadcast(bcast *serverMsg) *BroadcastNotification {
3333 decoded := make([]map[string]interface{}, 0)
3334 for _, p := range bcast.Payloads {
3335 var v map[string]interface{}
3336@@ -447,7 +492,7 @@
3337 }
3338
3339 // handle "broadcast" messages
3340-func (sess *ClientSession) handleBroadcast(bcast *serverMsg) error {
3341+func (sess *clientSession) handleBroadcast(bcast *serverMsg) error {
3342 err := sess.SeenState.SetLevel(bcast.ChanId, bcast.TopLevel)
3343 if err != nil {
3344 sess.setState(Error)
3345@@ -478,7 +523,7 @@
3346 }
3347
3348 // handle "notifications" messages
3349-func (sess *ClientSession) handleNotifications(ucast *serverMsg) error {
3350+func (sess *clientSession) handleNotifications(ucast *serverMsg) error {
3351 notifs, err := sess.SeenState.FilterBySeen(ucast.Notifications)
3352 if err != nil {
3353 sess.setState(Error)
3354@@ -512,7 +557,7 @@
3355 }
3356
3357 // handle "connbroken" messages
3358-func (sess *ClientSession) handleConnBroken(connBroken *serverMsg) error {
3359+func (sess *clientSession) handleConnBroken(connBroken *serverMsg) error {
3360 sess.setState(Error)
3361 reason := connBroken.Reason
3362 err := fmt.Errorf("server broke connection: %s", reason)
3363@@ -525,7 +570,7 @@
3364 }
3365
3366 // handle "setparams" messages
3367-func (sess *ClientSession) handleSetParams(setParams *serverMsg) error {
3368+func (sess *clientSession) handleSetParams(setParams *serverMsg) error {
3369 if setParams.SetCookie != "" {
3370 sess.setCookie(setParams.SetCookie)
3371 }
3372@@ -533,7 +578,7 @@
3373 }
3374
3375 // loop runs the session with the server, emits a stream of events.
3376-func (sess *ClientSession) loop() error {
3377+func (sess *clientSession) loop() error {
3378 var err error
3379 var recv serverMsg
3380 sess.setState(Running)
3381@@ -571,7 +616,7 @@
3382 }
3383
3384 // Call this when you've connected and want to start looping.
3385-func (sess *ClientSession) start() error {
3386+func (sess *clientSession) start() error {
3387 conn := sess.getConnection()
3388 err := conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout))
3389 if err != nil {
3390@@ -634,8 +679,8 @@
3391
3392 // run calls connect, and if it works it calls start, and if it works
3393 // it runs loop in a goroutine, and ships its return value over ErrCh.
3394-func (sess *ClientSession) run(closer func(), authChecker, hostGetter, connecter, starter, looper func() error) error {
3395- closer()
3396+func (sess *clientSession) run(closer func(bool), authChecker, hostGetter, connecter, starter, looper func() error) error {
3397+ closer(false)
3398 if err := authChecker(); err != nil {
3399 return err
3400 }
3401@@ -646,17 +691,14 @@
3402 if err == nil {
3403 err = starter()
3404 if err == nil {
3405- sess.ErrCh = make(chan error, 1)
3406- sess.BroadcastCh = make(chan *BroadcastNotification)
3407- sess.NotificationsCh = make(chan AddressedNotification)
3408- go func() { sess.ErrCh <- looper() }()
3409+ go func() { sess.errCh <- looper() }()
3410 }
3411 }
3412 return err
3413 }
3414
3415 // This Jitter returns a random time.Duration somewhere in [-spread, spread].
3416-func (sess *ClientSession) Jitter(spread time.Duration) time.Duration {
3417+func (sess *clientSession) Jitter(spread time.Duration) time.Duration {
3418 if spread < 0 {
3419 panic("spread must be non-negative")
3420 }
3421@@ -666,7 +708,7 @@
3422
3423 // Dial takes the session from newly created (or newly disconnected)
3424 // to running the main loop.
3425-func (sess *ClientSession) Dial() error {
3426+func (sess *clientSession) Dial() error {
3427 if sess.Protocolator == nil {
3428 // a missing protocolator means you've willfully overridden
3429 // it; returning an error here would prompt AutoRedial to just
3430@@ -676,6 +718,90 @@
3431 return sess.run(sess.doClose, sess.addAuthorization, sess.getHosts, sess.connect, sess.start, sess.loop)
3432 }
3433
3434+func (sess *clientSession) shutdown() {
3435+ sess.Log.Infof("session shutting down.")
3436+ sess.connLock.Lock()
3437+ defer sess.connLock.Unlock()
3438+ sess.stopRedial()
3439+ sess.closeConnection()
3440+}
3441+
3442+func (sess *clientSession) doKeepConnection() {
3443+ for {
3444+ select {
3445+ case cmd := <-sess.cmdCh:
3446+ switch cmd {
3447+ case cmdConnect:
3448+ sess.connHandler(true)
3449+ case cmdDisconnect:
3450+ sess.connHandler(false)
3451+ case cmdResetCookie:
3452+ sess.resetCookie()
3453+ }
3454+ case <-sess.stopCh:
3455+ sess.shutdown()
3456+ return
3457+ case n := <-sess.doneCh:
3458+ // if n == 0, the redialer aborted. If you do
3459+ // anything other than log it, keep that in mind.
3460+ sess.Log.Debugf("connected after %d attempts.", n)
3461+ case err := <-sess.errCh:
3462+ sess.errHandler(err)
3463+ }
3464+ }
3465+}
3466+
3467+func (sess *clientSession) handleConn(hasConn bool) {
3468+ sess.lastConn = hasConn
3469+
3470+ // Note this does not depend on the current state! That's because Dial
3471+ // starts with doClose, which gets you to Disconnected even if you're
3472+ // connected, and you can call Close when Disconnected without it
3473+ // losing its stuff.
3474+ if hasConn {
3475+ sess.autoRedial()
3476+ } else {
3477+ sess.stopRedial()
3478+ sess.doClose(false)
3479+ }
3480+}
3481+
3482+func (sess *clientSession) handleErr(err error) {
3483+ sess.Log.Errorf("session error'ed out with %v", err)
3484+ // State() == Error mostly defends interrupting an ongoing
3485+ // autoRedial if we went quickly already through hasConn =
3486+ // false => hasConn = true
3487+ if sess.State() == Error && sess.lastConn {
3488+ sess.autoRedial()
3489+ }
3490+}
3491+
3492+func (sess *clientSession) KeepConnection() error {
3493+ sess.stateLock.Lock()
3494+ defer sess.stateLock.Unlock()
3495+ if sess.state != Pristine {
3496+ return errors.New("don't call KeepConnection() on a non-pristine session.")
3497+ }
3498+ sess.state = Disconnected
3499+
3500+ go sess.doKeepConnection()
3501+
3502+ return nil
3503+}
3504+
3505+func (sess *clientSession) StopKeepConnection() {
3506+ sess.setState(Shutdown)
3507+ close(sess.stopCh)
3508+}
3509+
3510+func (sess *clientSession) HasConnectivity(hasConn bool) {
3511+ if hasConn {
3512+ sess.cmdCh <- cmdConnect
3513+ } else {
3514+ sess.cmdCh <- cmdDisconnect
3515+ }
3516+}
3517+
3518 func init() {
3519 rand.Seed(time.Now().Unix()) // good enough for us (we're not using it for crypto)
3520 }
3521
3522=== modified file 'client/session/session_test.go'
3523--- client/session/session_test.go 2014-10-23 19:30:24 +0000
3524+++ client/session/session_test.go 2015-03-26 16:42:21 +0000
3525@@ -162,6 +162,7 @@
3526
3527 func (*brokenSeenState) SetLevel(string, int64) error { return errors.New("broken.") }
3528 func (*brokenSeenState) GetAllLevels() (map[string]int64, error) { return nil, errors.New("broken.") }
3529+func (*brokenSeenState) Close() {}
3530 func (*brokenSeenState) FilterBySeen([]protocol.Notification) ([]protocol.Notification, error) {
3531 return nil, errors.New("broken.")
3532 }
3533@@ -189,6 +190,24 @@
3534 cs.lvls = func() (seenstate.SeenState, error) { return seenstate.NewSqliteSeenState(":memory:") }
3535 }
3536
3537+func (cs *clientSessionSuite) TestStateString(c *C) {
3538+ for _, i := range []struct {
3539+ v ClientSessionState
3540+ s string
3541+ }{
3542+ {Error, "Error"},
3543+ {Pristine, "Pristine"},
3544+ {Disconnected, "Disconnected"},
3545+ {Connected, "Connected"},
3546+ {Started, "Started"},
3547+ {Running, "Running"},
3548+ {Shutdown, "Shutdown"},
3549+ {Unknown, fmt.Sprintf("??? (%d)", Unknown)},
3550+ } {
3551+ c.Check(i.v.String(), Equals, i.s)
3552+ }
3553+}
3554+
3555 /****************************************************************
3556 parseServerAddrSpec() tests
3557 ****************************************************************/
3558@@ -211,10 +230,15 @@
3559 NewSession() tests
3560 ****************************************************************/
3561
3562-var dummyConf = ClientSessionConfig{}
3563+func dummyConf() ClientSessionConfig {
3564+ return ClientSessionConfig{
3565+ BroadcastCh: make(chan *BroadcastNotification, 5),
3566+ NotificationsCh: make(chan AddressedNotification, 5),
3567+ }
3568+}
3569
3570 func (cs *clientSessionSuite) TestNewSessionPlainWorks(c *C) {
3571- sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
3572+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
3573 c.Check(sess, NotNil)
3574 c.Check(err, IsNil)
3575 c.Check(sess.fallbackHosts, DeepEquals, []string{"foo:443"})
3576@@ -224,11 +248,13 @@
3577 c.Check(sess.redialDelays, DeepEquals, util.Timeouts())
3578 // but no root CAs set
3579 c.Check(sess.TLS.RootCAs, IsNil)
3580- c.Check(sess.State(), Equals, Disconnected)
3581+ c.Check(sess.State(), Equals, Pristine)
3582+ c.Check(sess.stopCh, NotNil)
3583+ c.Check(sess.cmdCh, NotNil)
3584 }
3585
3586 func (cs *clientSessionSuite) TestNewSessionHostEndpointWorks(c *C) {
3587- sess, err := NewSession("http://foo/hosts", dummyConf, "wah", cs.lvls, cs.log)
3588+ sess, err := NewSession("http://foo/hosts", dummyConf(), "wah", cs.lvls, cs.log)
3589 c.Assert(err, IsNil)
3590 c.Check(sess.getHost, NotNil)
3591 }
3592@@ -254,7 +280,7 @@
3593
3594 func (cs *clientSessionSuite) TestNewSessionBadSeenStateFails(c *C) {
3595 ferr := func() (seenstate.SeenState, error) { return nil, errors.New("Busted.") }
3596- sess, err := NewSession("", dummyConf, "wah", ferr, cs.log)
3597+ sess, err := NewSession("", dummyConf(), "wah", ferr, cs.log)
3598 c.Check(sess, IsNil)
3599 c.Assert(err, NotNil)
3600 }
3601@@ -265,7 +291,7 @@
3602
3603 func (cs *clientSessionSuite) TestGetHostsFallback(c *C) {
3604 fallback := []string{"foo:443", "bar:443"}
3605- sess := &ClientSession{fallbackHosts: fallback}
3606+ sess := &clientSession{fallbackHosts: fallback}
3607 err := sess.getHosts()
3608 c.Assert(err, IsNil)
3609 c.Check(sess.deliveryHosts, DeepEquals, fallback)
3610@@ -283,14 +309,14 @@
3611
3612 func (cs *clientSessionSuite) TestGetHostsRemote(c *C) {
3613 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
3614- sess := &ClientSession{getHost: hostGetter, timeSince: time.Since}
3615+ sess := &clientSession{getHost: hostGetter, timeSince: time.Since}
3616 err := sess.getHosts()
3617 c.Assert(err, IsNil)
3618 c.Check(sess.deliveryHosts, DeepEquals, []string{"foo:443", "bar:443"})
3619 }
3620
3621 func (cs *clientSessionSuite) TestGetHostsRemoteError(c *C) {
3622- sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log)
3623+ sess, err := NewSession("", dummyConf(), "", cs.lvls, cs.log)
3624 c.Assert(err, IsNil)
3625 hostsErr := errors.New("failed")
3626 hostGetter := &testHostGetter{"", nil, hostsErr}
3627@@ -303,7 +329,7 @@
3628
3629 func (cs *clientSessionSuite) TestGetHostsRemoteCaching(c *C) {
3630 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
3631- sess := &ClientSession{
3632+ sess := &clientSession{
3633 getHost: hostGetter,
3634 ClientSessionConfig: ClientSessionConfig{
3635 HostsCachingExpiryTime: 2 * time.Hour,
3636@@ -328,7 +354,7 @@
3637
3638 func (cs *clientSessionSuite) TestGetHostsRemoteCachingReset(c *C) {
3639 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
3640- sess := &ClientSession{
3641+ sess := &clientSession{
3642 getHost: hostGetter,
3643 ClientSessionConfig: ClientSessionConfig{
3644 HostsCachingExpiryTime: 2 * time.Hour,
3645@@ -355,7 +381,7 @@
3646
3647 func (cs *clientSessionSuite) TestAddAuthorizationAddsAuthorization(c *C) {
3648 url := "xyzzy://"
3649- sess := &ClientSession{Log: cs.log}
3650+ sess := &clientSession{Log: cs.log}
3651 sess.AuthGetter = func(url string) string { return url + " auth'ed" }
3652 sess.AuthURL = url
3653 c.Assert(sess.auth, Equals, "")
3654@@ -365,7 +391,7 @@
3655 }
3656
3657 func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnset(c *C) {
3658- sess := &ClientSession{Log: cs.log}
3659+ sess := &clientSession{Log: cs.log}
3660 sess.AuthGetter = nil
3661 c.Assert(sess.auth, Equals, "")
3662 err := sess.addAuthorization()
3663@@ -379,7 +405,7 @@
3664
3665 func (cs *clientSessionSuite) TestStartConnectionAttempt(c *C) {
3666 since := time.Since(time.Time{})
3667- sess := &ClientSession{
3668+ sess := &clientSession{
3669 ClientSessionConfig: ClientSessionConfig{
3670 ExpectAllRepairedTime: 10 * time.Second,
3671 },
3672@@ -403,7 +429,7 @@
3673
3674 func (cs *clientSessionSuite) TestStartConnectionAttemptNoHostsPanic(c *C) {
3675 since := time.Since(time.Time{})
3676- sess := &ClientSession{
3677+ sess := &clientSession{
3678 ClientSessionConfig: ClientSessionConfig{
3679 ExpectAllRepairedTime: 10 * time.Second,
3680 },
3681@@ -415,7 +441,7 @@
3682 }
3683
3684 func (cs *clientSessionSuite) TestNextHostToTry(c *C) {
3685- sess := &ClientSession{
3686+ sess := &clientSession{
3687 deliveryHosts: []string{"foo:443", "bar:443", "baz:443"},
3688 tryHost: 0,
3689 leftToTry: 3,
3690@@ -438,7 +464,7 @@
3691 }
3692
3693 func (cs *clientSessionSuite) TestStarted(c *C) {
3694- sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log)
3695+ sess, err := NewSession("", dummyConf(), "", cs.lvls, cs.log)
3696 c.Assert(err, IsNil)
3697
3698 sess.deliveryHosts = []string{"foo:443", "bar:443", "baz:443"}
3699@@ -457,7 +483,7 @@
3700 ****************************************************************/
3701
3702 func (cs *clientSessionSuite) TestConnectFailsWithNoAddress(c *C) {
3703- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
3704+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
3705 c.Assert(err, IsNil)
3706 sess.deliveryHosts = []string{"nowhere"}
3707 sess.clearShouldDelay()
3708@@ -471,7 +497,7 @@
3709 srv, err := net.Listen("tcp", "localhost:0")
3710 c.Assert(err, IsNil)
3711 defer srv.Close()
3712- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
3713+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
3714 c.Assert(err, IsNil)
3715 sess.deliveryHosts = []string{srv.Addr().String()}
3716 sess.clearShouldDelay()
3717@@ -486,7 +512,7 @@
3718 srv, err := net.Listen("tcp", "localhost:0")
3719 c.Assert(err, IsNil)
3720 defer srv.Close()
3721- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
3722+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
3723 c.Assert(err, IsNil)
3724 sess.deliveryHosts = []string{"nowhere", srv.Addr().String()}
3725 sess.clearShouldDelay()
3726@@ -501,7 +527,7 @@
3727 func (cs *clientSessionSuite) TestConnectConnectFail(c *C) {
3728 srv, err := net.Listen("tcp", "localhost:0")
3729 c.Assert(err, IsNil)
3730- sess, err := NewSession(srv.Addr().String(), dummyConf, "wah", cs.lvls, cs.log)
3731+ sess, err := NewSession(srv.Addr().String(), dummyConf(), "wah", cs.lvls, cs.log)
3732 srv.Close()
3733 c.Assert(err, IsNil)
3734 sess.deliveryHosts = []string{srv.Addr().String()}
3735@@ -512,101 +538,58 @@
3736 c.Check(sess.State(), Equals, Error)
3737 }
3738
3739-/****************************************************************
3740- Close() tests
3741-****************************************************************/
3742-
3743-func (cs *clientSessionSuite) TestClose(c *C) {
3744- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
3745- c.Assert(err, IsNil)
3746- sess.Connection = &testConn{Name: "TestClose"}
3747- sess.Close()
3748- c.Check(sess.Connection, IsNil)
3749- c.Check(sess.State(), Equals, Disconnected)
3750-}
3751-
3752-func (cs *clientSessionSuite) TestCloseTwice(c *C) {
3753- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
3754- c.Assert(err, IsNil)
3755- sess.Connection = &testConn{Name: "TestCloseTwice"}
3756- sess.Close()
3757- c.Check(sess.Connection, IsNil)
3758- sess.Close()
3759- c.Check(sess.Connection, IsNil)
3760- c.Check(sess.State(), Equals, Disconnected)
3761-}
3762-
3763-func (cs *clientSessionSuite) TestCloseFails(c *C) {
3764- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
3765- c.Assert(err, IsNil)
3766- sess.Connection = &testConn{Name: "TestCloseFails", CloseCondition: condition.Work(false)}
3767- sess.Close()
3768- c.Check(sess.Connection, IsNil) // nothing you can do to clean up anyway
3769- c.Check(sess.State(), Equals, Disconnected)
3770-}
3771-
3772-type derp struct{ stopped bool }
3773-
3774-func (*derp) Redial() uint32 { return 0 }
3775-func (d *derp) Stop() { d.stopped = true }
3776-
3777-func (cs *clientSessionSuite) TestCloseStopsRetrier(c *C) {
3778- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
3779- c.Assert(err, IsNil)
3780- ar := new(derp)
3781- sess.retrier = ar
3782- c.Check(ar.stopped, Equals, false)
3783- sess.Close()
3784- c.Check(ar.stopped, Equals, true)
3785- sess.Close() // double close check
3786- c.Check(ar.stopped, Equals, true)
3787-}
3788-
3789-/****************************************************************
3790- AutoRedial() tests
3791-****************************************************************/
3792+type dumbRetrier struct{ stopped bool }
3793+
3794+func (*dumbRetrier) Redial() uint32 { return 0 }
3795+func (d *dumbRetrier) Stop() { d.stopped = true }
3796+
3797+// /****************************************************************
3798+// AutoRedial() tests
3799+// ****************************************************************/
3800
3801 func (cs *clientSessionSuite) TestAutoRedialWorks(c *C) {
3802 // checks that AutoRedial sets up a retrier and tries redialing it
3803- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
3804+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
3805 c.Assert(err, IsNil)
3806- ar := new(derp)
3807+ ar := new(dumbRetrier)
3808 sess.retrier = ar
3809 c.Check(ar.stopped, Equals, false)
3810- sess.AutoRedial(nil)
3811+ sess.autoRedial()
3812+ defer sess.stopRedial()
3813 c.Check(ar.stopped, Equals, true)
3814 }
3815
3816 func (cs *clientSessionSuite) TestAutoRedialStopsRetrier(c *C) {
3817 // checks that AutoRedial stops the previous retrier
3818- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
3819+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
3820 c.Assert(err, IsNil)
3821- ch := make(chan uint32)
3822+ sess.doneCh = make(chan uint32)
3823 c.Check(sess.retrier, IsNil)
3824- sess.AutoRedial(ch)
3825+ sess.autoRedial()
3826 c.Assert(sess.retrier, NotNil)
3827 sess.retrier.Stop()
3828- c.Check(<-ch, Not(Equals), 0)
3829+ c.Check(<-sess.doneCh, Not(Equals), 0)
3830 }
3831
3832 func (cs *clientSessionSuite) TestAutoRedialCallsRedialDelay(c *C) {
3833- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
3834+ // NOTE there are tests that use calling redialDelay as an indication of calling autoRedial!
3835+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
3836 c.Assert(err, IsNil)
3837 flag := false
3838- sess.redialDelay = func(sess *ClientSession) time.Duration { flag = true; return 0 }
3839- sess.AutoRedial(nil)
3840+ sess.redialDelay = func(sess *clientSession) time.Duration { flag = true; return 0 }
3841+ sess.autoRedial()
3842 c.Check(flag, Equals, true)
3843 }
3844
3845 func (cs *clientSessionSuite) TestAutoRedialSetsRedialDelayIfTooQuick(c *C) {
3846- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
3847+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
3848 c.Assert(err, IsNil)
3849- sess.redialDelay = func(sess *ClientSession) time.Duration { return 0 }
3850- sess.AutoRedial(nil)
3851+ sess.redialDelay = func(sess *clientSession) time.Duration { return 0 }
3852+ sess.autoRedial()
3853 c.Check(sess.ShouldDelay(), Equals, false)
3854 sess.stopRedial()
3855 sess.clearShouldDelay()
3856- sess.AutoRedial(nil)
3857+ sess.autoRedial()
3858 c.Check(sess.ShouldDelay(), Equals, true)
3859 }
3860
3861@@ -615,29 +598,23 @@
3862 ****************************************************************/
3863
3864 type msgSuite struct {
3865- sess *ClientSession
3866+ sess *clientSession
3867 upCh chan interface{}
3868 downCh chan interface{}
3869- errCh chan error
3870 }
3871
3872 var _ = Suite(&msgSuite{})
3873
3874 func (s *msgSuite) SetUpTest(c *C) {
3875 var err error
3876- conf := ClientSessionConfig{
3877- ExchangeTimeout: time.Millisecond,
3878- }
3879+ conf := dummyConf()
3880+ conf.ExchangeTimeout = time.Millisecond
3881 s.sess, err = NewSession("", conf, "wah", seenstate.NewSeenState, helpers.NewTestLogger(c, "debug"))
3882 c.Assert(err, IsNil)
3883 s.sess.Connection = &testConn{Name: "TestHandle*"}
3884- s.errCh = make(chan error, 1)
3885 s.upCh = make(chan interface{}, 5)
3886 s.downCh = make(chan interface{}, 5)
3887 s.sess.proto = &testProtocol{up: s.upCh, down: s.downCh}
3888- // make the message channel buffered
3889- s.sess.BroadcastCh = make(chan *BroadcastNotification, 5)
3890- s.sess.NotificationsCh = make(chan AddressedNotification, 5)
3891 }
3892
3893 func (s *msgSuite) TestHandlePingWorks(c *C) {
3894@@ -693,10 +670,10 @@
3895 json.RawMessage(`{"img1/m1":[102,"tubular"]}`),
3896 },
3897 }
3898- go func() { s.errCh <- s.sess.handleBroadcast(msg) }()
3899+ go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
3900 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
3901 s.upCh <- nil // ack ok
3902- c.Check(<-s.errCh, Equals, nil)
3903+ c.Check(<-s.sess.errCh, Equals, nil)
3904 c.Assert(len(s.sess.BroadcastCh), Equals, 1)
3905 c.Check(<-s.sess.BroadcastCh, DeepEquals, &BroadcastNotification{
3906 TopLevel: 2,
3907@@ -725,11 +702,11 @@
3908 TopLevel: 2,
3909 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},
3910 }
3911- go func() { s.errCh <- s.sess.handleBroadcast(msg) }()
3912+ go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
3913 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
3914 failure := errors.New("ACK ACK ACK")
3915 s.upCh <- failure
3916- c.Assert(<-s.errCh, Equals, failure)
3917+ c.Assert(<-s.sess.errCh, Equals, failure)
3918 c.Check(s.sess.State(), Equals, Error)
3919 }
3920
3921@@ -743,10 +720,10 @@
3922 TopLevel: 2,
3923 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},
3924 }
3925- go func() { s.errCh <- s.sess.handleBroadcast(msg) }()
3926+ go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
3927 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
3928 s.upCh <- nil // ack ok
3929- c.Check(<-s.errCh, IsNil)
3930+ c.Check(<-s.sess.errCh, IsNil)
3931 c.Check(len(s.sess.BroadcastCh), Equals, 0)
3932 }
3933
3934@@ -761,10 +738,10 @@
3935 TopLevel: 2,
3936 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},
3937 }
3938- go func() { s.errCh <- s.sess.handleBroadcast(msg) }()
3939+ go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
3940 s.upCh <- nil // ack ok
3941 // start returns with error
3942- c.Check(<-s.errCh, Not(Equals), nil)
3943+ c.Check(<-s.sess.errCh, Not(Equals), nil)
3944 c.Check(s.sess.State(), Equals, Error)
3945 // no message sent out
3946 c.Check(len(s.sess.BroadcastCh), Equals, 0)
3947@@ -777,10 +754,10 @@
3948 s.sess.setShouldDelay()
3949
3950 msg := &serverMsg{Type: "broadcast"}
3951- go func() { s.errCh <- s.sess.handleBroadcast(msg) }()
3952+ go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
3953 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
3954 s.upCh <- nil // ack ok
3955- c.Check(<-s.errCh, IsNil)
3956+ c.Check(<-s.sess.errCh, IsNil)
3957
3958 c.Check(s.sess.ShouldDelay(), Equals, false)
3959 }
3960@@ -789,10 +766,10 @@
3961 s.sess.setShouldDelay()
3962
3963 msg := &serverMsg{Type: "broadcast"}
3964- go func() { s.errCh <- s.sess.handleBroadcast(msg) }()
3965+ go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
3966 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
3967 s.upCh <- errors.New("bcast")
3968- c.Check(<-s.errCh, NotNil)
3969+ c.Check(<-s.sess.errCh, NotNil)
3970
3971 c.Check(s.sess.ShouldDelay(), Equals, true)
3972 }
3973@@ -842,10 +819,10 @@
3974 msg.NotificationsMsg = protocol.NotificationsMsg{
3975 Notifications: []protocol.Notification{n1, n2},
3976 }
3977- go func() { s.errCh <- s.sess.handleNotifications(msg) }()
3978+ go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
3979 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
3980 s.upCh <- nil // ack ok
3981- c.Check(<-s.errCh, Equals, nil)
3982+ c.Check(<-s.sess.errCh, Equals, nil)
3983 c.Check(s.sess.ShouldDelay(), Equals, false)
3984 c.Assert(s.sess.NotificationsCh, HasLen, 2)
3985 app1, err := click.ParseAppId("com.example.app1_app1")
3986@@ -888,10 +865,10 @@
3987 msg.NotificationsMsg = protocol.NotificationsMsg{
3988 Notifications: []protocol.Notification{n1, n2},
3989 }
3990- go func() { s.errCh <- s.sess.handleNotifications(msg) }()
3991+ go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
3992 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
3993 s.upCh <- nil // ack ok
3994- c.Check(<-s.errCh, Equals, nil)
3995+ c.Check(<-s.sess.errCh, Equals, nil)
3996 c.Check(s.sess.ShouldDelay(), Equals, false)
3997 c.Assert(s.sess.NotificationsCh, HasLen, 1)
3998 app2, err := click.ParseAppId("com.example.app2_app2")
3999@@ -923,10 +900,10 @@
4000 msg.NotificationsMsg = protocol.NotificationsMsg{
4001 Notifications: []protocol.Notification{n1, n2},
4002 }
4003- go func() { s.errCh <- s.sess.handleNotifications(msg) }()
4004+ go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
4005 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
4006 s.upCh <- nil // ack ok
4007- c.Check(<-s.errCh, Equals, nil)
4008+ c.Check(<-s.sess.errCh, Equals, nil)
4009 c.Assert(s.sess.NotificationsCh, HasLen, 2)
4010 app1, err := click.ParseAppId("com.example.app1_app1")
4011 c.Assert(err, IsNil)
4012@@ -943,10 +920,10 @@
4013 c.Check(ac.ops, HasLen, 3)
4014
4015 // second time they get ignored
4016- go func() { s.errCh <- s.sess.handleNotifications(msg) }()
4017+ go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
4018 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
4019 s.upCh <- nil // ack ok
4020- c.Check(<-s.errCh, Equals, nil)
4021+ c.Check(<-s.sess.errCh, Equals, nil)
4022 c.Assert(s.sess.NotificationsCh, HasLen, 0)
4023 c.Check(ac.ops, HasLen, 4)
4024 }
4025@@ -963,11 +940,11 @@
4026 msg.NotificationsMsg = protocol.NotificationsMsg{
4027 Notifications: []protocol.Notification{n1},
4028 }
4029- go func() { s.errCh <- s.sess.handleNotifications(msg) }()
4030+ go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
4031 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
4032 failure := errors.New("ACK ACK ACK")
4033 s.upCh <- failure
4034- c.Assert(<-s.errCh, Equals, failure)
4035+ c.Assert(<-s.sess.errCh, Equals, failure)
4036 c.Check(s.sess.State(), Equals, Error)
4037 // didn't get to clear
4038 c.Check(s.sess.ShouldDelay(), Equals, true)
4039@@ -986,10 +963,10 @@
4040 msg.NotificationsMsg = protocol.NotificationsMsg{
4041 Notifications: []protocol.Notification{n1},
4042 }
4043- go func() { s.errCh <- s.sess.handleNotifications(msg) }()
4044+ go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
4045 s.upCh <- nil // ack ok
4046 // start returns with error
4047- c.Check(<-s.errCh, Not(Equals), nil)
4048+ c.Check(<-s.sess.errCh, Not(Equals), nil)
4049 c.Check(s.sess.State(), Equals, Error)
4050 // no message sent out
4051 c.Check(len(s.sess.NotificationsCh), Equals, 0)
4052@@ -1010,8 +987,8 @@
4053 msg.ConnBrokenMsg = protocol.ConnBrokenMsg{
4054 Reason: "REASON",
4055 }
4056- go func() { s.errCh <- s.sess.handleConnBroken(msg) }()
4057- c.Check(<-s.errCh, ErrorMatches, "server broke connection: REASON")
4058+ go func() { s.sess.errCh <- s.sess.handleConnBroken(msg) }()
4059+ c.Check(<-s.sess.errCh, ErrorMatches, "server broke connection: REASON")
4060 c.Check(s.sess.State(), Equals, Error)
4061 }
4062
4063@@ -1022,8 +999,8 @@
4064 Reason: protocol.BrokenHostMismatch,
4065 }
4066 s.sess.deliveryHosts = []string{"foo:443", "bar:443"}
4067- go func() { s.errCh <- s.sess.handleConnBroken(msg) }()
4068- c.Check(<-s.errCh, ErrorMatches, "server broke connection: host-mismatch")
4069+ go func() { s.sess.errCh <- s.sess.handleConnBroken(msg) }()
4070+ c.Check(<-s.sess.errCh, ErrorMatches, "server broke connection: host-mismatch")
4071 c.Check(s.sess.State(), Equals, Error)
4072 // hosts were reset
4073 c.Check(s.sess.deliveryHosts, IsNil)
4074@@ -1041,14 +1018,14 @@
4075 (*msgSuite)(s).SetUpTest(c)
4076 s.sess.Connection.(*testConn).Name = "TestLoop*"
4077 go func() {
4078- s.errCh <- s.sess.loop()
4079+ s.sess.errCh <- s.sess.loop()
4080 }()
4081 }
4082
4083 func (s *loopSuite) TestLoopReadError(c *C) {
4084 c.Check(s.sess.State(), Equals, Running)
4085 s.upCh <- errors.New("Read")
4086- err := <-s.errCh
4087+ err := <-s.sess.errCh
4088 c.Check(err, ErrorMatches, "Read")
4089 c.Check(s.sess.State(), Equals, Error)
4090 }
4091@@ -1060,7 +1037,7 @@
4092 c.Check(takeNext(s.downCh), Equals, protocol.PingPongMsg{Type: "pong"})
4093 failure := errors.New("pong")
4094 s.upCh <- failure
4095- c.Check(<-s.errCh, Equals, failure)
4096+ c.Check(<-s.sess.errCh, Equals, failure)
4097 }
4098
4099 func (s *loopSuite) TestLoopLoopsDaLoop(c *C) {
4100@@ -1073,7 +1050,7 @@
4101 }
4102 failure := errors.New("pong")
4103 s.upCh <- failure
4104- c.Check(<-s.errCh, Equals, failure)
4105+ c.Check(<-s.sess.errCh, Equals, failure)
4106 }
4107
4108 func (s *loopSuite) TestLoopBroadcast(c *C) {
4109@@ -1090,7 +1067,7 @@
4110 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
4111 failure := errors.New("ack")
4112 s.upCh <- failure
4113- c.Check(<-s.errCh, Equals, failure)
4114+ c.Check(<-s.sess.errCh, Equals, failure)
4115 }
4116
4117 func (s *loopSuite) TestLoopNotifications(c *C) {
4118@@ -1110,7 +1087,7 @@
4119 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
4120 failure := errors.New("ack")
4121 s.upCh <- failure
4122- c.Check(<-s.errCh, Equals, failure)
4123+ c.Check(<-s.sess.errCh, Equals, failure)
4124 }
4125
4126 func (s *loopSuite) TestLoopSetParams(c *C) {
4127@@ -1123,7 +1100,7 @@
4128 s.upCh <- setParams
4129 failure := errors.New("fail")
4130 s.upCh <- failure
4131- c.Assert(<-s.errCh, Equals, failure)
4132+ c.Assert(<-s.sess.errCh, Equals, failure)
4133 c.Check(s.sess.getCookie(), Equals, "COOKIE")
4134 }
4135
4136@@ -1135,7 +1112,7 @@
4137 }
4138 c.Check(takeNext(s.downCh), Equals, "deadline 1ms")
4139 s.upCh <- broken
4140- c.Check(<-s.errCh, NotNil)
4141+ c.Check(<-s.sess.errCh, NotNil)
4142 }
4143
4144 func (s *loopSuite) TestLoopConnWarn(c *C) {
4145@@ -1156,7 +1133,7 @@
4146 s.upCh <- warn
4147 s.upCh <- connwarn
4148 s.upCh <- failure
4149- c.Check(<-s.errCh, Equals, failure)
4150+ c.Check(<-s.sess.errCh, Equals, failure)
4151 c.Check(log.Captured(),
4152 Matches, `(?ms).* warning: XXX$.*`)
4153 c.Check(log.Captured(),
4154@@ -1167,7 +1144,7 @@
4155 start() tests
4156 ****************************************************************/
4157 func (cs *clientSessionSuite) TestStartFailsIfSetDeadlineFails(c *C) {
4158- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4159+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4160 c.Assert(err, IsNil)
4161 sess.Connection = &testConn{Name: "TestStartFailsIfSetDeadlineFails",
4162 DeadlineCondition: condition.Work(false)} // setdeadline will fail
4163@@ -1177,7 +1154,7 @@
4164 }
4165
4166 func (cs *clientSessionSuite) TestStartFailsIfWriteFails(c *C) {
4167- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4168+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4169 c.Assert(err, IsNil)
4170 sess.Connection = &testConn{Name: "TestStartFailsIfWriteFails",
4171 WriteCondition: condition.Work(false)} // write will fail
4172@@ -1187,7 +1164,7 @@
4173 }
4174
4175 func (cs *clientSessionSuite) TestStartFailsIfGetLevelsFails(c *C) {
4176- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4177+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4178 c.Assert(err, IsNil)
4179 sess.SeenState = &brokenSeenState{}
4180 sess.Connection = &testConn{Name: "TestStartConnectMessageFails"}
4181@@ -1207,7 +1184,7 @@
4182 }
4183
4184 func (cs *clientSessionSuite) TestStartConnectMessageFails(c *C) {
4185- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4186+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4187 c.Assert(err, IsNil)
4188 sess.Connection = &testConn{Name: "TestStartConnectMessageFails"}
4189 errCh := make(chan error, 1)
4190@@ -1234,7 +1211,7 @@
4191 }
4192
4193 func (cs *clientSessionSuite) TestStartConnackReadError(c *C) {
4194- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4195+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4196 c.Assert(err, IsNil)
4197 sess.Connection = &testConn{Name: "TestStartConnackReadError"}
4198 errCh := make(chan error, 1)
4199@@ -1258,7 +1235,7 @@
4200 }
4201
4202 func (cs *clientSessionSuite) TestStartBadConnack(c *C) {
4203- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4204+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4205 c.Assert(err, IsNil)
4206 sess.Connection = &testConn{Name: "TestStartBadConnack"}
4207 errCh := make(chan error, 1)
4208@@ -1282,7 +1259,7 @@
4209 }
4210
4211 func (cs *clientSessionSuite) TestStartNotConnack(c *C) {
4212- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4213+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4214 c.Assert(err, IsNil)
4215 sess.Connection = &testConn{Name: "TestStartBadConnack"}
4216 errCh := make(chan error, 1)
4217@@ -1349,13 +1326,31 @@
4218 run() tests
4219 ****************************************************************/
4220
4221+func (cs *clientSessionSuite) TestRunCallsCloserWithFalse(c *C) {
4222+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4223+ c.Assert(err, IsNil)
4224+ failure := errors.New("bail")
4225+ has_closed := false
4226+ with_false := false
4227+ err = sess.run(
4228+ func(b bool) { has_closed = true; with_false = !b },
4229+ func() error { return failure },
4230+ nil,
4231+ nil,
4232+ nil,
4233+ nil)
4234+ c.Check(err, Equals, failure)
4235+ c.Check(has_closed, Equals, true)
4236+ c.Check(with_false, Equals, true)
4237+}
4238+
4239 func (cs *clientSessionSuite) TestRunBailsIfAuthCheckFails(c *C) {
4240- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4241+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4242 c.Assert(err, IsNil)
4243 failure := errors.New("TestRunBailsIfAuthCheckFails")
4244 has_closed := false
4245 err = sess.run(
4246- func() { has_closed = true },
4247+ func(bool) { has_closed = true },
4248 func() error { return failure },
4249 nil,
4250 nil,
4251@@ -1366,12 +1361,12 @@
4252 }
4253
4254 func (cs *clientSessionSuite) TestRunBailsIfHostGetterFails(c *C) {
4255- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4256+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4257 c.Assert(err, IsNil)
4258 failure := errors.New("TestRunBailsIfHostGetterFails")
4259 has_closed := false
4260 err = sess.run(
4261- func() { has_closed = true },
4262+ func(bool) { has_closed = true },
4263 func() error { return nil },
4264 func() error { return failure },
4265 nil,
4266@@ -1382,11 +1377,11 @@
4267 }
4268
4269 func (cs *clientSessionSuite) TestRunBailsIfConnectFails(c *C) {
4270- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4271+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4272 c.Assert(err, IsNil)
4273 failure := errors.New("TestRunBailsIfConnectFails")
4274 err = sess.run(
4275- func() {},
4276+ func(bool) {},
4277 func() error { return nil },
4278 func() error { return nil },
4279 func() error { return failure },
4280@@ -1396,11 +1391,11 @@
4281 }
4282
4283 func (cs *clientSessionSuite) TestRunBailsIfStartFails(c *C) {
4284- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4285+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4286 c.Assert(err, IsNil)
4287 failure := errors.New("TestRunBailsIfStartFails")
4288 err = sess.run(
4289- func() {},
4290+ func(bool) {},
4291 func() error { return nil },
4292 func() error { return nil },
4293 func() error { return nil },
4294@@ -1410,16 +1405,12 @@
4295 }
4296
4297 func (cs *clientSessionSuite) TestRunRunsEvenIfLoopFails(c *C) {
4298- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4299+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4300 c.Assert(err, IsNil)
4301- // just to make a point: until here we haven't set ErrCh & BroadcastCh (no
4302- // biggie if this stops being true)
4303- c.Check(sess.ErrCh, IsNil)
4304- c.Check(sess.BroadcastCh, IsNil)
4305 failureCh := make(chan error) // must be unbuffered
4306 notf := &BroadcastNotification{}
4307 err = sess.run(
4308- func() {},
4309+ func(bool) {},
4310 func() error { return nil },
4311 func() error { return nil },
4312 func() error { return nil },
4313@@ -1427,12 +1418,12 @@
4314 func() error { sess.BroadcastCh <- notf; return <-failureCh })
4315 c.Check(err, Equals, nil)
4316 // if run doesn't error it sets up the channels
4317- c.Assert(sess.ErrCh, NotNil)
4318+ c.Assert(sess.errCh, NotNil)
4319 c.Assert(sess.BroadcastCh, NotNil)
4320 c.Check(<-sess.BroadcastCh, Equals, notf)
4321 failure := errors.New("TestRunRunsEvenIfLoopFails")
4322 failureCh <- failure
4323- c.Check(<-sess.ErrCh, Equals, failure)
4324+ c.Check(<-sess.errCh, Equals, failure)
4325 // so now you know it was running in a goroutine :)
4326 }
4327
4328@@ -1441,7 +1432,7 @@
4329 ****************************************************************/
4330
4331 func (cs *clientSessionSuite) TestJitter(c *C) {
4332- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4333+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4334 c.Assert(err, IsNil)
4335 num_tries := 20 // should do the math
4336 spread := time.Second //
4337@@ -1473,20 +1464,23 @@
4338
4339 func (cs *clientSessionSuite) TestDialPanics(c *C) {
4340 // one last unhappy test
4341- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
4342+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4343 c.Assert(err, IsNil)
4344 sess.Protocolator = nil
4345 c.Check(sess.Dial, PanicMatches, ".*protocol constructor.")
4346 }
4347
4348 var (
4349- dialTestTimeout = 100 * time.Millisecond
4350- dialTestConf = ClientSessionConfig{
4351- ExchangeTimeout: dialTestTimeout,
4352- PEM: helpers.TestCertPEMBlock,
4353- }
4354+ dialTestTimeout = 300 * time.Millisecond
4355 )
4356
4357+func dialTestConf() ClientSessionConfig {
4358+ conf := dummyConf()
4359+ conf.ExchangeTimeout = dialTestTimeout
4360+ conf.PEM = helpers.TestCertPEMBlock
4361+ return conf
4362+}
4363+
4364 func (cs *clientSessionSuite) TestDialBadServerName(c *C) {
4365 // a borked server name
4366 lst, err := tls.Listen("tcp", "localhost:0", helpers.TestTLSServerConfig)
4367@@ -1505,7 +1499,7 @@
4368 }))
4369 defer ts.Close()
4370
4371- sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log)
4372+ sess, err := NewSession(ts.URL, dialTestConf(), "wah", cs.lvls, cs.log)
4373 c.Assert(err, IsNil)
4374 tconn := &testConn{}
4375 sess.Connection = tconn
4376@@ -1550,7 +1544,7 @@
4377 }))
4378 defer ts.Close()
4379
4380- sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log)
4381+ sess, err := NewSession(ts.URL, dialTestConf(), "wah", cs.lvls, cs.log)
4382 c.Assert(err, IsNil)
4383 tconn := &testConn{CloseCondition: condition.Fail2Work(10)}
4384 sess.Connection = tconn
4385@@ -1584,7 +1578,7 @@
4386
4387 // 2. "connect" (but on the fake protcol above! woo)
4388
4389- c.Check(takeNext(downCh), Equals, "deadline 100ms")
4390+ c.Check(takeNext(downCh), Equals, fmt.Sprintf("deadline %v", dialTestTimeout))
4391 _, ok := takeNext(downCh).(protocol.ConnectMsg)
4392 c.Check(ok, Equals, true)
4393 upCh <- nil // no error
4394@@ -1597,7 +1591,7 @@
4395 // 3. "loop"
4396
4397 // ping works,
4398- c.Check(takeNext(downCh), Equals, "deadline 110ms")
4399+ c.Check(takeNext(downCh), Equals, fmt.Sprintf("deadline %v", dialTestTimeout+10*time.Millisecond))
4400 upCh <- protocol.PingPongMsg{Type: "ping"}
4401 c.Check(takeNext(downCh), Equals, protocol.PingPongMsg{Type: "pong"})
4402 upCh <- nil
4403@@ -1613,7 +1607,7 @@
4404 TopLevel: 2,
4405 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},
4406 }
4407- c.Check(takeNext(downCh), Equals, "deadline 110ms")
4408+ c.Check(takeNext(downCh), Equals, fmt.Sprintf("deadline %v", dialTestTimeout+10*time.Millisecond))
4409 upCh <- b
4410 c.Check(takeNext(downCh), Equals, protocol.AckMsg{"ack"})
4411 upCh <- nil
4412@@ -1625,21 +1619,21 @@
4413 c.Check(levels, DeepEquals, map[string]int64{"0": 2})
4414
4415 // and ping still work even after that.
4416- c.Check(takeNext(downCh), Equals, "deadline 110ms")
4417+ c.Check(takeNext(downCh), Equals, fmt.Sprintf("deadline %v", dialTestTimeout+10*time.Millisecond))
4418 upCh <- protocol.PingPongMsg{Type: "ping"}
4419 c.Check(takeNext(downCh), Equals, protocol.PingPongMsg{Type: "pong"})
4420 failure := errors.New("pongs")
4421 upCh <- failure
4422- c.Check(<-sess.ErrCh, Equals, failure)
4423+ c.Check(<-sess.errCh, Equals, failure)
4424 }
4425
4426 func (cs *clientSessionSuite) TestDialWorksDirect(c *C) {
4427 // happy path thoughts
4428 lst, err := tls.Listen("tcp", "localhost:0", helpers.TestTLSServerConfig)
4429 c.Assert(err, IsNil)
4430- sess, err := NewSession(lst.Addr().String(), dialTestConf, "wah", cs.lvls, cs.log)
4431+ sess, err := NewSession(lst.Addr().String(), dialTestConf(), "wah", cs.lvls, cs.log)
4432 c.Assert(err, IsNil)
4433- defer sess.Close()
4434+ defer sess.StopKeepConnection()
4435
4436 upCh := make(chan interface{}, 5)
4437 downCh := make(chan interface{}, 5)
4438@@ -1658,7 +1652,7 @@
4439 ****************************************************************/
4440
4441 func (cs *clientSessionSuite) TestShouldDelay(c *C) {
4442- sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
4443+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
4444 c.Assert(err, IsNil)
4445 c.Check(sess.ShouldDelay(), Equals, false)
4446 sess.setShouldDelay()
4447@@ -1668,7 +1662,7 @@
4448 }
4449
4450 func (cs *clientSessionSuite) TestRedialDelay(c *C) {
4451- sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
4452+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
4453 c.Assert(err, IsNil)
4454 sess.redialDelays = []time.Duration{17, 42}
4455 n := 0
4456@@ -1689,15 +1683,207 @@
4457 }
4458
4459 /****************************************************************
4460- ClearCookie() tests
4461+ ResetCookie() tests
4462 ****************************************************************/
4463
4464-func (cs *clientSessionSuite) TestClearCookie(c *C) {
4465- sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
4466+func (cs *clientSessionSuite) TestResetCookie(c *C) {
4467+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
4468 c.Assert(err, IsNil)
4469+ c.Assert(sess.KeepConnection(), IsNil)
4470+ defer sess.StopKeepConnection()
4471 c.Check(sess.getCookie(), Equals, "")
4472 sess.setCookie("COOKIE")
4473 c.Check(sess.getCookie(), Equals, "COOKIE")
4474- sess.ClearCookie()
4475+ sess.ResetCookie()
4476 c.Check(sess.getCookie(), Equals, "")
4477 }
4478+
4479+/****************************************************************
4480+ KeepConnection() (and related) tests
4481+****************************************************************/
4482+
4483+func (cs *clientSessionSuite) TestKeepConnectionDoesNothingIfNotConnected(c *C) {
4484+ // how do you test "does nothing?"
4485+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
4486+ c.Assert(err, IsNil)
4487+ c.Assert(sess, NotNil)
4488+ c.Assert(sess.State(), Equals, Pristine)
4489+ c.Assert(sess.KeepConnection(), IsNil)
4490+ defer sess.StopKeepConnection()
4491+ // stopCh is meant to be used just for closing it, but abusing
4492+ // it for testing seems the right thing to do: this ensures
4493+ // the thing is ticking along before we check the state of
4494+ // stuff.
4495+ sess.stopCh <- struct{}{}
4496+ c.Check(sess.State(), Equals, Disconnected)
4497+}
4498+
4499+func (cs *clientSessionSuite) TestYouCantCallKeepConnectionTwice(c *C) {
4500+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
4501+ c.Assert(err, IsNil)
4502+ c.Assert(sess, NotNil)
4503+ c.Assert(sess.State(), Equals, Pristine)
4504+ c.Assert(sess.KeepConnection(), IsNil)
4505+ defer sess.StopKeepConnection()
4506+ c.Check(sess.KeepConnection(), NotNil)
4507+}
4508+
4509+func (cs *clientSessionSuite) TestStopKeepConnectionShutsdown(c *C) {
4510+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
4511+ c.Assert(err, IsNil)
4512+ c.Assert(sess, NotNil)
4513+ sess.StopKeepConnection()
4514+ c.Check(sess.State(), Equals, Shutdown)
4515+}
4516+
4517+func (cs *clientSessionSuite) TestHasConnectivityTriggersConnectivityHandler(c *C) {
4518+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
4519+ c.Assert(err, IsNil)
4520+ c.Assert(sess, NotNil)
4521+ testCh := make(chan bool)
4522+ sess.connHandler = func(p bool) { testCh <- p }
4523+ go sess.doKeepConnection()
4524+ defer sess.StopKeepConnection()
4525+ sess.HasConnectivity(true)
4526+ c.Check(<-testCh, Equals, true)
4527+ sess.HasConnectivity(false)
4528+ c.Check(<-testCh, Equals, false)
4529+}
4530+
4531+func (cs *clientSessionSuite) TestDoneChIsEmptiedAndLogged(c *C) {
4532+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4533+ c.Assert(err, IsNil)
4534+ sess.doneCh = make(chan uint32) // unbuffered
4535+
4536+ sess.KeepConnection()
4537+ defer sess.StopKeepConnection()
4538+
4539+ sess.doneCh <- 23
4540+ sess.doneCh <- 24 // makes sure the first one has been processed before checking
4541+
4542+ c.Check(cs.log.Captured(),
4543+ Matches, `(?ms).* connected after 23 attempts\.`)
4544+}
4545+
4546+func (cs *clientSessionSuite) TestErrChIsEmptiedAndLoggedAndAutoRedial(c *C) {
4547+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4548+ c.Assert(err, IsNil)
4549+ ch := make(chan struct{}, 1)
4550+ sess.errCh = make(chan error) // unbuffered
4551+ sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
4552+ sess.lastConn = true // -> autoRedial, if the session is in Disconnected
4553+
4554+ sess.KeepConnection()
4555+ defer sess.StopKeepConnection()
4556+
4557+ sess.setState(Error)
4558+ sess.errCh <- errors.New("potato")
4559+ select {
4560+ case <-ch:
4561+ // all ok
4562+ case <-time.After(100 * time.Millisecond):
4563+ c.Fatalf("redialDelay not called (-> autoRedial not called)?")
4564+ }
4565+
4566+ c.Check(cs.log.Captured(),
4567+ Matches, `(?ms).* session error.*potato`)
4568+}
4569+
4570+func (cs *clientSessionSuite) TestErrChIsEmptiedAndLoggedNoAutoRedial(c *C) {
4571+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4572+ c.Assert(err, IsNil)
4573+ ch := make(chan struct{}, 1)
4574+ sess.errCh = make(chan error) // unbuffered
4575+ sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
4576+ sess.connHandler = func(bool) {}
4577+ sess.lastConn = false // so, no autoredial
4578+
4579+ sess.KeepConnection()
4580+ defer sess.StopKeepConnection()
4581+
4582+ sess.errCh <- errors.New("potato")
4583+ c.Assert(sess.State(), Equals, Disconnected)
4584+ select {
4585+ case <-ch:
4586+ c.Fatalf("redialDelay called (-> autoRedial called) when disconnected?")
4587+ case <-time.After(100 * time.Millisecond):
4588+ // all ok
4589+ }
4590+
4591+ c.Check(cs.log.Captured(),
4592+ Matches, `(?ms).* session error.*potato`)
4593+}
4594+
4595+func (cs *clientSessionSuite) TestHandleConnConnFromConnected(c *C) {
4596+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4597+ c.Assert(err, IsNil)
4598+ ch := make(chan struct{}, 1)
4599+ sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
4600+ sess.state = Connected
4601+ sess.lastConn = true
4602+ sess.handleConn(true)
4603+ c.Check(sess.lastConn, Equals, true)
4604+
4605+ select {
4606+ case <-ch:
4607+ // all ok
4608+ case <-time.After(100 * time.Millisecond):
4609+ c.Fatalf("redialDelay not called (-> autoRedial not called)?")
4610+ }
4611+}
4612+
4613+func (cs *clientSessionSuite) TestHandleConnConnFromDisconnected(c *C) {
4614+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4615+ c.Assert(err, IsNil)
4616+ ch := make(chan struct{}, 1)
4617+ sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
4618+ sess.state = Disconnected
4619+ sess.lastConn = false
4620+ sess.handleConn(true)
4621+ c.Check(sess.lastConn, Equals, true)
4622+
4623+ select {
4624+ case <-ch:
4625+ // all ok
4626+ case <-time.After(100 * time.Millisecond):
4627+ c.Fatalf("redialDelay not called (-> autoRedial not called)?")
4628+ }
4629+}
4630+
4631+func (cs *clientSessionSuite) TestHandleConnNotConnFromDisconnected(c *C) {
4632+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4633+ c.Assert(err, IsNil)
4634+ ch := make(chan struct{}, 1)
4635+ sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
4636+ sess.state = Disconnected
4637+ sess.lastConn = false
4638+ sess.handleConn(false)
4639+ c.Check(sess.lastConn, Equals, false)
4640+
4641+ select {
4642+ case <-ch:
4643+ c.Fatalf("redialDelay called (-> autoRedial called)?")
4644+ case <-time.After(100 * time.Millisecond):
4645+ // all ok
4646+ }
4647+ c.Check(cs.log.Captured(), Matches, `(?ms).*-> Disconnected`)
4648+}
4649+
4650+func (cs *clientSessionSuite) TestHandleConnNotConnFromConnected(c *C) {
4651+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
4652+ c.Assert(err, IsNil)
4653+ ch := make(chan struct{}, 1)
4654+ sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
4655+ sess.state = Connected
4656+ sess.lastConn = true
4657+ sess.handleConn(false)
4658+ c.Check(sess.lastConn, Equals, false)
4659+
4660+ select {
4661+ case <-ch:
4662+ c.Fatalf("redialDelay called (-> autoRedial called)?")
4663+ case <-time.After(100 * time.Millisecond):
4664+ // all ok
4665+ }
4666+ c.Check(cs.log.Captured(), Matches, `(?ms).*-> Disconnected`)
4667+}
4668
4669=== added file 'docs/Makefile'
4670--- docs/Makefile 1970-01-01 00:00:00 +0000
4671+++ docs/Makefile 2015-03-26 16:42:21 +0000
4672@@ -0,0 +1,4 @@
4673+all: *txt *svg
4674+ rst2html --link-stylesheet highlevel.txt highlevel.html
4675+ rst2html --link-stylesheet lowlevel.txt lowlevel.html
4676+
4677
4678=== modified file 'docs/_common.txt'
4679--- docs/_common.txt 2014-09-05 14:49:44 +0000
4680+++ docs/_common.txt 2015-03-26 16:42:21 +0000
4681@@ -7,46 +7,22 @@
4682 The helper receives two arguments ``infile`` and ``outfile``. The message is delivered via ``infile`` and the transformed
4683 version is placed in ``outfile``.
4684
4685-This is the simplest possible useful helper, which simply passes the message through unchanged::
4686-
4687- #!/usr/bin/python3
4688-
4689- import sys
4690- f1, f2 = sys.argv[1:3]
4691- open(f2, "w").write(open(f1).read())
4692-
4693-Helpers need to be added to the click package manifest::
4694-
4695- {
4696- "name": "com.ubuntu.developer.ralsina.hello",
4697- "description": "description of hello",
4698- "framework": "ubuntu-sdk-14.10-qml-dev2",
4699- "architecture": "all",
4700- "title": "hello",
4701- "hooks": {
4702- "hello": {
4703- "apparmor": "hello.json",
4704- "desktop": "hello.desktop"
4705- },
4706- "helloHelper": {
4707- "apparmor": "helloHelper-apparmor.json",
4708- "push-helper": "helloHelper.json"
4709- }
4710- },
4711- "version": "0.2",
4712- "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>"
4713- }
4714+This is the simplest possible useful helper, which simply passes the message through unchanged:
4715+
4716+.. include:: example-client/helloHelper
4717+ :literal:
4718+
4719+Helpers need to be added to the click package manifest:
4720+
4721+.. include:: example-client/manifest.json
4722+ :literal:
4723
4724 Here, we created a helloHelper entry in hooks that has an apparmor profile and an additional JSON file for the push-helper hook.
4725
4726-helloHelper-apparmor.json must contain **only** the push-notification-client policy group::
4727+helloHelper-apparmor.json must contain **only** the push-notification-client policy group and the ubuntu-push-helper template:
4728
4729- {
4730- "policy_groups": [
4731- "push-notification-client"
4732- ],
4733- "policy_version": 1.2
4734- }
4735+.. include:: example-client/helloHelper-apparmor.json
4736+ :literal:
4737
4738 And helloHelper.json must have at least a exec key with the path to the helper executable relative to the json, and optionally
4739 an app_id key containing the short id of one of the apps in the package (in the format packagename_appname without a version).
4740@@ -138,15 +114,10 @@
4741 Security
4742 ~~~~~~~~
4743
4744-To use the push API, applications need to request permission in their security profile, using something like this::
4745+To use the push API, applications need to request permission in their security profile, using something like this:
4746
4747- {
4748- "policy_groups": [
4749- "networking",
4750- "push-notification-client"
4751- ],
4752- "policy_version": 1.2
4753- }
4754+.. include:: example-client/hello.json
4755+ :literal:
4756
4757
4758 Ubuntu Push Server API
4759@@ -184,3 +155,46 @@
4760 :clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error.
4761 :replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one.
4762 :data: A JSON object.
4763+
4764+Limitations of the Server API
4765+-----------------------------
4766+
4767+The push notification infrastructure is meant to help ensuring timely
4768+delivery of application notifications if the device is online or
4769+timely informing the device user about application notifications that
4770+were pending when the device comes back online. This in the face of
4771+applications not being allowed to be running all the time, and
4772+avoiding the resource cost of many applications all polling different services
4773+frequently.
4774+
4775+The push notification infrastructure is architected to guarantee at
4776+least best-effort with respect to these goals and beyond it, on the
4777+other end applications should not expect to be able to use and only
4778+rely on the push notification infrastructure to store application
4779+messages if they want ensure all their notification or messages are
4780+delivered, the infrastructure is not intended to be the only long term
4781+"inbox" storage for an application.
4782+
4783+To preserve overall throughput the infrastructure imposes some limits
4784+on applications:
4785+
4786+ * message data payload is limited to 2K
4787+
4788+ * when inserted all messages need to specify an expiration date after
4789+ which they can be dropped and not delivered
4790+
4791+ * an application is limited in the number of messages per token
4792+ (application/user/device combination) that can be undelivered/pending at the
4793+ same time (100 currently)
4794+
4795+replace_tag can be used to implement notifications for which the newest
4796+one replace the previous one if pending.
4797+
4798+clear_pending can be used to be deal with a pending message limit
4799+reached, possibly substituting the current undelivered messages with a
4800+more generic one.
4801+
4802+Applications using the push notification HTTP API should be robust
4803+against receiving 503 errors, retrying after waiting with increasing
4804+back-off. Later rate limits (signaled with the 429 status) may also come
4805+into play.
4806
4807=== modified file 'docs/example-client/components/ChatClient.qml'
4808--- docs/example-client/components/ChatClient.qml 2014-09-05 14:40:39 +0000
4809+++ docs/example-client/components/ChatClient.qml 2015-03-26 16:42:21 +0000
4810@@ -60,8 +60,8 @@
4811 if (options["enabled"]) {
4812 data["data"]["notification"] = {
4813 "card": {
4814- "summary": nick + " says: " + message["message"],
4815- "body": "",
4816+ "summary": nick + " says:",
4817+ "body": message["message"],
4818 "popup": options["popup"],
4819 "persist": options["persist"],
4820 "actions": ["appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version"]
4821
4822=== modified file 'docs/example-client/helloHelper-apparmor.json'
4823--- docs/example-client/helloHelper-apparmor.json 2014-09-05 14:40:39 +0000
4824+++ docs/example-client/helloHelper-apparmor.json 2015-03-26 16:42:21 +0000
4825@@ -1,4 +1,5 @@
4826 {
4827+ "template": "ubuntu-push-helper",
4828 "policy_groups": [
4829 "push-notification-client"
4830 ],
4831
4832=== modified file 'docs/example-client/main.qml'
4833--- docs/example-client/main.qml 2014-09-10 14:38:40 +0000
4834+++ docs/example-client/main.qml 2015-03-26 16:42:21 +0000
4835@@ -26,9 +26,42 @@
4836 property alias nickEnabled: nickEdit.enabled
4837 }
4838
4839+ states: [
4840+ State {
4841+ name: "no-push-token"
4842+ when: (pushClient.token == "")
4843+ PropertyChanges { target: nickEdit; readOnly: true}
4844+ PropertyChanges { target: nickEdit; focus: true}
4845+ PropertyChanges { target: messageEdit; enabled: false}
4846+ PropertyChanges { target: loginButton; enabled: false}
4847+ PropertyChanges { target: loginButton; text: "Login"}
4848+ },
4849+ State {
4850+ name: "push-token-not-registered"
4851+ when: ((pushClient.token != "") && (chatClient.registered == false))
4852+ PropertyChanges { target: nickEdit; readOnly: false}
4853+ PropertyChanges { target: nickEdit; text: ""}
4854+ PropertyChanges { target: nickEdit; focus: true}
4855+ PropertyChanges { target: messageEdit; enabled: false}
4856+ PropertyChanges { target: loginButton; enabled: true}
4857+ PropertyChanges { target: loginButton; text: "Login"}
4858+ },
4859+ State {
4860+ name: "registered"
4861+ when: ((pushClient.token != "") && (chatClient.registered == true))
4862+ PropertyChanges { target: nickEdit; readOnly: true}
4863+ PropertyChanges { target: nickEdit; text: "Your nick is " + chatClient.nick}
4864+ PropertyChanges { target: messageEdit; focus: true}
4865+ PropertyChanges { target: messageEdit; enabled: true}
4866+ PropertyChanges { target: loginButton; enabled: true}
4867+ PropertyChanges { target: loginButton; text: "Logout"}
4868+ }
4869+ ]
4870+
4871+ state: "no-push-token"
4872+
4873 ChatClient {
4874 id: chatClient
4875- onRegisteredChanged: {nickEdit.registered()}
4876 onError: {messageList.handle_error(msg)}
4877 token: pushClient.token
4878 }
4879@@ -38,13 +71,16 @@
4880 Component.onCompleted: {
4881 notificationsChanged.connect(messageList.handle_notifications)
4882 error.connect(messageList.handle_error)
4883+ onTokenChanged: {
4884+ console.log("foooooo")
4885+ }
4886 }
4887 appId: "com.ubuntu.developer.ralsina.hello_hello"
4888+
4889 }
4890
4891 TextField {
4892 id: nickEdit
4893- focus: true
4894 placeholderText: "Your nickname"
4895 inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhPreferLowercase
4896 anchors.left: parent.left
4897@@ -53,31 +89,17 @@
4898 anchors.leftMargin: units.gu(.5)
4899 anchors.rightMargin: units.gu(1)
4900 anchors.topMargin: units.gu(.5)
4901- function registered() {
4902- readOnly = true
4903- text = "Your nick is " + chatClient.nick
4904- messageEdit.focus = true
4905- messageEdit.enabled = true
4906- loginButton.text = "Logout"
4907- }
4908 onAccepted: { loginButton.clicked() }
4909 }
4910
4911 Button {
4912 id: loginButton
4913- text: chatClient.rgistered? "Logout": "Login"
4914 anchors.top: nickEdit.top
4915 anchors.right: parent.right
4916 anchors.rightMargin: units.gu(.5)
4917 onClicked: {
4918 if (chatClient.nick) { // logout
4919 chatClient.nick = ""
4920- text = "Login"
4921- nickEdit.enabled = true
4922- nickEdit.readOnly = false
4923- nickEdit.text = ""
4924- nickEdit.focus = true
4925- messageEdit.enabled = false
4926 } else { // login
4927 chatClient.nick = nickEdit.text
4928 }
4929@@ -94,7 +116,6 @@
4930 anchors.rightMargin: units.gu(.5)
4931 anchors.leftMargin: units.gu(.5)
4932 placeholderText: "Your message"
4933- enabled: false
4934 onAccepted: {
4935 console.log("sending " + text)
4936 var idx = text.indexOf(":")
4937@@ -210,7 +231,7 @@
4938 right: parent.right
4939 bottom: parent.bottom
4940 }
4941- height: item1.height * 7
4942+ height: item1.height * 9
4943 UbuntuShape {
4944 anchors.fill: parent
4945 color: Theme.palette.normal.overlay
4946@@ -268,6 +289,14 @@
4947 value: 42
4948 }
4949 }
4950+ Button {
4951+ text: "Set Counter Via Plugin"
4952+ onClicked: { pushClient.count = counterSlider.value; }
4953+ }
4954+ Button {
4955+ text: "Clear Persistent Notifications"
4956+ onClicked: { pushClient.clearPersistent([]); }
4957+ }
4958 }
4959 }
4960 }
4961
4962=== modified file 'docs/example-client/manifest.json'
4963--- docs/example-client/manifest.json 2014-09-10 14:38:31 +0000
4964+++ docs/example-client/manifest.json 2015-03-26 16:42:21 +0000
4965@@ -1,7 +1,7 @@
4966 {
4967 "architecture": "all",
4968 "description": "Example app for Ubuntu push notifications.",
4969- "framework": "ubuntu-sdk-14.10-dev2",
4970+ "framework": "ubuntu-sdk-14.10",
4971 "hooks": {
4972 "hello": {
4973 "apparmor": "hello.json",
4974@@ -15,5 +15,5 @@
4975 "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>",
4976 "name": "com.ubuntu.developer.ralsina.hello",
4977 "title": "Hello",
4978- "version": "0.4.2"
4979+ "version": "0.4.4"
4980 }
4981
4982=== modified file 'docs/example-server/app.js'
4983--- docs/example-server/app.js 2014-09-05 14:57:17 +0000
4984+++ docs/example-server/app.js 2015-03-26 16:42:21 +0000
4985@@ -182,22 +182,40 @@
4986 */
4987 if (cfg.play_notify_form) {
4988 app.post("/play-notify-form", function(req, resp) {
4989- resp.type('text/plain')
4990- if (!req.body.data||!req.body.nick) {
4991- resp.send(400, "invalid/empty fields\n")
4992- return
4993- }
4994- var data
4995- try {
4996- data = JSON.parse(req.body.data)
4997- } catch(e) {
4998- resp.send(400, "data is not JSON\n")
4999- return
5000+ if (!req.body.message||!req.body.nick) {
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches