Merge lp:~pedronis/ubuntu-push/automatic-into-krillin-rtm into lp:ubuntu-push/krillin-rtm
- automatic-into-krillin-rtm
- Merge into krillin-rtm
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 |
Related bugs: |
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 handleAccountsC
* click.AppId.
and then fallback to icon+"-symbolic"
Description of the change
Roberto Alsina (ralsina) : | # |
Preview Diff
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", ¬if), 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", ¬if), 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", ¬if), Equals, true) |
1514 | + // ok! |
1515 | + hs.acc.vib = false |
1516 | + c.Check(ec.Present(hs.app, "nid", ¬if), 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", ¬if), 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) { |