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

Proposed by John Lenton
Status: Merged
Approved by: John Lenton
Approved revision: 120
Merged at revision: 117
Proposed branch: lp:~chipaca/ubuntu-push/the-push-automatic
Merge into: lp:ubuntu-push
Diff against target: 1754 lines (+1411/-133)
15 files modified
click/cblacklist/cblacklist.go (+75/-0)
click/click.go (+0/-44)
click/click_test.go (+0/-76)
client/service/postal.go (+8/-0)
client/service/postal_test.go (+26/-0)
debian/changelog (+18/-1)
debian/push-helper.hook (+1/-0)
debian/ubuntu-push-client.install (+1/-0)
docs/lowlevel.txt (+420/-0)
docs/push.svg (+439/-0)
launch_helper/cual/cual.go (+2/-1)
launch_helper/helper_finder/helper_finder.go (+107/-0)
launch_helper/helper_finder/helper_finder_test.go (+193/-0)
launch_helper/kindpool_test.go (+8/-11)
scripts/click-hook (+113/-0)
To merge this branch: bzr merge lp:~chipaca/ubuntu-push/the-push-automatic
Reviewer Review Type Date Requested Status
Ubuntu Push Hackers Pending
Review via email: mp+229306@code.launchpad.net

Description of the change

gsettings!

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'click/cblacklist'
=== added file 'click/cblacklist/cblacklist.go'
--- click/cblacklist/cblacklist.go 1970-01-01 00:00:00 +0000
+++ click/cblacklist/cblacklist.go 2014-08-04 15:37:24 +0000
@@ -0,0 +1,75 @@
1/*
2 Copyright 2013-2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17// Package cblacklist accesses the g_settings notification blacklist
18
19package cblacklist
20
21/*
22#cgo pkg-config: gio-unix-2.0
23#cgo pkg-config: glib-2.0
24
25#include <stdlib.h>
26#include <gio/gio.h>
27
28#define BLACKLIST_CONFIG_SCHEMA_ID "com.ubuntu.notifications.hub"
29#define BLACKLIST_KEY "blacklist"
30
31int is_blacklisted(const char *pkgname, const char *appname) {
32 static GSettings *pushSettings = NULL;
33 GVariantIter *iter;
34 gchar *pkg;
35 gchar *app;
36 int blacklisted = 0;
37
38 if (!pushSettings) {
39 GSettingsSchemaSource * source = g_settings_schema_source_get_default ();
40 if (!g_settings_schema_source_lookup (g_settings_schema_source_get_default (), BLACKLIST_CONFIG_SCHEMA_ID, TRUE)) {
41 return -1;
42 }
43 pushSettings = g_settings_new(BLACKLIST_CONFIG_SCHEMA_ID);
44 }
45 GVariant *blacklist = g_settings_get_value(pushSettings, BLACKLIST_KEY);
46 g_variant_get (blacklist, "a(ss)", &iter);
47 while (g_variant_iter_loop (iter, "(ss)", &pkg, &app)) {
48 if (0==g_strcmp0(pkg, pkgname) && 0==g_strcmp0(app, appname)) {
49 blacklisted = 1;
50 break;
51 }
52 // No need to free pkg and app, according to GVariant array example
53 }
54 g_variant_iter_free (iter);
55 g_variant_unref (blacklist);
56 return blacklisted;
57}
58
59*/
60import "C"
61
62import (
63 "unsafe"
64
65 "launchpad.net/ubuntu-push/click"
66)
67
68// IsBlacklisted returns true if the application is in the gsettings blacklist
69func IsBlacklisted(app *click.AppId) bool {
70 pkgname := C.CString(app.Package)
71 appname := C.CString(app.Application)
72 defer C.free(unsafe.Pointer(pkgname))
73 defer C.free(unsafe.Pointer(appname))
74 return C.is_blacklisted(pkgname, appname) == 1
75}
076
=== modified file 'click/click.go'
--- click/click.go 2014-07-25 21:50:53 +0000
+++ click/click.go 2014-08-04 15:37:24 +0000
@@ -22,7 +22,6 @@
22 "encoding/json"22 "encoding/json"
23 "errors"23 "errors"
24 "fmt"24 "fmt"
25 "io/ioutil"
26 "path/filepath"25 "path/filepath"
27 "regexp"26 "regexp"
28 "strings"27 "strings"
@@ -43,9 +42,6 @@
43 original string42 original string
44}43}
4544
46var hookPath = filepath.Join(xdg.Data.Home(), "ubuntu-push-client", "helpers")
47var hookExt = ".json"
48
49// from https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId45// from https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId
50// except the version is made optional46// except the version is made optional
51var rxClick = regexp.MustCompile(`^([a-z0-9][a-z0-9+.-]+)_([a-zA-Z0-9+.-]+)(?:_([0-9][a-zA-Z0-9.+:~-]*))?$`)47var rxClick = regexp.MustCompile(`^([a-z0-9][a-z0-9+.-]+)_([a-zA-Z0-9+.-]+)(?:_([0-9][a-zA-Z0-9.+:~-]*))?$`)
@@ -121,46 +117,6 @@
121 }117 }
122}118}
123119
124type hookFile struct {
125 AppId string `json:"app_id"`
126 Exec string `json:"exec"`
127}
128
129// Helper figures out the app id and executable of the untrusted
130// helper for this app.
131func (app *AppId) Helper() (helperAppId string, helperExec string) {
132 if !app.Click {
133 return "", ""
134 }
135 // xxx: should probably have a cache of this
136 matches, err := filepath.Glob(filepath.Join(hookPath, app.Package+"_*"+hookExt))
137 if err != nil {
138 return "", ""
139 }
140 var v hookFile
141 for _, m := range matches {
142 abs, err := filepath.EvalSymlinks(m)
143 if err != nil {
144 continue
145 }
146 data, err := ioutil.ReadFile(abs)
147 if err != nil {
148 continue
149 }
150 err = json.Unmarshal(data, &v)
151 if err != nil {
152 continue
153 }
154 if v.Exec != "" && (v.AppId == "" || v.AppId == app.Base()) {
155 basename := filepath.Base(m)
156 helperAppId = basename[:len(basename)-len(hookExt)]
157 helperExec = filepath.Join(filepath.Dir(abs), v.Exec)
158 return helperAppId, helperExec
159 }
160 }
161 return "", ""
162}
163
164func (app *AppId) Versioned() string {120func (app *AppId) Versioned() string {
165 if app.Click {121 if app.Click {
166 if app.Version == "" {122 if app.Version == "" {
167123
=== modified file 'click/click_test.go'
--- click/click_test.go 2014-07-25 21:50:53 +0000
+++ click/click_test.go 2014-08-04 15:37:24 +0000
@@ -19,8 +19,6 @@
19import (19import (
20 "encoding/json"20 "encoding/json"
21 "fmt"21 "fmt"
22 "os"
23 "path/filepath"
24 "testing"22 "testing"
2523
26 . "launchpad.net/gocheck"24 . "launchpad.net/gocheck"
@@ -183,78 +181,4 @@
183 c.Assert(err, Equals, ErrMissingAppId)181 c.Assert(err, Equals, ErrMissingAppId)
184 c.Check(app, NotNil)182 c.Check(app, NotNil)
185 c.Check(app.Original(), Equals, "_non-existent-app")183 c.Check(app.Original(), Equals, "_non-existent-app")
186
187}
188
189type helperSuite struct {
190 oldHookPath string
191 symlinkPath string
192}
193
194var _ = Suite(&helperSuite{})
195
196func (s *helperSuite) SetUpTest(c *C) {
197 s.oldHookPath = hookPath
198 hookPath = c.MkDir()
199 s.symlinkPath = c.MkDir()
200}
201
202func (s *helperSuite) createHookfile(name string, content string) error {
203 symlink := filepath.Join(hookPath, name) + ".json"
204 filename := filepath.Join(s.symlinkPath, name)
205 f, err := os.Create(filename)
206 if err != nil {
207 return err
208 }
209 _, err = f.WriteString(content)
210 if err != nil {
211 return err
212 }
213 err = os.Symlink(filename, symlink)
214 if err != nil {
215 return err
216 }
217 return nil
218}
219
220func (s *helperSuite) TearDownTest(c *C) {
221 hookPath = s.oldHookPath
222}
223
224func (s *helperSuite) TestHelperBasic(c *C) {
225 c.Assert(s.createHookfile("com.example.test_test-helper_1", `{"exec": "tsthlpr"}`), IsNil)
226 app, err := ParseAppId("com.example.test_test-app_1")
227 c.Assert(err, IsNil)
228 hid, hex := app.Helper()
229 c.Check(hid, Equals, "com.example.test_test-helper_1")
230 c.Check(hex, Equals, filepath.Join(s.symlinkPath, "tsthlpr"))
231}
232
233func (s *helperSuite) TestHelperFindsSpecific(c *C) {
234 // Glob() sorts, so the first one will come first
235 c.Assert(s.createHookfile("com.example.test_aaaa-helper_1", `{"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil)
236 c.Assert(s.createHookfile("com.example.test_test-helper_1", `{"exec": "tsthlpr", "app_id": "com.example.test_test-app"}`), IsNil)
237 app, err := ParseAppId("com.example.test_test-app_1")
238 c.Assert(err, IsNil)
239 hid, hex := app.Helper()
240 c.Check(hid, Equals, "com.example.test_test-helper_1")
241 c.Check(hex, Equals, filepath.Join(s.symlinkPath, "tsthlpr"))
242}
243
244func (s *helperSuite) TestHelperCanFail(c *C) {
245 c.Assert(s.createHookfile("com.example.test_aaaa-helper_1", `{"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil)
246 app, err := ParseAppId("com.example.test_test-app_1")
247 c.Assert(err, IsNil)
248 hid, hex := app.Helper()
249 c.Check(hid, Equals, "")
250 c.Check(hex, Equals, "")
251}
252
253func (s *clickSuite) TestHelperlegacy(c *C) {
254 appname := "ubuntu-system-settings"
255 app, err := ParseAppId("_" + appname)
256 c.Assert(err, IsNil)
257 hid, hex := app.Helper()
258 c.Check(hid, Equals, "")
259 c.Check(hex, Equals, "")
260}184}
261185
=== modified file 'client/service/postal.go'
--- client/service/postal.go 2014-07-29 15:38:04 +0000
+++ client/service/postal.go 2014-08-04 15:37:24 +0000
@@ -30,6 +30,7 @@
30 "launchpad.net/ubuntu-push/bus/urldispatcher"30 "launchpad.net/ubuntu-push/bus/urldispatcher"
31 "launchpad.net/ubuntu-push/bus/windowstack"31 "launchpad.net/ubuntu-push/bus/windowstack"
32 "launchpad.net/ubuntu-push/click"32 "launchpad.net/ubuntu-push/click"
33 "launchpad.net/ubuntu-push/click/cblacklist"
33 "launchpad.net/ubuntu-push/launch_helper"34 "launchpad.net/ubuntu-push/launch_helper"
34 "launchpad.net/ubuntu-push/logger"35 "launchpad.net/ubuntu-push/logger"
35 "launchpad.net/ubuntu-push/messaging"36 "launchpad.net/ubuntu-push/messaging"
@@ -392,6 +393,8 @@
392 return svc.urlDispatcher.TestURL(app, notif.Card.Actions)393 return svc.urlDispatcher.TestURL(app, notif.Card.Actions)
393}394}
394395
396var isBlacklisted = cblacklist.IsBlacklisted
397
395func (svc *PostalService) messageHandler(app *click.AppId, nid string, output *launch_helper.HelperOutput) bool {398func (svc *PostalService) messageHandler(app *click.AppId, nid string, output *launch_helper.HelperOutput) bool {
396 if output == nil || output.Notification == nil {399 if output == nil || output.Notification == nil {
397 svc.Log.Debugf("skipping notification: nil.")400 svc.Log.Debugf("skipping notification: nil.")
@@ -403,6 +406,11 @@
403 return false406 return false
404 }407 }
405 if !svc.windowStack.IsAppFocused(app) {408 if !svc.windowStack.IsAppFocused(app) {
409 if isBlacklisted(app) {
410 svc.Log.Debugf("notification skipped (except emblem counter) because app is blacklisted")
411 return svc.emblemCounter.Present(app, nid, output.Notification)
412 }
413
406 b := false414 b := false
407 for _, p := range svc.Presenters {415 for _, p := range svc.Presenters {
408 // we don't want this to shortcut :)416 // we don't want this to shortcut :)
409417
=== modified file 'client/service/postal_test.go'
--- client/service/postal_test.go 2014-07-29 15:38:04 +0000
+++ client/service/postal_test.go 2014-08-04 15:37:24 +0000
@@ -141,6 +141,8 @@
141 winStackBus bus.Endpoint141 winStackBus bus.Endpoint
142 fakeLauncher *fakeHelperLauncher142 fakeLauncher *fakeHelperLauncher
143 getTempDir func(string) (string, error)143 getTempDir func(string) (string, error)
144 oldIsBlisted func(*click.AppId) bool
145 blacklisted bool
144}146}
145147
146type ualPostalSuite struct {148type ualPostalSuite struct {
@@ -155,6 +157,8 @@
155var _ = Suite(&trivialPostalSuite{})157var _ = Suite(&trivialPostalSuite{})
156158
157func (ps *postalSuite) SetUpTest(c *C) {159func (ps *postalSuite) SetUpTest(c *C) {
160 ps.oldIsBlisted = isBlacklisted
161 isBlacklisted = func(*click.AppId) bool { return ps.blacklisted }
158 ps.log = helpers.NewTestLogger(c, "debug")162 ps.log = helpers.NewTestLogger(c, "debug")
159 ps.bus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))163 ps.bus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
160 ps.notifBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))164 ps.notifBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
@@ -163,6 +167,7 @@
163 ps.urlDispBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))167 ps.urlDispBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
164 ps.winStackBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{})168 ps.winStackBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{})
165 ps.fakeLauncher = &fakeHelperLauncher{ch: make(chan []byte)}169 ps.fakeLauncher = &fakeHelperLauncher{ch: make(chan []byte)}
170 ps.blacklisted = false
166171
167 ps.getTempDir = launch_helper.GetTempDir172 ps.getTempDir = launch_helper.GetTempDir
168 d := c.MkDir()173 d := c.MkDir()
@@ -173,6 +178,7 @@
173}178}
174179
175func (ps *postalSuite) TearDownTest(c *C) {180func (ps *postalSuite) TearDownTest(c *C) {
181 isBlacklisted = ps.oldIsBlisted
176 launch_helper.GetTempDir = ps.getTempDir182 launch_helper.GetTempDir = ps.getTempDir
177}183}
178184
@@ -779,3 +785,23 @@
779 c.Check(err, Equals, s.err, Commentf("iter %d", i))785 c.Check(err, Equals, s.err, Commentf("iter %d", i))
780 }786 }
781}787}
788
789func (ps *postalSuite) TestBlacklisted(c *C) {
790 svc := ps.replaceBuses(NewPostalService(nil, ps.log))
791 svc.Start()
792 ps.blacklisted = false
793
794 emb := &launch_helper.EmblemCounter{Count: 2, Visible: true}
795 card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Persist: true}
796 output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card}}
797 embOut := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{EmblemCounter: emb}}
798 app := clickhelp.MustParseAppId("com.example.app_app_1.0")
799 // sanity check: things are presented as normal if blacklist == false
800 ps.blacklisted = false
801 c.Check(svc.messageHandler(app, "0", output), Equals, true)
802 c.Check(svc.messageHandler(app, "1", embOut), Equals, true)
803 ps.blacklisted = true
804 // and regular notifications (but not emblem counters) are supprsessed if blacklisted.
805 c.Check(svc.messageHandler(app, "2", output), Equals, false)
806 c.Check(svc.messageHandler(app, "3", embOut), Equals, true)
807}
782808
=== modified file 'debian/changelog'
--- debian/changelog 2014-08-01 02:15:29 +0000
+++ debian/changelog 2014-08-04 15:37:24 +0000
@@ -1,3 +1,20 @@
1ubuntu-push (0.60-0ubuntu1) UNRELEASED; urgency=medium
2
3 [ Guillermo Gonzalez ]
4 * Add click hook to collect helpers data on install/update/etc and
5 support to read the helper cached data, when available, and only
6 refresh it when it changes.
7 * Include notification settings cleanup in the click install hook, and
8 rename it to click-hook
9
10 [ John R. Lenton ]
11 * For the gsettings interface: Improve, add tests, make design-compliant.
12
13 [Roberto Alsina]
14 * Query gsettings as to whether a notification should be presented.
15
16 -- John R. Lenton <john.lenton@canonical.com> Sat, 02 Aug 2014 01:42:17 +0200
17
1ubuntu-push (0.50+14.10.20140801-0ubuntu1) utopic; urgency=low18ubuntu-push (0.50+14.10.20140801-0ubuntu1) utopic; urgency=low
219
3 * New rebuild forced20 * New rebuild forced
@@ -7,7 +24,7 @@
7ubuntu-push (0.50+14.10.20140729-0ubuntu1) utopic; urgency=medium24ubuntu-push (0.50+14.10.20140729-0ubuntu1) utopic; urgency=medium
825
9 [ Samuele Pedroni ]26 [ Samuele Pedroni ]
10 * Cleanup and improve pos/Post tests27 * Cleanup and improve post/Post tests
1128
12 [ Guillermo Gonzalez ]29 [ Guillermo Gonzalez ]
13 * Add a loop to cleanup MessagingMenu.notifications map when the30 * Add a loop to cleanup MessagingMenu.notifications map when the
1431
=== modified file 'debian/push-helper.hook'
--- debian/push-helper.hook 2014-07-23 13:56:09 +0000
+++ debian/push-helper.hook 2014-08-04 15:37:24 +0000
@@ -1,3 +1,4 @@
1Pattern: ${home}/.local/share/ubuntu-push-client/helpers/${id}.json1Pattern: ${home}/.local/share/ubuntu-push-client/helpers/${id}.json
2User-Level: yes2User-Level: yes
3Hook-Name: push-helper3Hook-Name: push-helper
4Exec: /usr/lib/ubuntu-push-client/click-hook
45
=== modified file 'debian/ubuntu-push-client.install'
--- debian/ubuntu-push-client.install 2014-07-19 00:18:18 +0000
+++ debian/ubuntu-push-client.install 2014-08-04 15:37:24 +0000
@@ -4,5 +4,6 @@
4debian/exec-tool /usr/lib/${DEB_HOST_MULTIARCH}/ubuntu-app-launch/push-helper4debian/exec-tool /usr/lib/${DEB_HOST_MULTIARCH}/ubuntu-app-launch/push-helper
5debian/push-helper.hook /usr/share/click/hooks5debian/push-helper.hook /usr/share/click/hooks
6signing-helper/signing-helper /usr/lib/ubuntu-push-client6signing-helper/signing-helper /usr/lib/ubuntu-push-client
7scripts/click-hook /usr/lib/ubuntu-push-client
7usr/bin/ubuntu-push => /usr/lib/ubuntu-push-client/ubuntu-push-client8usr/bin/ubuntu-push => /usr/lib/ubuntu-push-client/ubuntu-push-client
8scripts/software-updates-helper.py => /usr/lib/ubuntu-push-client/legacy-helpers/ubuntu-system-settings9scripts/software-updates-helper.py => /usr/lib/ubuntu-push-client/legacy-helpers/ubuntu-system-settings
910
=== added directory 'docs'
=== added file 'docs/lowlevel.txt'
--- docs/lowlevel.txt 1970-01-01 00:00:00 +0000
+++ docs/lowlevel.txt 2014-08-04 15:37:24 +0000
@@ -0,0 +1,420 @@
1Ubuntu Push Client Developer Guide
2==================================
3
4:Version: 0.50+
5
6Introduction
7------------
8
9This document describes how to use the Ubuntu Push Client service from a platform integrator's point of view.
10Application developers are expected to use a much simpler API, in turn based on the lower-level API described here.
11
12The expected audience for this document is, therefore, either platform developers, or application developers who,
13for whatever reason, can't use or prefer not to use the available higher level APIs.
14
15---------
16
17Let's describe the push system by way of an example.
18
19Alice has written a chat application called Chatter. Using it, Bob can send messages to Carol and viceversa. Alice has a
20web application for it, so the way it works now is that Bob connects to the service, posts a message, and when Carol
21connects, she gets it. If Carol leaves the browser window open, it beeps when messages arrive.
22
23Now Alice wants to create an Ubuntu Touch app for Chatter, so she implements the same architecture using a client that
24does the same thing as the web browser. Sadly, since applications on Ubuntu Touch don't run continuously, messages are
25only delivered when Carol opens the app, and the user experience suffers.
26
27Using the Ubuntu Push Server, this problem is alleviated: the Chatter server will deliver the messages to the Ubuntu
28Push Server, which in turn will send it in an efficient manner to the Ubuntu Push Client running in Bob and Carol's
29devices. The user sees a notification (all without starting the app) and then can launch it if he's interested in
30reading messages at that point.
31
32Since the app is not started and messages are delivered oportunistically, this is both battery and bandwidth-efficient.
33
34.. figure:: push.svg
35
36The Ubuntu Push system provides:
37
38* A push server which receives **push messages** from the app servers, queues them and delivers them efficiently
39 to the devices.
40* A push client which receives those messages, queues messages to the app and displays notifications to the user
41
42The full lifecycle of a push message is:
43
44* Created in a application-specific server
45* Sent to the Ubuntu Push server, targeted at a user or user+device pair
46* Delivered to one or more Ubuntu devices
47* Passed through the application helper for processing
48* Notification displayed to the user (via different mechanisms)
49* Application Message queued for the app's use
50
51If the user interacts with the notification, the application is launched and should check its queue for messages
52it has to process.
53
54For the app developer, there are several components needed:
55
56* A server that sends the **push messages** to the Ubuntu Push server
57* Support in the client app for registering with the Ubuntu Push client
58* Support in the client app to react to **notifications** displayed to the user and process **application messages**
59* A helper program with application-specific knowledge that transforms **push messages** as needed.
60
61In the following sections, we'll see how to implement all the client side parts. For the application server, see the
62`Ubuntu Push Server API section <#ubuntu-push-server-api>`__
63
64The PushNotifications Service
65-----------------------------
66
67:Service: com.ubuntu.PushNotifications
68:Object path: /com/ubuntu/PushNotifications/QUOTED_PKGNAME
69
70The PushNotifications service handles registering the device with the Ubuntu Push service to enable delivery of messages to
71it.
72
73Each Ubuntu Touch package has to use a separate object path for security reasons, that's why the object path includes QUOTED_PKGNAME.
74For example, in the case of the music application, the package name is ``com.ubuntu.music`` and QUOTED_PKGNAME is com_2eubuntu_2emusic.
75Everything that is not a letter or digit has to be quoted as _XX where XX are the hex digits of the character. In practice,
76this means replacing "." with "_2e" and "-" with "_2f"
77
78.. note:: For applications that are not installed as part of click packages, the QUOTED_PKGNAME is "_" and the APP_ID when required is
79 _PACKAGENAME.
80
81 For example, for ubuntu-system-settins:
82
83 * QUOTED_PKGNAME is _
84 * APP_ID is _ubuntu-system-settings
85
86
87com.ubuntu.PushNotifications.Register
88~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
89
90``string Register(string APP_ID)``
91
92Example::
93
94 $ gdbus call --session --dest com.ubuntu.PushNotifications --object-path /com/ubuntu/PushNotifications/com_2eubuntu_2emusic \
95 --method com.ubuntu.PushNotifications.Register com.ubuntu.music_music
96
97 ('LeA4tRQG9hhEkuhngdouoA==',)
98
99The Register method takes as argument the APP_ID (in the example, com.ubuntu.music_music) and returns a token identifying the user
100and device. For this to succeed the user **must** have an Ubuntu One account configured in the device.
101
102The APP_ID is as described in the `ApplicationId documentation <https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId>`__
103except that the version is treated as optional. Therefore both ``com.ubuntu.music_music`` and ``com.ubuntu.music_music_1.3.496``
104are valid. Keep in mind that while both versioned and unversioned APP_IDs are valid, they are still different and will affect
105which notifications are delivered to the application. Unversioned IDs mean the token will be the same after updates and the application
106will receive old notifications, while versioned IDs mean the app needs to explicitly ask to get older messages delivered.
107
108Register is idempotent, and calling it multiple times returns the same token.
109
110This token is later used by the application server to indicate the recipient of notifications.
111
112.. FIXME crosslink to server app
113
114.. note:: There is currently no way to send a push message to all of a user's devices. The application server has to send to
115 each registered device individually instead.
116
117com.ubuntu.PushNotifications.Unregister
118~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
119
120``void Unregister(string APP_ID)``
121
122Example::
123
124 $ gdbus call --session --dest com.ubuntu.PushNotifications --object-path /com/ubuntu/PushNotifications/com_2eubuntu_2emusic \
125 --method com.ubuntu.PushNotifications.Unregister com.ubuntu.music_music
126
127The Unregister method invalidates the token obtained via `Register <#com-ubuntu-pushnotifications-register>`_ therefore disabling
128reception of push messages.
129
130The method takes as argument the APP_ID (in the example, com.ubuntu.music_music) and returns nothing.
131
132The APP_ID is as described in the `ApplicationId documentation <https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId>`__
133except that the version is treated as optional. Therefore both ``com.ubuntu.music_music`` and ``com.ubuntu.music_music_1.3.496``
134are valid.
135
136The Postal Service
137------------------
138
139:Service: com.ubuntu.Postal
140:Object path: /com/ubuntu/Postal/QUOTED_PKGNAME
141
142The Postal service delivers the actual messages to the applications. After the application is registered, the push client will begin
143delivering messages to the device, which will then (possibly) cause specific notifications to be presented to the user (message bubbles,
144sounds, haptic feedbak, etc.) Regardless of whether the user acknowledges those notifications or not, the payload of the push message
145is put in the Postal service for the application to pick up.
146
147Because user response to notifications can cause application activation, apps should check the status of the Postal service every time
148the application activates.
149
150com.ubuntu.Postal.Post
151~~~~~~~~~~~~~~~~~~~~~~
152
153``void Post(string APP_ID, string message)``
154
155Example::
156
157 gdbus call --session --dest com.ubuntu.Postal --object-path /com/ubuntu/Postal/com_2eubuntu_2emusic \
158 --method com.ubuntu.Postal.Post com.ubuntu.music_music \
159 '"{\"message\": \"foobar\", \"notification\":{\"card\": {\"summary\": \"yes\", \"body\": \"hello\", \"popup\": true, \"persist\": true}}}"'
160
161
162The arguments for the Post method are APP_ID (in the example, com.ubuntu.music_music) and a JSON string
163`describing a push message. <#push-message-format>`__
164
165Depending on the contents of the push message it may trigger user-facing notifications, and will queue a
166message for the app to get via the `PopAll <#com-ubuntu-postal-popalls>`__ method.
167
168The APP_ID is as described in the `ApplicationId documentation <https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId>`__
169except that the version is treated as optional. Therefore both ``com.ubuntu.music_music`` and ``com.ubuntu.music_music_1.3.496``
170are valid.
171
172.. note:: Post is useful as a unified frontend for notifications in Ubuntu Touch, since it wraps and abstracts several different APIs.
173
174com.ubuntu.Postal.PopAll
175~~~~~~~~~~~~~~~~~~~~~~~~
176
177``array{string} PopAll(string APP_ID)``
178
179Example::
180
181 $ gdbus call --session --dest com.ubuntu.Postal --object-path /com/ubuntu/Postal/com_2eubuntu_2emusic \
182 --method com.ubuntu.Postal.PopAll com.ubuntu.music_music
183
184 (['{"foo": "bar", ....}'],)
185
186The argument for the PopAll method is the APP_ID and it returns a list of strings, each string being a separate postal
187message, the "message" element of a helper's output fed from `Post <#com-ubuntu-postal-post>`__
188or from the Ubuntu Push service,
189
190Post Signal
191~~~~~~~~~~~
192
193``void Post(string APP_ID)``
194
195Every time a notification is posted, the postal service will emit the Post signal. Your app can connect to it to react to
196incoming notifications if it's running when they arrive. Remember that on Ubuntu Touch, the application lifecycle means
197it will often **not** be running when notifications arrive. If the application is in the foreground when a notification
198arrives, the notification **will not** be presented.
199
200The object path is similar to that of the Postal service methods, containing the QUOTED_PKGNAME.
201
202Persistent Notification Management
203~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
204
205Some notifications are persistent, meaning they don't disappear automatically. For those notifications, there is an API that
206allows the app to manage them without having to know the underlying details of the platform.
207
208On each notification there's an optional ``tag`` field, used for this purpose.
209
210``array(string) ListPersistent(string APP_ID)``
211
212Returns a list of the tags of notifications with the "persist" element set to true that are visible to the user right now.
213
214``void ClearPersistent(string APP_ID, [tag1, tag2,....])``
215
216Clears persistent notifications for that app by tag(s). If none given, match all.
217
218``void SetCounter(string APP_ID, int count int, bool visible)``
219
220Set the counter to the given values.
221
222
223Application Helpers
224-------------------
225
226The payload delivered to push-client will be passed onto a helper program that can modify it as needed before passing it onto
227the postal service (see `Helper Output Format <#helper-output-format>`__).
228
229The helper receives two arguments ``infile`` and ``outfile``. The message is delivered via ``infile`` and the transformed
230version is placed in ``outfile``.
231
232This is the simplest possible useful helper, which simply passes the message through unchanged::
233
234 #!/usr/bin/python3
235
236 import sys
237 f1, f2 = sys.argv[1:3]
238 open(f2, "w").write(open(f1).read())
239
240Helpers need to be added to the click package manifest::
241
242 {
243 "name": "com.ubuntu.developer.ralsina.hello",
244 "description": "description of hello",
245 "framework": "ubuntu-sdk-14.10-qml-dev2",
246 "architecture": "all",
247 "title": "hello",
248 "hooks": {
249 "hello": {
250 "apparmor": "hello.json",
251 "desktop": "hello.desktop"
252 },
253 "helloHelper": {
254 "apparmor": "helloHelper-apparmor.json",
255 "push-helper": "helloHelper.json"
256 }
257 },
258 "version": "0.2",
259 "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>"
260 }
261
262Here, we created a helloHelper entry in hooks that has an apparmor profile and an additional JSON file for the push-helper hook.
263
264helloHelper-apparmor.json must contain **only** the push-notification-client policy group::
265
266 {
267 "policy_groups": [
268 "push-notification-client"
269 ],
270 "policy_version": 1.2
271 }
272
273And helloHelper.json must have at least a exec key with the path to the helper executable relative to the json, and optionally
274an app_id key containing the short id of one of the apps in the package (in the format packagename_appname without a version).
275If the app_id is not specified, the helper will be used for all apps in the package::
276
277 {
278 "exec": "helloHelper",
279 "app_id": "com.ubuntu.developer.ralsina.hello_hello"
280 }
281
282.. note:: For deb packages, helpers should be installed into /usr/lib/ubuntu-push-client/legacy-helpers/ as part of the package.
283
284Helper Output Format
285--------------------
286
287Helpers output has two parts, the postal message (in the "message" key) and a notification to be presented to the user (in the "notification" key).
288
289Here's a simple example::
290
291 {
292 "message": "foobar",
293 "notification": {
294 "tag": "foo",
295 "card": {
296 "summary": "yes",
297 "body": "hello",
298 "popup": true,
299 "persist": true
300 }
301 "sound": "buzz.mp3",
302 "vibrate": {
303 "pattern": [200, 100],
304 "duration": 200,
305 "repeat": 2
306 }
307 "emblem-counter": {
308 "count": 12,
309 "visible": true
310 }
311 }
312 }
313
314The notification can contain a **tag** field, which can later be used by the `persistent notification management API. <#persistent-notification-management>`__
315
316:message: (optional) A JSON object that is passed as-is to the application via PopAll.
317:notification: (optional) Describes the user-facing notifications triggered by this push message.
318
319The notification can contain a **card**. A card describes a specific notification to be given to the user,
320and has the following fields:
321
322:summary: (required) a title. The card will not be presented if this is missing.
323:body: longer text, defaults to empty.
324:actions: If empty (the default), a bubble notification is non-clickable.
325 If you add a URL, then bubble notifications are clickable and launch that URL. One use for this is using a URL like
326 ``appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version`` which will switch to the app or launch
327 it if it's not running. See `URLDispatcher <https://wiki.ubuntu.com/URLDispatcher>`__ for more information.
328
329:icon: An icon relating to the event being notified. Defaults to empty (no icon);
330 a secondary icon relating to the application will be shown as well, regardless of this field.
331:timestamp: Seconds since the unix epoch, only used for persist (for now)
332:persist: Whether to show in notification centre; defaults to false
333:popup: Whether to show in a bubble. Users can disable this, and can easily miss them, so don't rely on it exclusively. Defaults to false.
334
335.. note:: Keep in mind that the precise way in which each field is presented to the user depends on factors such as
336 whether it's shown as a bubble or in the notification centre, or even the version of Ubuntu Touch the user
337 has on their device.
338
339The notification can contain a **sound** field. This is the path to a sound file. The user can disable it, so don't rely on it exclusively.
340Defaults to empty (no sound). This is a relative path, and will be looked up in (a) the application's .local/share/<pkgname>, and (b)
341standard xdg dirs.
342
343The notification can contain a **vibrate** field, causing haptic feedback, that has the following content:
344
345:pattern: a list of integers describing a vibration pattern.
346:duration: duration in milliseconds. Is equivalent to setting pattern to [duration], and overrides pattern.
347:repeat: number of times the pattern has to be repeated (defaults to 1, 0 is the same as 1).
348
349The notification can contain a **emblem-counter** field, with the following content:
350
351:count: a number to be displayed over the application's icon in the launcher.
352:visible: set to true to show the counter, or false to hide it.
353
354.. note:: Unlike other notifications, emblem-counter needs to be cleaned by the app itself.
355 Please see `the persistent notification management section. <#persistent-notification-management>`__
356
357.. FIXME crosslink to hello example app on each method
358
359Security
360~~~~~~~~
361
362To use the push API, applications need to request permission in their security profile, using something like this::
363
364 {
365 "policy_groups": [
366 "networking",
367 "push-notification-client"
368 ],
369 "policy_version": 1.2
370 }
371
372
373Ubuntu Push Server API
374----------------------
375
376The Ubuntu Push server is located at https://push.ubuntu.com and has a single endpoint: ``/notify``.
377To notify a user, your application has to do a POST with ``Content-type: application/json``.
378
379Here is an example of the POST body using all the fields::
380
381 {
382 "appid": "com.ubuntu.music_music",
383 "expire_on": "2014-10-08T14:48:00.000Z",
384 "token": "LeA4tRQG9hhEkuhngdouoA==",
385 "clear_pending": true,
386 "replace_tag": "tagname",
387 "data": {
388 "message": "foobar",
389 "notification": {
390 "card": {
391 "summary": "yes",
392 "body": "hello",
393 "popup": true,
394 "persist": true
395 }
396 "sound": "buzz.mp3",
397 "tag": "foo",
398 "vibrate": {
399 "duration": 200,
400 "pattern": (200, 100),
401 "repeat": 2
402 }
403 "emblem-counter": {
404 "count": 12,
405 "visible": true
406 }
407 }
408 }
409 }
410
411
412:appid: ID of the application that will receive the notification, as described in the client side documentation.
413:expire_on: Expiration date/time for this message, in `ISO8601 Extendend format <http://en.wikipedia.org/wiki/ISO_8601>`__
414:token: The token identifying the user+device to which the message is directed, as described in the client side documentation.
415:clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error.
416:replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one.
417:data: A JSON object.
418
419In this example, data is `what a helper would output <#helper-output-format>`__ but that's not necessarily the case.
420The content of the data field will be passed to the helper application which **has** to produce output in that format.
0421
=== added file 'docs/push.svg'
--- docs/push.svg 1970-01-01 00:00:00 +0000
+++ docs/push.svg 2014-08-04 15:37:24 +0000
@@ -0,0 +1,439 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<svg
3 xmlns:dc="http://purl.org/dc/elements/1.1/"
4 xmlns:cc="http://creativecommons.org/ns#"
5 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6 xmlns:svg="http://www.w3.org/2000/svg"
7 xmlns="http://www.w3.org/2000/svg"
8 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10 width="41cm"
11 height="41cm"
12 viewBox="381 56 811 811"
13 id="svg2"
14 version="1.1"
15 inkscape:version="0.48.4 r9939"
16 sodipodi:docname="push.svg">
17 <metadata
18 id="metadata88">
19 <rdf:RDF>
20 <cc:Work
21 rdf:about="">
22 <dc:format>image/svg+xml</dc:format>
23 <dc:type
24 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
25 </cc:Work>
26 </rdf:RDF>
27 </metadata>
28 <defs
29 id="defs86">
30 <marker
31 inkscape:stockid="Arrow1Lstart"
32 orient="auto"
33 refY="0.0"
34 refX="0.0"
35 id="Arrow1Lstart"
36 style="overflow:visible">
37 <path
38 id="path3984"
39 d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
40 style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
41 transform="scale(0.8) translate(12.5,0)" />
42 </marker>
43 <marker
44 inkscape:stockid="Arrow1Lend"
45 orient="auto"
46 refY="0.0"
47 refX="0.0"
48 id="Arrow1Lend"
49 style="overflow:visible;">
50 <path
51 id="path3987"
52 d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
53 style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;"
54 transform="scale(0.8) rotate(180) translate(12.5,0)" />
55 </marker>
56 </defs>
57 <sodipodi:namedview
58 pagecolor="#ffffff"
59 bordercolor="#666666"
60 borderopacity="1"
61 objecttolerance="10"
62 gridtolerance="10"
63 guidetolerance="10"
64 inkscape:pageopacity="0"
65 inkscape:pageshadow="2"
66 inkscape:window-width="1855"
67 inkscape:window-height="1056"
68 id="namedview84"
69 showgrid="false"
70 showguides="true"
71 inkscape:guide-bbox="true"
72 inkscape:zoom="0.62854741"
73 inkscape:cx="311.03462"
74 inkscape:cy="472.71845"
75 inkscape:window-x="65"
76 inkscape:window-y="24"
77 inkscape:window-maximized="1"
78 inkscape:current-layer="svg2">
79 <sodipodi:guide
80 orientation="0,1"
81 position="820.94047,984.81037"
82 id="guide4555" />
83 </sodipodi:namedview>
84 <line
85 style="fill:none;fill-opacity:0;stroke-width:2;stroke-dasharray:4;stroke:#000000;stroke-opacity:0.3037037"
86 x1="786"
87 y1="57"
88 x2="786"
89 y2="865"
90 id="line10" />
91 <line
92 style="fill:none;fill-opacity:0;stroke-width:2;stroke-dasharray:4;stroke:#000000;stroke-opacity:0.3037037"
93 x1="382"
94 y1="461"
95 x2="1190"
96 y2="461"
97 id="line12" />
98 <g
99 id="g6336"
100 transform="translate(695.42763,-47.960526)">
101 <rect
102 transform="matrix(0.55824934,0,0,0.55824934,381,56)"
103 y="124.296"
104 x="-641.16089"
105 height="87.503342"
106 width="240.23645"
107 id="rect6311"
108 style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:0.3037037;stroke-dasharray:none" />
109 <text
110 id="text14"
111 y="154.5843"
112 x="53.172604"
113 style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif"
114 font-size="12.7998">
115 <tspan
116 id="tspan16"
117 y="154.5843"
118 x="53.172604">Server Side</tspan>
119 </text>
120 </g>
121 <g
122 id="g6331"
123 transform="translate(679.44079,315.29605)">
124 <rect
125 y="473.10199"
126 x="39.503288"
127 height="48.848682"
128 width="134.11185"
129 id="rect6311-7"
130 style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.67474806;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:0.3037037;stroke-dasharray:none" />
131 <text
132 id="text18"
133 y="502.29813"
134 x="72.128502"
135 style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif"
136 font-size="12.7998">
137 <tspan
138 id="tspan20"
139 y="502.29813"
140 x="72.128502">Client Side</tspan>
141 </text>
142 </g>
143 <g
144 id="g30"
145 transform="translate(-13.351976,0)">
146 <rect
147 style="fill:#ffffff"
148 x="467.5"
149 y="594"
150 width="238"
151 height="152"
152 id="rect32" />
153 <rect
154 style="fill:none;stroke:#000000;stroke-width:2"
155 x="467.5"
156 y="594"
157 width="238"
158 height="152"
159 id="rect34" />
160 </g>
161 <text
162 font-size="12.7998"
163 style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif"
164 x="535.41882"
165 y="674.77179"
166 id="text36">
167 <tspan
168 x="535.41882"
169 y="674.77179"
170 id="tspan38">Push Client</tspan>
171 </text>
172 <g
173 id="g3102"
174 transform="translate(-26.16447,-47.960526)">
175 <g
176 id="g40">
177 <rect
178 id="rect42"
179 height="84"
180 width="205"
181 y="725"
182 x="909.5"
183 style="fill:#ffffff" />
184 <rect
185 id="rect44"
186 height="84"
187 width="205"
188 y="725"
189 x="909.5"
190 style="fill:none;stroke:#000000;stroke-width:2" />
191 </g>
192 <text
193 id="text46"
194 y="770.53119"
195 x="972.96625"
196 style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif"
197 font-size="12.7998">
198 <tspan
199 id="tspan48"
200 y="770.53119"
201 x="972.96625">Push Helper</tspan>
202 </text>
203 </g>
204 <g
205 id="g3972"
206 transform="translate(-10.46581,-2.6644735)">
207 <g
208 transform="translate(-15.098684,-4.4407892)"
209 id="g3095">
210 <g
211 id="g50">
212 <rect
213 style="fill:#ffffff"
214 x="908.90002"
215 y="534.70001"
216 width="205"
217 height="84"
218 id="rect52" />
219 <rect
220 style="fill:none;stroke:#000000;stroke-width:2"
221 x="908.90002"
222 y="534.70001"
223 width="205"
224 height="84"
225 id="rect54" />
226 </g>
227 <text
228 font-size="12.7998"
229 style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif"
230 x="976.13806"
231 y="580.2312"
232 id="text56">
233 <tspan
234 x="976.13806"
235 y="580.2312"
236 id="tspan58">Application</tspan>
237 </text>
238 </g>
239 </g>
240 <text
241 xml:space="preserve"
242 style="font-size:15.63098145px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
243 x="825.89801"
244 y="214.20395"
245 id="text4424"
246 sodipodi:linespacing="125%"><tspan
247 sodipodi:role="line"
248 id="tspan4426"
249 x="825.89801"
250 y="214.20395">/notify</tspan></text>
251 <g
252 id="g4546"
253 transform="translate(8.2993444,1.4703926)">
254 <g
255 transform="translate(-21.65132,-445.13816)"
256 id="g30-8-0">
257 <rect
258 id="rect32-6-7"
259 height="152"
260 width="238"
261 y="594"
262 x="467.5"
263 style="fill:#ffffff" />
264 <rect
265 id="rect34-62-9"
266 height="152"
267 width="238"
268 y="594"
269 x="467.5"
270 style="fill:none;stroke:#000000;stroke-width:2" />
271 </g>
272 <text
273 font-size="12.7998"
274 style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif"
275 x="528.01489"
276 y="229.63364"
277 id="text80-9">
278 <tspan
279 x="528.01489"
280 y="229.63364"
281 id="tspan82-1">PushServer</tspan>
282 </text>
283 </g>
284 <g
285 id="g4994">
286 <g
287 transform="translate(399.33553,-442.47368)"
288 id="g30-8-0-2">
289 <rect
290 id="rect32-6-7-0"
291 height="152"
292 width="238"
293 y="594"
294 x="467.5"
295 style="fill:#ffffff" />
296 <rect
297 id="rect34-62-9-0"
298 height="152"
299 width="238"
300 y="594"
301 x="467.5"
302 style="fill:none;stroke:#000000;stroke-width:2" />
303 </g>
304 <text
305 font-size="12.7998"
306 style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif"
307 x="950.25482"
308 y="232.29811"
309 id="text80-9-1">
310 <tspan
311 x="950.25482"
312 y="232.29811"
313 id="tspan82-1-7">App Server</tspan>
314 </text>
315 </g>
316 <path
317 style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:none;marker-end:url(#Arrow1Lend)"
318 d="M 870.28411,306.64076 557.36389,305.73534"
319 id="path5001"
320 inkscape:connector-type="polyline"
321 inkscape:connector-curvature="0"
322 inkscape:connection-start="#g4994"
323 inkscape:connection-start-point="d4"
324 inkscape:connection-end="#g4546"
325 inkscape:connection-end-point="d4"
326 transform="matrix(0.55824934,0,0,0.55824934,381,56)" />
327 <path
328 style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow1Lstart);marker-end:url(#Arrow1Lend)"
329 d="m 1083.4505,443.39743 0,401.37697"
330 id="path5003"
331 inkscape:connector-type="polyline"
332 inkscape:connector-curvature="0"
333 inkscape:connection-start="#g4994"
334 inkscape:connection-start-point="d4"
335 inkscape:connection-end="#g3972"
336 inkscape:connection-end-point="d4"
337 transform="matrix(0.55824934,0,0,0.55824934,381,56)" />
338 <path
339 style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow1Lstart);marker-end:none"
340 d="M 899.8408,964.68112 557.36389,1048.0044"
341 id="path5005"
342 inkscape:connector-type="polyline"
343 inkscape:connector-curvature="0"
344 inkscape:connection-start="#g3972"
345 inkscape:connection-start-point="d4"
346 inkscape:connection-end="#g30"
347 inkscape:connection-end-point="d4"
348 transform="matrix(0.55824934,0,0,0.55824934,381,56)" />
349 <path
350 style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:none;marker-end:url(#Arrow1Lend)"
351 d="m 344.19749,441.25844 0,522.46863"
352 id="path5009"
353 inkscape:connector-type="polyline"
354 inkscape:connector-curvature="0"
355 inkscape:connection-start="#g4546"
356 inkscape:connection-start-point="d4"
357 inkscape:connection-end="#g30"
358 inkscape:connection-end-point="d4"
359 transform="matrix(0.55824934,0,0,0.55824934,381,56)" />
360 <text
361 xml:space="preserve"
362 style="font-size:15.63098145px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
363 x="825.62708"
364 y="589.00653"
365 id="text6709"
366 sodipodi:linespacing="125%"><tspan
367 sodipodi:role="line"
368 id="tspan6711"
369 x="825.62708"
370 y="589.00653">PopAll</tspan></text>
371 <text
372 xml:space="preserve"
373 style="font-size:15.63098145px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
374 x="491.13156"
375 y="412.44592"
376 id="text6717"
377 sodipodi:linespacing="125%"><tspan
378 sodipodi:role="line"
379 id="tspan6719"
380 x="491.13156"
381 y="412.44592">Push Notifications</tspan></text>
382 <text
383 xml:space="preserve"
384 style="font-size:15.63098145px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
385 x="1041.7894"
386 y="410.93091"
387 id="text6721"
388 sodipodi:linespacing="125%"><tspan
389 sodipodi:role="line"
390 id="tspan6723"
391 x="1041.7894"
392 y="410.93091">App-specific</tspan></text>
393 <text
394 xml:space="preserve"
395 style="font-size:22.32997322px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
396 x="459.1579"
397 y="104.07236"
398 id="text3045"
399 sodipodi:linespacing="125%"><tspan
400 sodipodi:role="line"
401 id="tspan3047"
402 x="459.1579"
403 y="104.07236">Ubuntu Push System</tspan></text>
404 <text
405 xml:space="preserve"
406 style="font-size:22.32997322px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
407 x="919.22369"
408 y="101.4079"
409 id="text3049"
410 sodipodi:linespacing="125%"><tspan
411 sodipodi:role="line"
412 id="tspan3051"
413 x="919.22369"
414 y="101.4079">Application</tspan></text>
415 <path
416 style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#Arrow1Lend)"
417 d="m 556.83946,1145.6987 c 0,0 254.55518,138.4143 340.46755,62.0478"
418 id="path3051"
419 inkscape:connector-curvature="0"
420 transform="matrix(0.55824934,0,0,0.55824934,381,56)"
421 sodipodi:nodetypes="cc" />
422 <path
423 sodipodi:nodetypes="cc"
424 inkscape:connector-curvature="0"
425 id="path3053"
426 d="m 882.80916,712.85627 c 0,0 -142.10524,-77.26969 -190.06574,-34.63814"
427 style="fill:none;stroke:#000000;stroke-width:1.67474802000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#Arrow1Lend)" />
428 <text
429 xml:space="preserve"
430 style="font-size:15.63098145px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
431 x="811.3623"
432 y="717.78949"
433 id="text4019"
434 sodipodi:linespacing="125%"><tspan
435 sodipodi:role="line"
436 id="tspan4021"
437 x="811.3623"
438 y="717.78949">Call</tspan></text>
439</svg>
0440
=== modified file 'launch_helper/cual/cual.go'
--- launch_helper/cual/cual.go 2014-07-18 20:45:21 +0000
+++ launch_helper/cual/cual.go 2014-08-04 15:37:24 +0000
@@ -31,6 +31,7 @@
31 "unsafe"31 "unsafe"
3232
33 "launchpad.net/ubuntu-push/click"33 "launchpad.net/ubuntu-push/click"
34 "launchpad.net/ubuntu-push/launch_helper/helper_finder"
34 "launchpad.net/ubuntu-push/logger"35 "launchpad.net/ubuntu-push/logger"
35)36)
3637
@@ -77,7 +78,7 @@
77}78}
7879
79func (hs *helperState) HelperInfo(app *click.AppId) (string, string) {80func (hs *helperState) HelperInfo(app *click.AppId) (string, string) {
80 return app.Helper()81 return helper_finder.Helper(app, hs.log)
81}82}
8283
83func (hs *helperState) Launch(appId, exec, f1, f2 string) (string, error) {84func (hs *helperState) Launch(appId, exec, f1, f2 string) (string, error) {
8485
=== added directory 'launch_helper/helper_finder'
=== added file 'launch_helper/helper_finder/helper_finder.go'
--- launch_helper/helper_finder/helper_finder.go 1970-01-01 00:00:00 +0000
+++ launch_helper/helper_finder/helper_finder.go 2014-08-04 15:37:24 +0000
@@ -0,0 +1,107 @@
1package helper_finder
2
3import (
4 "encoding/json"
5 "io/ioutil"
6 "os"
7 "path/filepath"
8 "sync"
9 "time"
10
11 "launchpad.net/go-xdg/v0"
12
13 "launchpad.net/ubuntu-push/click"
14 "launchpad.net/ubuntu-push/logger"
15)
16
17type helperValue struct {
18 HelperId string `json:"helper_id"`
19 Exec string `json:"exec"`
20}
21
22type hookFile struct {
23 AppId string `json:"app_id"`
24 Exec string `json:"exec"`
25}
26
27var mapLock sync.Mutex
28var helpersInfo = make(map[string]helperValue)
29var helpersDataMtime time.Time
30
31var helpersDataPath = filepath.Join(xdg.Data.Home(), "ubuntu-push-client", "helpers_data.json")
32var hookPath = filepath.Join(xdg.Data.Home(), "ubuntu-push-client", "helpers")
33var hookExt = ".json"
34
35// helperFromHookfile figures out the app id and executable of the untrusted
36// helper for this app.
37func helperFromHookFile(app *click.AppId) (helperAppId string, helperExec string) {
38 matches, err := filepath.Glob(filepath.Join(hookPath, app.Package+"_*"+hookExt))
39 if err != nil {
40 return "", ""
41 }
42 var v hookFile
43 for _, m := range matches {
44 abs, err := filepath.EvalSymlinks(m)
45 if err != nil {
46 continue
47 }
48 data, err := ioutil.ReadFile(abs)
49 if err != nil {
50 continue
51 }
52 err = json.Unmarshal(data, &v)
53 if err != nil {
54 continue
55 }
56 if v.Exec != "" && (v.AppId == "" || v.AppId == app.Base()) {
57 basename := filepath.Base(m)
58 helperAppId = basename[:len(basename)-len(hookExt)]
59 helperExec = filepath.Join(filepath.Dir(abs), v.Exec)
60 return helperAppId, helperExec
61 }
62 }
63 return "", ""
64}
65
66// Helper figures out the id and executable of the untrusted
67// helper for this app.
68func Helper(app *click.AppId, log logger.Logger) (helperAppId string, helperExec string) {
69 if !app.Click {
70 return "", ""
71 }
72 fInfo, err := os.Stat(helpersDataPath)
73 if err != nil {
74 // cache file is missing, go via the slow route
75 log.Infof("Cache file not found, falling back to .json file lookup")
76 return helperFromHookFile(app)
77 }
78 // get the lock as the map can be changed while we read
79 mapLock.Lock()
80 defer mapLock.Unlock()
81 if helpersInfo == nil || fInfo.ModTime().After(helpersDataMtime) {
82 data, err := ioutil.ReadFile(helpersDataPath)
83 if err != nil {
84 return "", ""
85 }
86 err = json.Unmarshal(data, &helpersInfo)
87 if err != nil {
88 return "", ""
89 }
90 helpersDataMtime = fInfo.ModTime()
91 }
92 var info helperValue
93 info, ok := helpersInfo[app.Base()]
94 if !ok {
95 // ok, appid wasn't there, try with the package
96 info, ok = helpersInfo[app.Package]
97 if !ok {
98 return "", ""
99 }
100 }
101 if info.Exec != "" {
102 helperAppId = info.HelperId
103 helperExec = info.Exec
104 return helperAppId, helperExec
105 }
106 return "", ""
107}
0108
=== added file 'launch_helper/helper_finder/helper_finder_test.go'
--- launch_helper/helper_finder/helper_finder_test.go 1970-01-01 00:00:00 +0000
+++ launch_helper/helper_finder/helper_finder_test.go 2014-08-04 15:37:24 +0000
@@ -0,0 +1,193 @@
1package helper_finder
2
3import (
4 "os"
5 "path/filepath"
6 "testing"
7 "time"
8
9 . "launchpad.net/gocheck"
10 helpers "launchpad.net/ubuntu-push/testing"
11
12 "launchpad.net/ubuntu-push/click"
13)
14
15type helperSuite struct {
16 oldHookPath string
17 symlinkPath string
18 oldHelpersDataPath string
19 log *helpers.TestLogger
20}
21
22func TestHelperFinder(t *testing.T) { TestingT(t) }
23
24var _ = Suite(&helperSuite{})
25
26func (s *helperSuite) SetUpTest(c *C) {
27 s.oldHookPath = hookPath
28 hookPath = c.MkDir()
29 s.symlinkPath = c.MkDir()
30 s.oldHelpersDataPath = helpersDataPath
31 helpersDataPath = filepath.Join(c.MkDir(), "helpers_data.json")
32 s.log = helpers.NewTestLogger(c, "debug")
33}
34
35func (s *helperSuite) createHookfile(name string, content string) error {
36 symlink := filepath.Join(hookPath, name) + ".json"
37 filename := filepath.Join(s.symlinkPath, name)
38 f, err := os.Create(filename)
39 if err != nil {
40 return err
41 }
42 _, err = f.WriteString(content)
43 if err != nil {
44 return err
45 }
46 err = os.Symlink(filename, symlink)
47 if err != nil {
48 return err
49 }
50 return nil
51}
52
53func (s *helperSuite) createHelpersDatafile(content string) error {
54 f, err := os.Create(helpersDataPath)
55 if err != nil {
56 return err
57 }
58 _, err = f.WriteString(content)
59 if err != nil {
60 return err
61 }
62 return nil
63}
64
65func (s *helperSuite) TearDownTest(c *C) {
66 hookPath = s.oldHookPath
67 os.Remove(helpersDataPath)
68 helpersDataPath = s.oldHelpersDataPath
69 helpersDataMtime = time.Now().Add(-1 * time.Hour)
70 helpersInfo = nil
71}
72
73func (s *helperSuite) TestHelperBasic(c *C) {
74 c.Assert(s.createHelpersDatafile(`{"com.example.test": {"helper_id": "com.example.test_test-helper_1", "exec": "tsthlpr"}}`), IsNil)
75 app, err := click.ParseAppId("com.example.test_test-app_1")
76 c.Assert(err, IsNil)
77 hid, hex := Helper(app, s.log)
78 c.Check(hid, Equals, "com.example.test_test-helper_1")
79 c.Check(hex, Equals, "tsthlpr")
80}
81
82func (s *helperSuite) TestHelperFindsSpecific(c *C) {
83 fileContent := `{"com.example.test_test-other-app": {"exec": "aaaaaaa", "helper_id": "com.example.test_aaaa-helper_1"},
84 "com.example.test_test-app": {"exec": "tsthlpr", "helper_id": "com.example.test_test-helper_1"}}`
85 c.Assert(s.createHelpersDatafile(fileContent), IsNil)
86
87 app, err := click.ParseAppId("com.example.test_test-app_1")
88 c.Assert(err, IsNil)
89 hid, hex := Helper(app, s.log)
90 c.Check(hid, Equals, "com.example.test_test-helper_1")
91 c.Check(hex, Equals, "tsthlpr")
92}
93
94func (s *helperSuite) TestHelperCanFail(c *C) {
95 fileContent := `{"com.example.test_test-other-app": {"exec": "aaaaaaa", "helper_id": "com.example.test_aaaa-helper_1"}}`
96 c.Assert(s.createHelpersDatafile(fileContent), IsNil)
97 app, err := click.ParseAppId("com.example.test_test-app_1")
98 c.Assert(err, IsNil)
99 hid, hex := Helper(app, s.log)
100 c.Check(hid, Equals, "")
101 c.Check(hex, Equals, "")
102}
103
104func (s *helperSuite) TestHelperFailInvalidJson(c *C) {
105 fileContent := `{invalid json"com.example.test_test-other-app": {"exec": "aaaaaaa", "helper_id": "com.example.test_aaaa-helper_1"}}`
106 c.Assert(s.createHelpersDatafile(fileContent), IsNil)
107 app, err := click.ParseAppId("com.example.test_test-app_1")
108 c.Assert(err, IsNil)
109 hid, hex := Helper(app, s.log)
110 c.Check(hid, Equals, "")
111 c.Check(hex, Equals, "")
112}
113
114func (s *helperSuite) TestHelperFailMissingExec(c *C) {
115 fileContent := `{"com.example.test_test-app": {"helper_id": "com.example.test_aaaa-helper_1"}}`
116 c.Assert(s.createHelpersDatafile(fileContent), IsNil)
117 app, err := click.ParseAppId("com.example.test_test-app_1")
118 c.Assert(err, IsNil)
119 hid, hex := Helper(app, s.log)
120 c.Check(hid, Equals, "")
121 c.Check(hex, Equals, "")
122}
123
124func (s *helperSuite) TestHelperlegacy(c *C) {
125 appname := "ubuntu-system-settings"
126 app, err := click.ParseAppId("_" + appname)
127 c.Assert(err, IsNil)
128 hid, hex := Helper(app, s.log)
129 c.Check(hid, Equals, "")
130 c.Check(hex, Equals, "")
131}
132
133// Missing Cache file test
134
135func (s *helperSuite) TestHelperMissingCacheFile(c *C) {
136 c.Assert(s.createHookfile("com.example.test_test-helper_1", `{"exec": "tsthlpr"}`), IsNil)
137 app, err := click.ParseAppId("com.example.test_test-app_1")
138 c.Assert(err, IsNil)
139 hid, hex := Helper(app, s.log)
140 c.Check(hid, Equals, "com.example.test_test-helper_1")
141 c.Check(hex, Equals, filepath.Join(s.symlinkPath, "tsthlpr"))
142 c.Check(s.log.Captured(), Matches, ".*Cache file not found, falling back to .json file lookup\n")
143}
144
145func (s *helperSuite) TestHelperFromHookBasic(c *C) {
146 c.Assert(s.createHookfile("com.example.test_test-helper_1", `{"exec": "tsthlpr"}`), IsNil)
147 app, err := click.ParseAppId("com.example.test_test-app_1")
148 c.Assert(err, IsNil)
149 hid, hex := Helper(app, s.log)
150 c.Check(hid, Equals, "com.example.test_test-helper_1")
151 c.Check(hex, Equals, filepath.Join(s.symlinkPath, "tsthlpr"))
152}
153
154func (s *helperSuite) TestHelperFromHookFindsSpecific(c *C) {
155 // Glob() sorts, so the first one will come first
156 c.Assert(s.createHookfile("com.example.test_aaaa-helper_1", `{"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil)
157 c.Assert(s.createHookfile("com.example.test_test-helper_1", `{"exec": "tsthlpr", "app_id": "com.example.test_test-app"}`), IsNil)
158 app, err := click.ParseAppId("com.example.test_test-app_1")
159 c.Assert(err, IsNil)
160 hid, hex := Helper(app, s.log)
161 c.Check(hid, Equals, "com.example.test_test-helper_1")
162 c.Check(hex, Equals, filepath.Join(s.symlinkPath, "tsthlpr"))
163}
164
165func (s *helperSuite) TestHelperFromHookCanFail(c *C) {
166 c.Assert(s.createHookfile("com.example.test_aaaa-helper_1", `{"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil)
167 app, err := click.ParseAppId("com.example.test_test-app_1")
168 c.Assert(err, IsNil)
169 hid, hex := Helper(app, s.log)
170 c.Check(hid, Equals, "")
171 c.Check(hex, Equals, "")
172}
173
174func (s *helperSuite) TestHelperFromHookInvalidJson(c *C) {
175 c.Assert(s.createHookfile("com.example.test_aaaa-helper_1", `invalid json {"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil)
176 app, err := click.ParseAppId("com.example.test_test-app_1")
177 c.Assert(err, IsNil)
178 hid, hex := Helper(app, s.log)
179 c.Check(hid, Equals, "")
180 c.Check(hex, Equals, "")
181}
182
183func (s *helperSuite) TestHelperFromHooFailBrokenSymlink(c *C) {
184 name := "com.example.test_aaaa-helper_1"
185 c.Assert(s.createHookfile(name, `{"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil)
186 filename := filepath.Join(s.symlinkPath, name)
187 os.Remove(filename)
188 app, err := click.ParseAppId("com.example.test_test-app_1")
189 c.Assert(err, IsNil)
190 hid, hex := Helper(app, s.log)
191 c.Check(hid, Equals, "")
192 c.Check(hex, Equals, "")
193}
0194
=== modified file 'launch_helper/kindpool_test.go'
--- launch_helper/kindpool_test.go 2014-07-29 15:24:01 +0000
+++ launch_helper/kindpool_test.go 2014-08-04 15:37:24 +0000
@@ -542,31 +542,28 @@
542}542}
543543
544func (s *poolSuite) TestBigBacklogShrinks(c *C) {544func (s *poolSuite) TestBigBacklogShrinks(c *C) {
545 oldBufSz := InputBufferSize
546 InputBufferSize = 0
547 defer func() { InputBufferSize = oldBufSz }()
545 s.pool.(*kindHelperPool).maxNum = 1548 s.pool.(*kindHelperPool).maxNum = 1
546 ch := s.pool.Start()549 ch := s.pool.Start()
547 defer s.pool.Stop()550 defer s.pool.Stop()
548 numBad := 10
549551
550 app := clickhelp.MustParseAppId("com.example.test_test-app")552 app := clickhelp.MustParseAppId("com.example.test_test-app")
551 s.pool.Run("fake", &HelperInput{App: app, NotificationId: "0", Payload: []byte(`""`)})553 s.pool.Run("fake", &HelperInput{App: app, NotificationId: "0", Payload: []byte(`""`)})
552
553 for i := 0; i < numBad; i++ {
554 s.pool.Run("NOT-THERE", &HelperInput{App: app})
555 }
556 s.pool.Run("fake", &HelperInput{App: app, NotificationId: "1", Payload: []byte(`""`)})554 s.pool.Run("fake", &HelperInput{App: app, NotificationId: "1", Payload: []byte(`""`)})
557 s.pool.Run("fake", &HelperInput{App: app, NotificationId: "2", Payload: []byte(`""`)})555 s.pool.Run("fake", &HelperInput{App: app, NotificationId: "2", Payload: []byte(`""`)})
558 s.waitForArgs(c, "Launch")556 s.waitForArgs(c, "Launch")
559 go s.fakeLauncher.done("0")557 go s.fakeLauncher.done("0")
560 // now we should get the fake + all the bad ones558 takeNext(ch, c)
561 for i := 0; i < numBad+1; i++ {559 // so now there's one done, one "running", and one more waiting.
562 takeNext(ch, c)560 // kicking it forward one more notch before checking the logs:
563 }
564 s.waitForArgs(c, "Launch")561 s.waitForArgs(c, "Launch")
565 go s.fakeLauncher.done("1")562 go s.fakeLauncher.done("1")
566 takeNext(ch, c)563 takeNext(ch, c)
567 // so now there's one good one "running", and one more waiting.564 // (two done, one "running")
568 c.Check(s.log.Captured(), Matches, `(?ms).* shrunk to 1 entries\.$`)565 c.Check(s.log.Captured(), Matches, `(?ms).* shrunk to 1 entries\.$`)
569 // and the shrinker shrunk566 // and the backlog shrinker shrunk the backlog
570 c.Check(s.log.Captured(), Matches, `(?ms).*copying backlog to avoid wasting too much space .*`)567 c.Check(s.log.Captured(), Matches, `(?ms).*copying backlog to avoid wasting too much space .*`)
571}568}
572569
573570
=== added file 'scripts/click-hook'
--- scripts/click-hook 1970-01-01 00:00:00 +0000
+++ scripts/click-hook 2014-08-04 15:37:24 +0000
@@ -0,0 +1,113 @@
1#!/usr/bin/python3
2"""Collect helpers hook data into a single json file"""
3
4import argparse
5import json
6import os
7import sys
8import time
9
10import xdg.BaseDirectory
11
12from gi.repository import GLib
13from gi.repository import Gio
14from gi.repository import Click
15
16hook_ext = '.json'
17
18
19def tup2variant(tup):
20 builder = GLib.VariantBuilder.new(GLib.VariantType.new("(ss)"))
21 builder.add_value(GLib.Variant.new_string(tup[0]))
22 builder.add_value(GLib.Variant.new_string(tup[1]))
23 return builder.end()
24
25
26def cleanup_settings():
27 clickdb = Click.DB.new()
28 clickdb.read()
29
30 pkgnames = []
31 for package in clickdb.get_packages(False):
32 pkgnames.append(package.get_property('package'))
33
34 settings = Gio.Settings.new('com.ubuntu.notifications.hub')
35 goodapps = GLib.VariantBuilder.new(GLib.VariantType.new("a(ss)"))
36
37 for appname in settings.get_value('blacklist').unpack():
38 if appname[0] in pkgnames:
39 goodapps.add_value(tup2variant(appname))
40 elif (appname[0] == appname[1]):
41 appinfo = Gio.DesktopAppInfo.new(appname[0] + ".desktop")
42 if not appinfo is None:
43 goodapps.add_value(tup2variant(appname))
44
45 settings.set_value('blacklist', goodapps.end())
46
47
48def collect_helpers(helpers_data_path, helpers_data_path_tmp, hooks_path):
49 helpers_data = {}
50 for hook_fname in os.listdir(hooks_path):
51 if not hook_fname.endswith(hook_ext):
52 continue
53 try:
54 with open(os.path.join(hooks_path, hook_fname), 'r') as fd:
55 data = json.load(fd)
56 except Exception:
57 continue
58 else:
59 helper_id = os.path.splitext(hook_fname)[0]
60 exec_path = data['exec']
61 if exec_path != "":
62 realpath = os.path.realpath(os.path.join(hooks_path,
63 hook_fname))
64 exec_path = os.path.join(os.path.dirname(realpath), exec_path)
65 app_id = data.get('app_id', None)
66 if app_id is None:
67 # no app_id, use the package name from the helper_id
68 app_id = helper_id.split('_')[0]
69 elif app_id.count('_') >= 3:
70 # remove the version from the app_id
71 app_id = app_id.rsplit('_', 1)[0]
72 helpers_data[app_id] = {'exec': exec_path, 'helper_id': helper_id}
73
74 # write the collected data to a temp file and rename the original once
75 # everything is on disk
76 try:
77 tmp_filename = helpers_data_path_tmp % (time.time(),)
78 with open(tmp_filename, 'w') as dest:
79 json.dump(helpers_data, dest)
80 dest.flush()
81 os.rename(tmp_filename, helpers_data_path)
82 except Exception:
83 return True
84 return False
85
86
87def main(helpers_data_path=None, helpers_data_path_tmp=None, hooks_path=None):
88 collect_fail = collect_helpers(helpers_data_path, helpers_data_path_tmp,
89 hooks_path)
90 clean_settings_fail = False
91 try:
92 cleanup_settings()
93 except Exception:
94 clean_settings_fail = True
95 return int(collect_fail or clean_settings_fail)
96
97
98if __name__ == "__main__":
99 xdg_data_home = xdg.BaseDirectory.xdg_data_home
100 parser = argparse.ArgumentParser(description=__doc__)
101 parser.add_argument('-d', '--data-home',
102 help='The Path to the (xdg) data home',
103 default=xdg_data_home)
104 args = parser.parse_args()
105 xdg_data_home = args.data_home
106 helpers_data_path = os.path.join(xdg_data_home, 'ubuntu-push-client',
107 'helpers_data.json')
108 helpers_data_path_tmp = os.path.join(xdg_data_home, 'ubuntu-push-client',
109 '.helpers_data_%s.tmp')
110 hooks_path = os.path.join(xdg_data_home, 'ubuntu-push-client', 'helpers')
111 sys.exit(main(helpers_data_path=helpers_data_path,
112 helpers_data_path_tmp=helpers_data_path_tmp,
113 hooks_path=hooks_path))

Subscribers

People subscribed via source and target branches