~ps-jenkins/ubuntu-push/ubuntu-vivid-proposed

« back to all changes in this revision

Viewing changes to client/client.go

  • Committer: CI bot
  • Date: 2014-06-05 09:42:13 UTC
  • mfrom: (102.1.1 trunk)
  • Revision ID: ps-jenkins@lists.canonical.com-20140605094213-kwkn6q4n4bqhvf0i
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
package client
20
20
 
21
21
import (
 
22
        "crypto/sha256"
 
23
        "encoding/base64"
 
24
        "encoding/hex"
 
25
        "encoding/json"
22
26
        "encoding/pem"
23
27
        "errors"
24
28
        "fmt"
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"
43
49
)
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"`
61
69
}
79
87
        actionsCh          <-chan notifications.RawActionReply
80
88
        session            *session.ClientSession
81
89
        sessionConnectedCh chan uint32
 
90
        serviceEndpoint    bus.Endpoint
 
91
        service            *service.Service
82
92
}
83
93
 
84
 
var ACTION_ID_SNOWFLAKE = "::ubuntu-push-client::"
 
94
var (
 
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
 
98
)
85
99
 
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(),
147
 
                PEM:  client.pem,
148
 
                Info: info,
 
161
                PEM:        client.pem,
 
162
                Info:       info,
 
163
                AuthHelper: client.config.AuthHelper,
149
164
        }
150
165
}
151
166
 
155
170
        if err != nil {
156
171
                return err
157
172
        }
158
 
        client.deviceId = client.idder.String()
 
173
        baseId := client.idder.String()
 
174
        b, err := hex.DecodeString(baseId)
 
175
        if err != nil {
 
176
                return fmt.Errorf("whoopsie id should be hex: %v", err)
 
177
        }
 
178
        h := sha256.Sum224(b)
 
179
        client.deviceId = base64.StdEncoding.EncodeToString(h[:])
159
180
        return nil
160
181
}
161
182
 
192
213
        }
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)
196
217
        if err != nil {
197
218
                return err
198
219
        }
200
221
        return nil
201
222
}
202
223
 
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()
207
228
        } else {
208
 
                return levelmap.NewSqliteLevelMap(client.leveldbPath)
 
229
                return seenstate.NewSqliteSeenState(client.leveldbPath)
209
230
        }
210
231
}
211
232
 
232
253
        }
233
254
}
234
255
 
235
 
// filterNotification finds out if the notification is about an actual
 
256
// filterBroadcastNotification finds out if the notification is about an actual
236
257
// upgrade for the device. It expects msg.Decoded entries to look
237
258
// like:
238
259
//
240
261
// "IMAGE-CHANNEL/DEVICE-MODEL": [BUILD-NUMBER, CHANNEL-ALIAS]
241
262
// ...
242
263
// }
243
 
func (client *PushClient) filterNotification(msg *session.Notification) bool {
 
264
func (client *PushClient) filterBroadcastNotification(msg *session.BroadcastNotification) bool {
244
265
        n := len(msg.Decoded)
245
266
        if n == 0 {
246
267
                return false
275
296
        return false
276
297
}
277
298
 
278
 
// handleNotification deals with receiving a notification
279
 
func (client *PushClient) handleNotification(msg *session.Notification) error {
280
 
        if !client.filterNotification(msg) {
281
 
                return nil
282
 
        }
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
290
 
                uint32(0),                          // id
291
 
                "update_manager_icon",              // icon
292
 
                "There's an updated system image.", // summary
293
 
                body,           // body
294
 
                a,              // actions
295
 
                h,              // hints
296
 
                int32(10*1000), // timeout (ms)
 
303
        return nots.Notify(
 
304
                "ubuntu-push-client", // app name
 
305
                uint32(0),            // id
 
306
                icon,                 // icon
 
307
                summary,              // summary
 
308
                body,                 // body
 
309
                a,                    // actions
 
310
                h,                    // hints
 
311
                int32(10*1000),       // timeout (ms)
297
312
        )
 
313
}
 
314
 
 
315
// handleBroadcastNotification deals with receiving a broadcast notification
 
316
func (client *PushClient) handleBroadcastNotification(msg *session.BroadcastNotification) error {
 
317
        if !client.filterBroadcastNotification(msg) {
 
318
                return nil
 
319
        }
 
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.")
298
323
        if err != nil {
299
324
                client.log.Errorf("showing notification: %s", err)
300
325
                return err
303
328
        return nil
304
329
}
305
330
 
 
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))
 
335
}
 
336
 
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.”
 
342
        //
 
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
309
348
                return nil
310
349
        }
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)
314
353
}
315
354
 
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)) {
318
357
        for {
319
358
                select {
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:
325
 
                        notifhandler(msg)
 
363
                case bcast := <-client.session.BroadcastCh:
 
364
                        bcasthandler(bcast)
 
365
                case ucast := <-client.session.NotificationsCh:
 
366
                        ucasthandler(ucast)
326
367
                case err := <-client.session.ErrCh:
327
368
                        errhandler(err)
328
369
                case count := <-client.sessionConnectedCh:
344
385
 
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,
 
389
                client.handleClick,
 
390
                client.handleBroadcastNotification,
 
391
                client.handleUnicastNotification,
 
392
                client.handleErr)
 
393
}
 
394
 
 
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"`
 
402
}
 
403
 
 
404
func (client *PushClient) messageHandler(message []byte) error {
 
405
        var umsg = new(UnicastMessage)
 
406
        err := json.Unmarshal(message, &umsg)
 
407
        if err != nil {
 
408
                client.log.Errorf("unable to unmarshal message: %v", err)
 
409
                return err
 
410
        }
 
411
 
 
412
        not_id, err := client.sendNotification(
 
413
                ACTION_ID_SNOWFLAKE+umsg.URL,
 
414
                umsg.Icon, umsg.Summary, umsg.Body)
 
415
 
 
416
        if err != nil {
 
417
                client.log.Errorf("showing notification: %s", err)
 
418
                return err
 
419
        }
 
420
        client.log.Debugf("got notification id %d", not_id)
 
421
        return nil
 
422
}
 
423
 
 
424
func (client *PushClient) startService() error {
 
425
        if client.serviceEndpoint == nil {
 
426
                client.serviceEndpoint = bus.SessionBus.Endpoint(service.BusAddress, client.log)
 
427
        }
 
428
 
 
429
        client.service = service.NewService(client.serviceEndpoint, client.log)
 
430
        client.service.SetMessageHandler(client.messageHandler)
 
431
        return client.service.Start()
349
432
}
350
433
 
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,
 
438
                client.startService,
355
439
                client.getDeviceId,
356
440
                client.takeTheBus,
357
441
                client.initSession,