34
38
"launchpad.net/ubuntu-push/bus/notifications"
35
39
"launchpad.net/ubuntu-push/bus/systemimage"
36
40
"launchpad.net/ubuntu-push/bus/urldispatcher"
41
"launchpad.net/ubuntu-push/client/service"
37
42
"launchpad.net/ubuntu-push/client/session"
38
"launchpad.net/ubuntu-push/client/session/levelmap"
43
"launchpad.net/ubuntu-push/client/session/seenstate"
39
44
"launchpad.net/ubuntu-push/config"
40
45
"launchpad.net/ubuntu-push/logger"
46
"launchpad.net/ubuntu-push/protocol"
41
47
"launchpad.net/ubuntu-push/util"
42
48
"launchpad.net/ubuntu-push/whoopsie/identifier"
56
62
ExpectAllRepairedTime config.ConfigTimeDuration `json:"expect_all_repaired"` // worth retrying all servers after
57
63
// The PEM-encoded server certificate
58
64
CertPEMFile string `json:"cert_pem_file"`
65
// How to invoke the auth helper
66
AuthHelper []string `json:"auth_helper"`
59
67
// The logging level (one of "debug", "info", "error")
60
68
LogLevel logger.ConfigLogLevel `json:"log_level"`
79
87
actionsCh <-chan notifications.RawActionReply
80
88
session *session.ClientSession
81
89
sessionConnectedCh chan uint32
90
serviceEndpoint bus.Endpoint
91
service *service.Service
84
var ACTION_ID_SNOWFLAKE = "::ubuntu-push-client::"
95
system_update_url = "settings:///system/system-update"
96
ACTION_ID_SNOWFLAKE = "::ubuntu-push-client::"
97
ACTION_ID_BROADCAST = ACTION_ID_SNOWFLAKE + system_update_url
86
100
// Creates a new Ubuntu Push Notifications client-side daemon that will use
87
101
// the given configuration file.
144
158
ExchangeTimeout: client.config.ExchangeTimeout.TimeDuration(),
145
159
HostsCachingExpiryTime: client.config.HostsCachingExpiryTime.TimeDuration(),
146
160
ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(),
163
AuthHelper: client.config.AuthHelper,
158
client.deviceId = client.idder.String()
173
baseId := client.idder.String()
174
b, err := hex.DecodeString(baseId)
176
return fmt.Errorf("whoopsie id should be hex: %v", err)
178
h := sha256.Sum224(b)
179
client.deviceId = base64.StdEncoding.EncodeToString(h[:])
193
214
sess, err := session.NewSession(client.config.Addr,
194
215
client.deriveSessionConfig(info), client.deviceId,
195
client.levelMapFactory, client.log)
216
client.seenStateFactory, client.log)
203
// levelmapFactory returns a levelMap for the session
204
func (client *PushClient) levelMapFactory() (levelmap.LevelMap, error) {
224
// seenStateFactory returns a SeenState for the session
225
func (client *PushClient) seenStateFactory() (seenstate.SeenState, error) {
205
226
if client.leveldbPath == "" {
206
return levelmap.NewLevelMap()
227
return seenstate.NewSeenState()
208
return levelmap.NewSqliteLevelMap(client.leveldbPath)
229
return seenstate.NewSqliteSeenState(client.leveldbPath)
278
// handleNotification deals with receiving a notification
279
func (client *PushClient) handleNotification(msg *session.Notification) error {
280
if !client.filterNotification(msg) {
283
action_id := ACTION_ID_SNOWFLAKE
284
a := []string{action_id, "Go get it!"} // action value not visible on the phone
299
func (client *PushClient) sendNotification(action_id, icon, summary, body string) (uint32, error) {
300
a := []string{action_id, "Switch to app"} // action value not visible on the phone
285
301
h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}}
286
302
nots := notifications.Raw(client.notificationsEndp, client.log)
287
body := "Tap to open the system updater."
288
not_id, err := nots.Notify(
289
"ubuntu-push-client", // app name
291
"update_manager_icon", // icon
292
"There's an updated system image.", // summary
296
int32(10*1000), // timeout (ms)
304
"ubuntu-push-client", // app name
311
int32(10*1000), // timeout (ms)
315
// handleBroadcastNotification deals with receiving a broadcast notification
316
func (client *PushClient) handleBroadcastNotification(msg *session.BroadcastNotification) error {
317
if !client.filterBroadcastNotification(msg) {
320
not_id, err := client.sendNotification(ACTION_ID_BROADCAST,
321
"update_manager_icon", "There's an updated system image.",
322
"Tap to open the system updater.")
299
324
client.log.Errorf("showing notification: %s", err)
331
// handleUnicastNotification deals with receiving a unicast notification
332
func (client *PushClient) handleUnicastNotification(msg *protocol.Notification) error {
333
client.log.Debugf("sending notification %#v for %#v.", msg.MsgId, msg.AppId)
334
return client.service.Inject(msg.AppId, string(msg.Payload))
306
337
// handleClick deals with the user clicking a notification
307
338
func (client *PushClient) handleClick(action_id string) error {
308
if action_id != ACTION_ID_SNOWFLAKE {
339
// “The string is a stark data structure and everywhere it is passed
340
// there is much duplication of process. It is a perfect vehicle for
341
// hiding information.”
343
// From ACM's SIGPLAN publication, (September, 1982), Article
344
// "Epigrams in Programming", by Alan J. Perlis of Yale University.
345
url := strings.TrimPrefix(action_id, ACTION_ID_SNOWFLAKE)
346
if len(url) == len(action_id) || len(url) == 0 {
347
// it didn't start with the prefix
311
350
// it doesn't get much simpler...
312
351
urld := urldispatcher.New(client.urlDispatcherEndp, client.log)
313
return urld.DispatchURL("settings:///system/system-update")
352
return urld.DispatchURL(url)
316
355
// doLoop connects events with their handlers
317
func (client *PushClient) doLoop(connhandler func(bool), clickhandler func(string) error, notifhandler func(*session.Notification) error, errhandler func(error)) {
356
func (client *PushClient) doLoop(connhandler func(bool), clickhandler func(string) error, bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(*protocol.Notification) error, errhandler func(error)) {
320
359
case state := <-client.connCh:
321
360
connhandler(state)
322
361
case action := <-client.actionsCh:
323
362
clickhandler(action.ActionId)
324
case msg := <-client.session.MsgCh:
363
case bcast := <-client.session.BroadcastCh:
365
case ucast := <-client.session.NotificationsCh:
326
367
case err := <-client.session.ErrCh:
328
369
case count := <-client.sessionConnectedCh:
345
386
// Loop calls doLoop with the "real" handlers
346
387
func (client *PushClient) Loop() {
347
client.doLoop(client.handleConnState, client.handleClick,
348
client.handleNotification, client.handleErr)
388
client.doLoop(client.handleConnState,
390
client.handleBroadcastNotification,
391
client.handleUnicastNotification,
395
// these are the currently supported fields of a unicast message
396
type UnicastMessage struct {
397
Icon string `json:"icon"`
398
Body string `json:"body"`
399
Summary string `json:"summary"`
400
URL string `json:"url"`
401
Blob json.RawMessage `json:"blob"`
404
func (client *PushClient) messageHandler(message []byte) error {
405
var umsg = new(UnicastMessage)
406
err := json.Unmarshal(message, &umsg)
408
client.log.Errorf("unable to unmarshal message: %v", err)
412
not_id, err := client.sendNotification(
413
ACTION_ID_SNOWFLAKE+umsg.URL,
414
umsg.Icon, umsg.Summary, umsg.Body)
417
client.log.Errorf("showing notification: %s", err)
420
client.log.Debugf("got notification id %d", not_id)
424
func (client *PushClient) startService() error {
425
if client.serviceEndpoint == nil {
426
client.serviceEndpoint = bus.SessionBus.Endpoint(service.BusAddress, client.log)
429
client.service = service.NewService(client.serviceEndpoint, client.log)
430
client.service.SetMessageHandler(client.messageHandler)
431
return client.service.Start()
351
434
// Start calls doStart with the "real" starters
352
435
func (client *PushClient) Start() error {
353
436
return client.doStart(
354
437
client.configure,
355
439
client.getDeviceId,
356
440
client.takeTheBus,
357
441
client.initSession,