Merge lp:~chipaca/ubuntu-push/the-push-automatic into lp:ubuntu-push
- the-push-automatic
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Push Hackers | Pending | ||
Review via email: mp+229306@code.launchpad.net |
Commit message
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
1 | === added directory 'click/cblacklist' | |||
2 | === added file 'click/cblacklist/cblacklist.go' | |||
3 | --- click/cblacklist/cblacklist.go 1970-01-01 00:00:00 +0000 | |||
4 | +++ click/cblacklist/cblacklist.go 2014-08-04 15:37:24 +0000 | |||
5 | @@ -0,0 +1,75 @@ | |||
6 | 1 | /* | ||
7 | 2 | Copyright 2013-2014 Canonical Ltd. | ||
8 | 3 | |||
9 | 4 | This program is free software: you can redistribute it and/or modify it | ||
10 | 5 | under the terms of the GNU General Public License version 3, as published | ||
11 | 6 | by the Free Software Foundation. | ||
12 | 7 | |||
13 | 8 | This program is distributed in the hope that it will be useful, but | ||
14 | 9 | WITHOUT ANY WARRANTY; without even the implied warranties of | ||
15 | 10 | MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
16 | 11 | PURPOSE. See the GNU General Public License for more details. | ||
17 | 12 | |||
18 | 13 | You should have received a copy of the GNU General Public License along | ||
19 | 14 | with this program. If not, see <http://www.gnu.org/licenses/>. | ||
20 | 15 | */ | ||
21 | 16 | |||
22 | 17 | // Package cblacklist accesses the g_settings notification blacklist | ||
23 | 18 | |||
24 | 19 | package cblacklist | ||
25 | 20 | |||
26 | 21 | /* | ||
27 | 22 | #cgo pkg-config: gio-unix-2.0 | ||
28 | 23 | #cgo pkg-config: glib-2.0 | ||
29 | 24 | |||
30 | 25 | #include <stdlib.h> | ||
31 | 26 | #include <gio/gio.h> | ||
32 | 27 | |||
33 | 28 | #define BLACKLIST_CONFIG_SCHEMA_ID "com.ubuntu.notifications.hub" | ||
34 | 29 | #define BLACKLIST_KEY "blacklist" | ||
35 | 30 | |||
36 | 31 | int is_blacklisted(const char *pkgname, const char *appname) { | ||
37 | 32 | static GSettings *pushSettings = NULL; | ||
38 | 33 | GVariantIter *iter; | ||
39 | 34 | gchar *pkg; | ||
40 | 35 | gchar *app; | ||
41 | 36 | int blacklisted = 0; | ||
42 | 37 | |||
43 | 38 | if (!pushSettings) { | ||
44 | 39 | GSettingsSchemaSource * source = g_settings_schema_source_get_default (); | ||
45 | 40 | if (!g_settings_schema_source_lookup (g_settings_schema_source_get_default (), BLACKLIST_CONFIG_SCHEMA_ID, TRUE)) { | ||
46 | 41 | return -1; | ||
47 | 42 | } | ||
48 | 43 | pushSettings = g_settings_new(BLACKLIST_CONFIG_SCHEMA_ID); | ||
49 | 44 | } | ||
50 | 45 | GVariant *blacklist = g_settings_get_value(pushSettings, BLACKLIST_KEY); | ||
51 | 46 | g_variant_get (blacklist, "a(ss)", &iter); | ||
52 | 47 | while (g_variant_iter_loop (iter, "(ss)", &pkg, &app)) { | ||
53 | 48 | if (0==g_strcmp0(pkg, pkgname) && 0==g_strcmp0(app, appname)) { | ||
54 | 49 | blacklisted = 1; | ||
55 | 50 | break; | ||
56 | 51 | } | ||
57 | 52 | // No need to free pkg and app, according to GVariant array example | ||
58 | 53 | } | ||
59 | 54 | g_variant_iter_free (iter); | ||
60 | 55 | g_variant_unref (blacklist); | ||
61 | 56 | return blacklisted; | ||
62 | 57 | } | ||
63 | 58 | |||
64 | 59 | */ | ||
65 | 60 | import "C" | ||
66 | 61 | |||
67 | 62 | import ( | ||
68 | 63 | "unsafe" | ||
69 | 64 | |||
70 | 65 | "launchpad.net/ubuntu-push/click" | ||
71 | 66 | ) | ||
72 | 67 | |||
73 | 68 | // IsBlacklisted returns true if the application is in the gsettings blacklist | ||
74 | 69 | func IsBlacklisted(app *click.AppId) bool { | ||
75 | 70 | pkgname := C.CString(app.Package) | ||
76 | 71 | appname := C.CString(app.Application) | ||
77 | 72 | defer C.free(unsafe.Pointer(pkgname)) | ||
78 | 73 | defer C.free(unsafe.Pointer(appname)) | ||
79 | 74 | return C.is_blacklisted(pkgname, appname) == 1 | ||
80 | 75 | } | ||
81 | 0 | 76 | ||
82 | === modified file 'click/click.go' | |||
83 | --- click/click.go 2014-07-25 21:50:53 +0000 | |||
84 | +++ click/click.go 2014-08-04 15:37:24 +0000 | |||
85 | @@ -22,7 +22,6 @@ | |||
86 | 22 | "encoding/json" | 22 | "encoding/json" |
87 | 23 | "errors" | 23 | "errors" |
88 | 24 | "fmt" | 24 | "fmt" |
89 | 25 | "io/ioutil" | ||
90 | 26 | "path/filepath" | 25 | "path/filepath" |
91 | 27 | "regexp" | 26 | "regexp" |
92 | 28 | "strings" | 27 | "strings" |
93 | @@ -43,9 +42,6 @@ | |||
94 | 43 | original string | 42 | original string |
95 | 44 | } | 43 | } |
96 | 45 | 44 | ||
97 | 46 | var hookPath = filepath.Join(xdg.Data.Home(), "ubuntu-push-client", "helpers") | ||
98 | 47 | var hookExt = ".json" | ||
99 | 48 | |||
100 | 49 | // from https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId | 45 | // from https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId |
101 | 50 | // except the version is made optional | 46 | // except the version is made optional |
102 | 51 | var rxClick = regexp.MustCompile(`^([a-z0-9][a-z0-9+.-]+)_([a-zA-Z0-9+.-]+)(?:_([0-9][a-zA-Z0-9.+:~-]*))?$`) | 47 | var rxClick = regexp.MustCompile(`^([a-z0-9][a-z0-9+.-]+)_([a-zA-Z0-9+.-]+)(?:_([0-9][a-zA-Z0-9.+:~-]*))?$`) |
103 | @@ -121,46 +117,6 @@ | |||
104 | 121 | } | 117 | } |
105 | 122 | } | 118 | } |
106 | 123 | 119 | ||
107 | 124 | type hookFile struct { | ||
108 | 125 | AppId string `json:"app_id"` | ||
109 | 126 | Exec string `json:"exec"` | ||
110 | 127 | } | ||
111 | 128 | |||
112 | 129 | // Helper figures out the app id and executable of the untrusted | ||
113 | 130 | // helper for this app. | ||
114 | 131 | func (app *AppId) Helper() (helperAppId string, helperExec string) { | ||
115 | 132 | if !app.Click { | ||
116 | 133 | return "", "" | ||
117 | 134 | } | ||
118 | 135 | // xxx: should probably have a cache of this | ||
119 | 136 | matches, err := filepath.Glob(filepath.Join(hookPath, app.Package+"_*"+hookExt)) | ||
120 | 137 | if err != nil { | ||
121 | 138 | return "", "" | ||
122 | 139 | } | ||
123 | 140 | var v hookFile | ||
124 | 141 | for _, m := range matches { | ||
125 | 142 | abs, err := filepath.EvalSymlinks(m) | ||
126 | 143 | if err != nil { | ||
127 | 144 | continue | ||
128 | 145 | } | ||
129 | 146 | data, err := ioutil.ReadFile(abs) | ||
130 | 147 | if err != nil { | ||
131 | 148 | continue | ||
132 | 149 | } | ||
133 | 150 | err = json.Unmarshal(data, &v) | ||
134 | 151 | if err != nil { | ||
135 | 152 | continue | ||
136 | 153 | } | ||
137 | 154 | if v.Exec != "" && (v.AppId == "" || v.AppId == app.Base()) { | ||
138 | 155 | basename := filepath.Base(m) | ||
139 | 156 | helperAppId = basename[:len(basename)-len(hookExt)] | ||
140 | 157 | helperExec = filepath.Join(filepath.Dir(abs), v.Exec) | ||
141 | 158 | return helperAppId, helperExec | ||
142 | 159 | } | ||
143 | 160 | } | ||
144 | 161 | return "", "" | ||
145 | 162 | } | ||
146 | 163 | |||
147 | 164 | func (app *AppId) Versioned() string { | 120 | func (app *AppId) Versioned() string { |
148 | 165 | if app.Click { | 121 | if app.Click { |
149 | 166 | if app.Version == "" { | 122 | if app.Version == "" { |
150 | 167 | 123 | ||
151 | === modified file 'click/click_test.go' | |||
152 | --- click/click_test.go 2014-07-25 21:50:53 +0000 | |||
153 | +++ click/click_test.go 2014-08-04 15:37:24 +0000 | |||
154 | @@ -19,8 +19,6 @@ | |||
155 | 19 | import ( | 19 | import ( |
156 | 20 | "encoding/json" | 20 | "encoding/json" |
157 | 21 | "fmt" | 21 | "fmt" |
158 | 22 | "os" | ||
159 | 23 | "path/filepath" | ||
160 | 24 | "testing" | 22 | "testing" |
161 | 25 | 23 | ||
162 | 26 | . "launchpad.net/gocheck" | 24 | . "launchpad.net/gocheck" |
163 | @@ -183,78 +181,4 @@ | |||
164 | 183 | c.Assert(err, Equals, ErrMissingAppId) | 181 | c.Assert(err, Equals, ErrMissingAppId) |
165 | 184 | c.Check(app, NotNil) | 182 | c.Check(app, NotNil) |
166 | 185 | c.Check(app.Original(), Equals, "_non-existent-app") | 183 | c.Check(app.Original(), Equals, "_non-existent-app") |
167 | 186 | |||
168 | 187 | } | ||
169 | 188 | |||
170 | 189 | type helperSuite struct { | ||
171 | 190 | oldHookPath string | ||
172 | 191 | symlinkPath string | ||
173 | 192 | } | ||
174 | 193 | |||
175 | 194 | var _ = Suite(&helperSuite{}) | ||
176 | 195 | |||
177 | 196 | func (s *helperSuite) SetUpTest(c *C) { | ||
178 | 197 | s.oldHookPath = hookPath | ||
179 | 198 | hookPath = c.MkDir() | ||
180 | 199 | s.symlinkPath = c.MkDir() | ||
181 | 200 | } | ||
182 | 201 | |||
183 | 202 | func (s *helperSuite) createHookfile(name string, content string) error { | ||
184 | 203 | symlink := filepath.Join(hookPath, name) + ".json" | ||
185 | 204 | filename := filepath.Join(s.symlinkPath, name) | ||
186 | 205 | f, err := os.Create(filename) | ||
187 | 206 | if err != nil { | ||
188 | 207 | return err | ||
189 | 208 | } | ||
190 | 209 | _, err = f.WriteString(content) | ||
191 | 210 | if err != nil { | ||
192 | 211 | return err | ||
193 | 212 | } | ||
194 | 213 | err = os.Symlink(filename, symlink) | ||
195 | 214 | if err != nil { | ||
196 | 215 | return err | ||
197 | 216 | } | ||
198 | 217 | return nil | ||
199 | 218 | } | ||
200 | 219 | |||
201 | 220 | func (s *helperSuite) TearDownTest(c *C) { | ||
202 | 221 | hookPath = s.oldHookPath | ||
203 | 222 | } | ||
204 | 223 | |||
205 | 224 | func (s *helperSuite) TestHelperBasic(c *C) { | ||
206 | 225 | c.Assert(s.createHookfile("com.example.test_test-helper_1", `{"exec": "tsthlpr"}`), IsNil) | ||
207 | 226 | app, err := ParseAppId("com.example.test_test-app_1") | ||
208 | 227 | c.Assert(err, IsNil) | ||
209 | 228 | hid, hex := app.Helper() | ||
210 | 229 | c.Check(hid, Equals, "com.example.test_test-helper_1") | ||
211 | 230 | c.Check(hex, Equals, filepath.Join(s.symlinkPath, "tsthlpr")) | ||
212 | 231 | } | ||
213 | 232 | |||
214 | 233 | func (s *helperSuite) TestHelperFindsSpecific(c *C) { | ||
215 | 234 | // Glob() sorts, so the first one will come first | ||
216 | 235 | c.Assert(s.createHookfile("com.example.test_aaaa-helper_1", `{"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil) | ||
217 | 236 | c.Assert(s.createHookfile("com.example.test_test-helper_1", `{"exec": "tsthlpr", "app_id": "com.example.test_test-app"}`), IsNil) | ||
218 | 237 | app, err := ParseAppId("com.example.test_test-app_1") | ||
219 | 238 | c.Assert(err, IsNil) | ||
220 | 239 | hid, hex := app.Helper() | ||
221 | 240 | c.Check(hid, Equals, "com.example.test_test-helper_1") | ||
222 | 241 | c.Check(hex, Equals, filepath.Join(s.symlinkPath, "tsthlpr")) | ||
223 | 242 | } | ||
224 | 243 | |||
225 | 244 | func (s *helperSuite) TestHelperCanFail(c *C) { | ||
226 | 245 | c.Assert(s.createHookfile("com.example.test_aaaa-helper_1", `{"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil) | ||
227 | 246 | app, err := ParseAppId("com.example.test_test-app_1") | ||
228 | 247 | c.Assert(err, IsNil) | ||
229 | 248 | hid, hex := app.Helper() | ||
230 | 249 | c.Check(hid, Equals, "") | ||
231 | 250 | c.Check(hex, Equals, "") | ||
232 | 251 | } | ||
233 | 252 | |||
234 | 253 | func (s *clickSuite) TestHelperlegacy(c *C) { | ||
235 | 254 | appname := "ubuntu-system-settings" | ||
236 | 255 | app, err := ParseAppId("_" + appname) | ||
237 | 256 | c.Assert(err, IsNil) | ||
238 | 257 | hid, hex := app.Helper() | ||
239 | 258 | c.Check(hid, Equals, "") | ||
240 | 259 | c.Check(hex, Equals, "") | ||
241 | 260 | } | 184 | } |
242 | 261 | 185 | ||
243 | === modified file 'client/service/postal.go' | |||
244 | --- client/service/postal.go 2014-07-29 15:38:04 +0000 | |||
245 | +++ client/service/postal.go 2014-08-04 15:37:24 +0000 | |||
246 | @@ -30,6 +30,7 @@ | |||
247 | 30 | "launchpad.net/ubuntu-push/bus/urldispatcher" | 30 | "launchpad.net/ubuntu-push/bus/urldispatcher" |
248 | 31 | "launchpad.net/ubuntu-push/bus/windowstack" | 31 | "launchpad.net/ubuntu-push/bus/windowstack" |
249 | 32 | "launchpad.net/ubuntu-push/click" | 32 | "launchpad.net/ubuntu-push/click" |
250 | 33 | "launchpad.net/ubuntu-push/click/cblacklist" | ||
251 | 33 | "launchpad.net/ubuntu-push/launch_helper" | 34 | "launchpad.net/ubuntu-push/launch_helper" |
252 | 34 | "launchpad.net/ubuntu-push/logger" | 35 | "launchpad.net/ubuntu-push/logger" |
253 | 35 | "launchpad.net/ubuntu-push/messaging" | 36 | "launchpad.net/ubuntu-push/messaging" |
254 | @@ -392,6 +393,8 @@ | |||
255 | 392 | return svc.urlDispatcher.TestURL(app, notif.Card.Actions) | 393 | return svc.urlDispatcher.TestURL(app, notif.Card.Actions) |
256 | 393 | } | 394 | } |
257 | 394 | 395 | ||
258 | 396 | var isBlacklisted = cblacklist.IsBlacklisted | ||
259 | 397 | |||
260 | 395 | func (svc *PostalService) messageHandler(app *click.AppId, nid string, output *launch_helper.HelperOutput) bool { | 398 | func (svc *PostalService) messageHandler(app *click.AppId, nid string, output *launch_helper.HelperOutput) bool { |
261 | 396 | if output == nil || output.Notification == nil { | 399 | if output == nil || output.Notification == nil { |
262 | 397 | svc.Log.Debugf("skipping notification: nil.") | 400 | svc.Log.Debugf("skipping notification: nil.") |
263 | @@ -403,6 +406,11 @@ | |||
264 | 403 | return false | 406 | return false |
265 | 404 | } | 407 | } |
266 | 405 | if !svc.windowStack.IsAppFocused(app) { | 408 | if !svc.windowStack.IsAppFocused(app) { |
267 | 409 | if isBlacklisted(app) { | ||
268 | 410 | svc.Log.Debugf("notification skipped (except emblem counter) because app is blacklisted") | ||
269 | 411 | return svc.emblemCounter.Present(app, nid, output.Notification) | ||
270 | 412 | } | ||
271 | 413 | |||
272 | 406 | b := false | 414 | b := false |
273 | 407 | for _, p := range svc.Presenters { | 415 | for _, p := range svc.Presenters { |
274 | 408 | // we don't want this to shortcut :) | 416 | // we don't want this to shortcut :) |
275 | 409 | 417 | ||
276 | === modified file 'client/service/postal_test.go' | |||
277 | --- client/service/postal_test.go 2014-07-29 15:38:04 +0000 | |||
278 | +++ client/service/postal_test.go 2014-08-04 15:37:24 +0000 | |||
279 | @@ -141,6 +141,8 @@ | |||
280 | 141 | winStackBus bus.Endpoint | 141 | winStackBus bus.Endpoint |
281 | 142 | fakeLauncher *fakeHelperLauncher | 142 | fakeLauncher *fakeHelperLauncher |
282 | 143 | getTempDir func(string) (string, error) | 143 | getTempDir func(string) (string, error) |
283 | 144 | oldIsBlisted func(*click.AppId) bool | ||
284 | 145 | blacklisted bool | ||
285 | 144 | } | 146 | } |
286 | 145 | 147 | ||
287 | 146 | type ualPostalSuite struct { | 148 | type ualPostalSuite struct { |
288 | @@ -155,6 +157,8 @@ | |||
289 | 155 | var _ = Suite(&trivialPostalSuite{}) | 157 | var _ = Suite(&trivialPostalSuite{}) |
290 | 156 | 158 | ||
291 | 157 | func (ps *postalSuite) SetUpTest(c *C) { | 159 | func (ps *postalSuite) SetUpTest(c *C) { |
292 | 160 | ps.oldIsBlisted = isBlacklisted | ||
293 | 161 | isBlacklisted = func(*click.AppId) bool { return ps.blacklisted } | ||
294 | 158 | ps.log = helpers.NewTestLogger(c, "debug") | 162 | ps.log = helpers.NewTestLogger(c, "debug") |
295 | 159 | ps.bus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) | 163 | ps.bus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
296 | 160 | ps.notifBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) | 164 | ps.notifBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
297 | @@ -163,6 +167,7 @@ | |||
298 | 163 | ps.urlDispBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) | 167 | ps.urlDispBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
299 | 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{}) |
300 | 165 | ps.fakeLauncher = &fakeHelperLauncher{ch: make(chan []byte)} | 169 | ps.fakeLauncher = &fakeHelperLauncher{ch: make(chan []byte)} |
301 | 170 | ps.blacklisted = false | ||
302 | 166 | 171 | ||
303 | 167 | ps.getTempDir = launch_helper.GetTempDir | 172 | ps.getTempDir = launch_helper.GetTempDir |
304 | 168 | d := c.MkDir() | 173 | d := c.MkDir() |
305 | @@ -173,6 +178,7 @@ | |||
306 | 173 | } | 178 | } |
307 | 174 | 179 | ||
308 | 175 | func (ps *postalSuite) TearDownTest(c *C) { | 180 | func (ps *postalSuite) TearDownTest(c *C) { |
309 | 181 | isBlacklisted = ps.oldIsBlisted | ||
310 | 176 | launch_helper.GetTempDir = ps.getTempDir | 182 | launch_helper.GetTempDir = ps.getTempDir |
311 | 177 | } | 183 | } |
312 | 178 | 184 | ||
313 | @@ -779,3 +785,23 @@ | |||
314 | 779 | c.Check(err, Equals, s.err, Commentf("iter %d", i)) | 785 | c.Check(err, Equals, s.err, Commentf("iter %d", i)) |
315 | 780 | } | 786 | } |
316 | 781 | } | 787 | } |
317 | 788 | |||
318 | 789 | func (ps *postalSuite) TestBlacklisted(c *C) { | ||
319 | 790 | svc := ps.replaceBuses(NewPostalService(nil, ps.log)) | ||
320 | 791 | svc.Start() | ||
321 | 792 | ps.blacklisted = false | ||
322 | 793 | |||
323 | 794 | emb := &launch_helper.EmblemCounter{Count: 2, Visible: true} | ||
324 | 795 | card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Persist: true} | ||
325 | 796 | output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card}} | ||
326 | 797 | embOut := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{EmblemCounter: emb}} | ||
327 | 798 | app := clickhelp.MustParseAppId("com.example.app_app_1.0") | ||
328 | 799 | // sanity check: things are presented as normal if blacklist == false | ||
329 | 800 | ps.blacklisted = false | ||
330 | 801 | c.Check(svc.messageHandler(app, "0", output), Equals, true) | ||
331 | 802 | c.Check(svc.messageHandler(app, "1", embOut), Equals, true) | ||
332 | 803 | ps.blacklisted = true | ||
333 | 804 | // and regular notifications (but not emblem counters) are supprsessed if blacklisted. | ||
334 | 805 | c.Check(svc.messageHandler(app, "2", output), Equals, false) | ||
335 | 806 | c.Check(svc.messageHandler(app, "3", embOut), Equals, true) | ||
336 | 807 | } | ||
337 | 782 | 808 | ||
338 | === modified file 'debian/changelog' | |||
339 | --- debian/changelog 2014-08-01 02:15:29 +0000 | |||
340 | +++ debian/changelog 2014-08-04 15:37:24 +0000 | |||
341 | @@ -1,3 +1,20 @@ | |||
342 | 1 | ubuntu-push (0.60-0ubuntu1) UNRELEASED; urgency=medium | ||
343 | 2 | |||
344 | 3 | [ Guillermo Gonzalez ] | ||
345 | 4 | * Add click hook to collect helpers data on install/update/etc and | ||
346 | 5 | support to read the helper cached data, when available, and only | ||
347 | 6 | refresh it when it changes. | ||
348 | 7 | * Include notification settings cleanup in the click install hook, and | ||
349 | 8 | rename it to click-hook | ||
350 | 9 | |||
351 | 10 | [ John R. Lenton ] | ||
352 | 11 | * For the gsettings interface: Improve, add tests, make design-compliant. | ||
353 | 12 | |||
354 | 13 | [Roberto Alsina] | ||
355 | 14 | * Query gsettings as to whether a notification should be presented. | ||
356 | 15 | |||
357 | 16 | -- John R. Lenton <john.lenton@canonical.com> Sat, 02 Aug 2014 01:42:17 +0200 | ||
358 | 17 | |||
359 | 1 | ubuntu-push (0.50+14.10.20140801-0ubuntu1) utopic; urgency=low | 18 | ubuntu-push (0.50+14.10.20140801-0ubuntu1) utopic; urgency=low |
360 | 2 | 19 | ||
361 | 3 | * New rebuild forced | 20 | * New rebuild forced |
362 | @@ -7,7 +24,7 @@ | |||
363 | 7 | ubuntu-push (0.50+14.10.20140729-0ubuntu1) utopic; urgency=medium | 24 | ubuntu-push (0.50+14.10.20140729-0ubuntu1) utopic; urgency=medium |
364 | 8 | 25 | ||
365 | 9 | [ Samuele Pedroni ] | 26 | [ Samuele Pedroni ] |
367 | 10 | * Cleanup and improve pos/Post tests | 27 | * Cleanup and improve post/Post tests |
368 | 11 | 28 | ||
369 | 12 | [ Guillermo Gonzalez ] | 29 | [ Guillermo Gonzalez ] |
370 | 13 | * Add a loop to cleanup MessagingMenu.notifications map when the | 30 | * Add a loop to cleanup MessagingMenu.notifications map when the |
371 | 14 | 31 | ||
372 | === modified file 'debian/push-helper.hook' | |||
373 | --- debian/push-helper.hook 2014-07-23 13:56:09 +0000 | |||
374 | +++ debian/push-helper.hook 2014-08-04 15:37:24 +0000 | |||
375 | @@ -1,3 +1,4 @@ | |||
376 | 1 | Pattern: ${home}/.local/share/ubuntu-push-client/helpers/${id}.json | 1 | Pattern: ${home}/.local/share/ubuntu-push-client/helpers/${id}.json |
377 | 2 | User-Level: yes | 2 | User-Level: yes |
378 | 3 | Hook-Name: push-helper | 3 | Hook-Name: push-helper |
379 | 4 | Exec: /usr/lib/ubuntu-push-client/click-hook | ||
380 | 4 | 5 | ||
381 | === modified file 'debian/ubuntu-push-client.install' | |||
382 | --- debian/ubuntu-push-client.install 2014-07-19 00:18:18 +0000 | |||
383 | +++ debian/ubuntu-push-client.install 2014-08-04 15:37:24 +0000 | |||
384 | @@ -4,5 +4,6 @@ | |||
385 | 4 | debian/exec-tool /usr/lib/${DEB_HOST_MULTIARCH}/ubuntu-app-launch/push-helper | 4 | debian/exec-tool /usr/lib/${DEB_HOST_MULTIARCH}/ubuntu-app-launch/push-helper |
386 | 5 | debian/push-helper.hook /usr/share/click/hooks | 5 | debian/push-helper.hook /usr/share/click/hooks |
387 | 6 | signing-helper/signing-helper /usr/lib/ubuntu-push-client | 6 | signing-helper/signing-helper /usr/lib/ubuntu-push-client |
388 | 7 | scripts/click-hook /usr/lib/ubuntu-push-client | ||
389 | 7 | usr/bin/ubuntu-push => /usr/lib/ubuntu-push-client/ubuntu-push-client | 8 | usr/bin/ubuntu-push => /usr/lib/ubuntu-push-client/ubuntu-push-client |
390 | 8 | scripts/software-updates-helper.py => /usr/lib/ubuntu-push-client/legacy-helpers/ubuntu-system-settings | 9 | scripts/software-updates-helper.py => /usr/lib/ubuntu-push-client/legacy-helpers/ubuntu-system-settings |
391 | 9 | 10 | ||
392 | === added directory 'docs' | |||
393 | === added file 'docs/lowlevel.txt' | |||
394 | --- docs/lowlevel.txt 1970-01-01 00:00:00 +0000 | |||
395 | +++ docs/lowlevel.txt 2014-08-04 15:37:24 +0000 | |||
396 | @@ -0,0 +1,420 @@ | |||
397 | 1 | Ubuntu Push Client Developer Guide | ||
398 | 2 | ================================== | ||
399 | 3 | |||
400 | 4 | :Version: 0.50+ | ||
401 | 5 | |||
402 | 6 | Introduction | ||
403 | 7 | ------------ | ||
404 | 8 | |||
405 | 9 | This document describes how to use the Ubuntu Push Client service from a platform integrator's point of view. | ||
406 | 10 | Application developers are expected to use a much simpler API, in turn based on the lower-level API described here. | ||
407 | 11 | |||
408 | 12 | The expected audience for this document is, therefore, either platform developers, or application developers who, | ||
409 | 13 | for whatever reason, can't use or prefer not to use the available higher level APIs. | ||
410 | 14 | |||
411 | 15 | --------- | ||
412 | 16 | |||
413 | 17 | Let's describe the push system by way of an example. | ||
414 | 18 | |||
415 | 19 | Alice has written a chat application called Chatter. Using it, Bob can send messages to Carol and viceversa. Alice has a | ||
416 | 20 | web application for it, so the way it works now is that Bob connects to the service, posts a message, and when Carol | ||
417 | 21 | connects, she gets it. If Carol leaves the browser window open, it beeps when messages arrive. | ||
418 | 22 | |||
419 | 23 | Now Alice wants to create an Ubuntu Touch app for Chatter, so she implements the same architecture using a client that | ||
420 | 24 | does the same thing as the web browser. Sadly, since applications on Ubuntu Touch don't run continuously, messages are | ||
421 | 25 | only delivered when Carol opens the app, and the user experience suffers. | ||
422 | 26 | |||
423 | 27 | Using the Ubuntu Push Server, this problem is alleviated: the Chatter server will deliver the messages to the Ubuntu | ||
424 | 28 | Push Server, which in turn will send it in an efficient manner to the Ubuntu Push Client running in Bob and Carol's | ||
425 | 29 | devices. The user sees a notification (all without starting the app) and then can launch it if he's interested in | ||
426 | 30 | reading messages at that point. | ||
427 | 31 | |||
428 | 32 | Since the app is not started and messages are delivered oportunistically, this is both battery and bandwidth-efficient. | ||
429 | 33 | |||
430 | 34 | .. figure:: push.svg | ||
431 | 35 | |||
432 | 36 | The Ubuntu Push system provides: | ||
433 | 37 | |||
434 | 38 | * A push server which receives **push messages** from the app servers, queues them and delivers them efficiently | ||
435 | 39 | to the devices. | ||
436 | 40 | * A push client which receives those messages, queues messages to the app and displays notifications to the user | ||
437 | 41 | |||
438 | 42 | The full lifecycle of a push message is: | ||
439 | 43 | |||
440 | 44 | * Created in a application-specific server | ||
441 | 45 | * Sent to the Ubuntu Push server, targeted at a user or user+device pair | ||
442 | 46 | * Delivered to one or more Ubuntu devices | ||
443 | 47 | * Passed through the application helper for processing | ||
444 | 48 | * Notification displayed to the user (via different mechanisms) | ||
445 | 49 | * Application Message queued for the app's use | ||
446 | 50 | |||
447 | 51 | If the user interacts with the notification, the application is launched and should check its queue for messages | ||
448 | 52 | it has to process. | ||
449 | 53 | |||
450 | 54 | For the app developer, there are several components needed: | ||
451 | 55 | |||
452 | 56 | * A server that sends the **push messages** to the Ubuntu Push server | ||
453 | 57 | * Support in the client app for registering with the Ubuntu Push client | ||
454 | 58 | * Support in the client app to react to **notifications** displayed to the user and process **application messages** | ||
455 | 59 | * A helper program with application-specific knowledge that transforms **push messages** as needed. | ||
456 | 60 | |||
457 | 61 | In the following sections, we'll see how to implement all the client side parts. For the application server, see the | ||
458 | 62 | `Ubuntu Push Server API section <#ubuntu-push-server-api>`__ | ||
459 | 63 | |||
460 | 64 | The PushNotifications Service | ||
461 | 65 | ----------------------------- | ||
462 | 66 | |||
463 | 67 | :Service: com.ubuntu.PushNotifications | ||
464 | 68 | :Object path: /com/ubuntu/PushNotifications/QUOTED_PKGNAME | ||
465 | 69 | |||
466 | 70 | The PushNotifications service handles registering the device with the Ubuntu Push service to enable delivery of messages to | ||
467 | 71 | it. | ||
468 | 72 | |||
469 | 73 | Each Ubuntu Touch package has to use a separate object path for security reasons, that's why the object path includes QUOTED_PKGNAME. | ||
470 | 74 | For example, in the case of the music application, the package name is ``com.ubuntu.music`` and QUOTED_PKGNAME is com_2eubuntu_2emusic. | ||
471 | 75 | Everything that is not a letter or digit has to be quoted as _XX where XX are the hex digits of the character. In practice, | ||
472 | 76 | this means replacing "." with "_2e" and "-" with "_2f" | ||
473 | 77 | |||
474 | 78 | .. note:: For applications that are not installed as part of click packages, the QUOTED_PKGNAME is "_" and the APP_ID when required is | ||
475 | 79 | _PACKAGENAME. | ||
476 | 80 | |||
477 | 81 | For example, for ubuntu-system-settins: | ||
478 | 82 | |||
479 | 83 | * QUOTED_PKGNAME is _ | ||
480 | 84 | * APP_ID is _ubuntu-system-settings | ||
481 | 85 | |||
482 | 86 | |||
483 | 87 | com.ubuntu.PushNotifications.Register | ||
484 | 88 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
485 | 89 | |||
486 | 90 | ``string Register(string APP_ID)`` | ||
487 | 91 | |||
488 | 92 | Example:: | ||
489 | 93 | |||
490 | 94 | $ gdbus call --session --dest com.ubuntu.PushNotifications --object-path /com/ubuntu/PushNotifications/com_2eubuntu_2emusic \ | ||
491 | 95 | --method com.ubuntu.PushNotifications.Register com.ubuntu.music_music | ||
492 | 96 | |||
493 | 97 | ('LeA4tRQG9hhEkuhngdouoA==',) | ||
494 | 98 | |||
495 | 99 | The Register method takes as argument the APP_ID (in the example, com.ubuntu.music_music) and returns a token identifying the user | ||
496 | 100 | and device. For this to succeed the user **must** have an Ubuntu One account configured in the device. | ||
497 | 101 | |||
498 | 102 | The APP_ID is as described in the `ApplicationId documentation <https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId>`__ | ||
499 | 103 | except that the version is treated as optional. Therefore both ``com.ubuntu.music_music`` and ``com.ubuntu.music_music_1.3.496`` | ||
500 | 104 | are valid. Keep in mind that while both versioned and unversioned APP_IDs are valid, they are still different and will affect | ||
501 | 105 | which notifications are delivered to the application. Unversioned IDs mean the token will be the same after updates and the application | ||
502 | 106 | will receive old notifications, while versioned IDs mean the app needs to explicitly ask to get older messages delivered. | ||
503 | 107 | |||
504 | 108 | Register is idempotent, and calling it multiple times returns the same token. | ||
505 | 109 | |||
506 | 110 | This token is later used by the application server to indicate the recipient of notifications. | ||
507 | 111 | |||
508 | 112 | .. FIXME crosslink to server app | ||
509 | 113 | |||
510 | 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 | ||
511 | 115 | each registered device individually instead. | ||
512 | 116 | |||
513 | 117 | com.ubuntu.PushNotifications.Unregister | ||
514 | 118 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
515 | 119 | |||
516 | 120 | ``void Unregister(string APP_ID)`` | ||
517 | 121 | |||
518 | 122 | Example:: | ||
519 | 123 | |||
520 | 124 | $ gdbus call --session --dest com.ubuntu.PushNotifications --object-path /com/ubuntu/PushNotifications/com_2eubuntu_2emusic \ | ||
521 | 125 | --method com.ubuntu.PushNotifications.Unregister com.ubuntu.music_music | ||
522 | 126 | |||
523 | 127 | The Unregister method invalidates the token obtained via `Register <#com-ubuntu-pushnotifications-register>`_ therefore disabling | ||
524 | 128 | reception of push messages. | ||
525 | 129 | |||
526 | 130 | The method takes as argument the APP_ID (in the example, com.ubuntu.music_music) and returns nothing. | ||
527 | 131 | |||
528 | 132 | The APP_ID is as described in the `ApplicationId documentation <https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId>`__ | ||
529 | 133 | except that the version is treated as optional. Therefore both ``com.ubuntu.music_music`` and ``com.ubuntu.music_music_1.3.496`` | ||
530 | 134 | are valid. | ||
531 | 135 | |||
532 | 136 | The Postal Service | ||
533 | 137 | ------------------ | ||
534 | 138 | |||
535 | 139 | :Service: com.ubuntu.Postal | ||
536 | 140 | :Object path: /com/ubuntu/Postal/QUOTED_PKGNAME | ||
537 | 141 | |||
538 | 142 | The Postal service delivers the actual messages to the applications. After the application is registered, the push client will begin | ||
539 | 143 | delivering messages to the device, which will then (possibly) cause specific notifications to be presented to the user (message bubbles, | ||
540 | 144 | sounds, haptic feedbak, etc.) Regardless of whether the user acknowledges those notifications or not, the payload of the push message | ||
541 | 145 | is put in the Postal service for the application to pick up. | ||
542 | 146 | |||
543 | 147 | Because user response to notifications can cause application activation, apps should check the status of the Postal service every time | ||
544 | 148 | the application activates. | ||
545 | 149 | |||
546 | 150 | com.ubuntu.Postal.Post | ||
547 | 151 | ~~~~~~~~~~~~~~~~~~~~~~ | ||
548 | 152 | |||
549 | 153 | ``void Post(string APP_ID, string message)`` | ||
550 | 154 | |||
551 | 155 | Example:: | ||
552 | 156 | |||
553 | 157 | gdbus call --session --dest com.ubuntu.Postal --object-path /com/ubuntu/Postal/com_2eubuntu_2emusic \ | ||
554 | 158 | --method com.ubuntu.Postal.Post com.ubuntu.music_music \ | ||
555 | 159 | '"{\"message\": \"foobar\", \"notification\":{\"card\": {\"summary\": \"yes\", \"body\": \"hello\", \"popup\": true, \"persist\": true}}}"' | ||
556 | 160 | |||
557 | 161 | |||
558 | 162 | The arguments for the Post method are APP_ID (in the example, com.ubuntu.music_music) and a JSON string | ||
559 | 163 | `describing a push message. <#push-message-format>`__ | ||
560 | 164 | |||
561 | 165 | Depending on the contents of the push message it may trigger user-facing notifications, and will queue a | ||
562 | 166 | message for the app to get via the `PopAll <#com-ubuntu-postal-popalls>`__ method. | ||
563 | 167 | |||
564 | 168 | The APP_ID is as described in the `ApplicationId documentation <https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId>`__ | ||
565 | 169 | except that the version is treated as optional. Therefore both ``com.ubuntu.music_music`` and ``com.ubuntu.music_music_1.3.496`` | ||
566 | 170 | are valid. | ||
567 | 171 | |||
568 | 172 | .. note:: Post is useful as a unified frontend for notifications in Ubuntu Touch, since it wraps and abstracts several different APIs. | ||
569 | 173 | |||
570 | 174 | com.ubuntu.Postal.PopAll | ||
571 | 175 | ~~~~~~~~~~~~~~~~~~~~~~~~ | ||
572 | 176 | |||
573 | 177 | ``array{string} PopAll(string APP_ID)`` | ||
574 | 178 | |||
575 | 179 | Example:: | ||
576 | 180 | |||
577 | 181 | $ gdbus call --session --dest com.ubuntu.Postal --object-path /com/ubuntu/Postal/com_2eubuntu_2emusic \ | ||
578 | 182 | --method com.ubuntu.Postal.PopAll com.ubuntu.music_music | ||
579 | 183 | |||
580 | 184 | (['{"foo": "bar", ....}'],) | ||
581 | 185 | |||
582 | 186 | The argument for the PopAll method is the APP_ID and it returns a list of strings, each string being a separate postal | ||
583 | 187 | message, the "message" element of a helper's output fed from `Post <#com-ubuntu-postal-post>`__ | ||
584 | 188 | or from the Ubuntu Push service, | ||
585 | 189 | |||
586 | 190 | Post Signal | ||
587 | 191 | ~~~~~~~~~~~ | ||
588 | 192 | |||
589 | 193 | ``void Post(string APP_ID)`` | ||
590 | 194 | |||
591 | 195 | Every time a notification is posted, the postal service will emit the Post signal. Your app can connect to it to react to | ||
592 | 196 | incoming notifications if it's running when they arrive. Remember that on Ubuntu Touch, the application lifecycle means | ||
593 | 197 | it will often **not** be running when notifications arrive. If the application is in the foreground when a notification | ||
594 | 198 | arrives, the notification **will not** be presented. | ||
595 | 199 | |||
596 | 200 | The object path is similar to that of the Postal service methods, containing the QUOTED_PKGNAME. | ||
597 | 201 | |||
598 | 202 | Persistent Notification Management | ||
599 | 203 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
600 | 204 | |||
601 | 205 | Some notifications are persistent, meaning they don't disappear automatically. For those notifications, there is an API that | ||
602 | 206 | allows the app to manage them without having to know the underlying details of the platform. | ||
603 | 207 | |||
604 | 208 | On each notification there's an optional ``tag`` field, used for this purpose. | ||
605 | 209 | |||
606 | 210 | ``array(string) ListPersistent(string APP_ID)`` | ||
607 | 211 | |||
608 | 212 | Returns a list of the tags of notifications with the "persist" element set to true that are visible to the user right now. | ||
609 | 213 | |||
610 | 214 | ``void ClearPersistent(string APP_ID, [tag1, tag2,....])`` | ||
611 | 215 | |||
612 | 216 | Clears persistent notifications for that app by tag(s). If none given, match all. | ||
613 | 217 | |||
614 | 218 | ``void SetCounter(string APP_ID, int count int, bool visible)`` | ||
615 | 219 | |||
616 | 220 | Set the counter to the given values. | ||
617 | 221 | |||
618 | 222 | |||
619 | 223 | Application Helpers | ||
620 | 224 | ------------------- | ||
621 | 225 | |||
622 | 226 | The payload delivered to push-client will be passed onto a helper program that can modify it as needed before passing it onto | ||
623 | 227 | the postal service (see `Helper Output Format <#helper-output-format>`__). | ||
624 | 228 | |||
625 | 229 | The helper receives two arguments ``infile`` and ``outfile``. The message is delivered via ``infile`` and the transformed | ||
626 | 230 | version is placed in ``outfile``. | ||
627 | 231 | |||
628 | 232 | This is the simplest possible useful helper, which simply passes the message through unchanged:: | ||
629 | 233 | |||
630 | 234 | #!/usr/bin/python3 | ||
631 | 235 | |||
632 | 236 | import sys | ||
633 | 237 | f1, f2 = sys.argv[1:3] | ||
634 | 238 | open(f2, "w").write(open(f1).read()) | ||
635 | 239 | |||
636 | 240 | Helpers need to be added to the click package manifest:: | ||
637 | 241 | |||
638 | 242 | { | ||
639 | 243 | "name": "com.ubuntu.developer.ralsina.hello", | ||
640 | 244 | "description": "description of hello", | ||
641 | 245 | "framework": "ubuntu-sdk-14.10-qml-dev2", | ||
642 | 246 | "architecture": "all", | ||
643 | 247 | "title": "hello", | ||
644 | 248 | "hooks": { | ||
645 | 249 | "hello": { | ||
646 | 250 | "apparmor": "hello.json", | ||
647 | 251 | "desktop": "hello.desktop" | ||
648 | 252 | }, | ||
649 | 253 | "helloHelper": { | ||
650 | 254 | "apparmor": "helloHelper-apparmor.json", | ||
651 | 255 | "push-helper": "helloHelper.json" | ||
652 | 256 | } | ||
653 | 257 | }, | ||
654 | 258 | "version": "0.2", | ||
655 | 259 | "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>" | ||
656 | 260 | } | ||
657 | 261 | |||
658 | 262 | Here, we created a helloHelper entry in hooks that has an apparmor profile and an additional JSON file for the push-helper hook. | ||
659 | 263 | |||
660 | 264 | helloHelper-apparmor.json must contain **only** the push-notification-client policy group:: | ||
661 | 265 | |||
662 | 266 | { | ||
663 | 267 | "policy_groups": [ | ||
664 | 268 | "push-notification-client" | ||
665 | 269 | ], | ||
666 | 270 | "policy_version": 1.2 | ||
667 | 271 | } | ||
668 | 272 | |||
669 | 273 | And helloHelper.json must have at least a exec key with the path to the helper executable relative to the json, and optionally | ||
670 | 274 | an app_id key containing the short id of one of the apps in the package (in the format packagename_appname without a version). | ||
671 | 275 | If the app_id is not specified, the helper will be used for all apps in the package:: | ||
672 | 276 | |||
673 | 277 | { | ||
674 | 278 | "exec": "helloHelper", | ||
675 | 279 | "app_id": "com.ubuntu.developer.ralsina.hello_hello" | ||
676 | 280 | } | ||
677 | 281 | |||
678 | 282 | .. note:: For deb packages, helpers should be installed into /usr/lib/ubuntu-push-client/legacy-helpers/ as part of the package. | ||
679 | 283 | |||
680 | 284 | Helper Output Format | ||
681 | 285 | -------------------- | ||
682 | 286 | |||
683 | 287 | Helpers output has two parts, the postal message (in the "message" key) and a notification to be presented to the user (in the "notification" key). | ||
684 | 288 | |||
685 | 289 | Here's a simple example:: | ||
686 | 290 | |||
687 | 291 | { | ||
688 | 292 | "message": "foobar", | ||
689 | 293 | "notification": { | ||
690 | 294 | "tag": "foo", | ||
691 | 295 | "card": { | ||
692 | 296 | "summary": "yes", | ||
693 | 297 | "body": "hello", | ||
694 | 298 | "popup": true, | ||
695 | 299 | "persist": true | ||
696 | 300 | } | ||
697 | 301 | "sound": "buzz.mp3", | ||
698 | 302 | "vibrate": { | ||
699 | 303 | "pattern": [200, 100], | ||
700 | 304 | "duration": 200, | ||
701 | 305 | "repeat": 2 | ||
702 | 306 | } | ||
703 | 307 | "emblem-counter": { | ||
704 | 308 | "count": 12, | ||
705 | 309 | "visible": true | ||
706 | 310 | } | ||
707 | 311 | } | ||
708 | 312 | } | ||
709 | 313 | |||
710 | 314 | The notification can contain a **tag** field, which can later be used by the `persistent notification management API. <#persistent-notification-management>`__ | ||
711 | 315 | |||
712 | 316 | :message: (optional) A JSON object that is passed as-is to the application via PopAll. | ||
713 | 317 | :notification: (optional) Describes the user-facing notifications triggered by this push message. | ||
714 | 318 | |||
715 | 319 | The notification can contain a **card**. A card describes a specific notification to be given to the user, | ||
716 | 320 | and has the following fields: | ||
717 | 321 | |||
718 | 322 | :summary: (required) a title. The card will not be presented if this is missing. | ||
719 | 323 | :body: longer text, defaults to empty. | ||
720 | 324 | :actions: If empty (the default), a bubble notification is non-clickable. | ||
721 | 325 | If you add a URL, then bubble notifications are clickable and launch that URL. One use for this is using a URL like | ||
722 | 326 | ``appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version`` which will switch to the app or launch | ||
723 | 327 | it if it's not running. See `URLDispatcher <https://wiki.ubuntu.com/URLDispatcher>`__ for more information. | ||
724 | 328 | |||
725 | 329 | :icon: An icon relating to the event being notified. Defaults to empty (no icon); | ||
726 | 330 | a secondary icon relating to the application will be shown as well, regardless of this field. | ||
727 | 331 | :timestamp: Seconds since the unix epoch, only used for persist (for now) | ||
728 | 332 | :persist: Whether to show in notification centre; defaults to false | ||
729 | 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. | ||
730 | 334 | |||
731 | 335 | .. note:: Keep in mind that the precise way in which each field is presented to the user depends on factors such as | ||
732 | 336 | whether it's shown as a bubble or in the notification centre, or even the version of Ubuntu Touch the user | ||
733 | 337 | has on their device. | ||
734 | 338 | |||
735 | 339 | The 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. | ||
736 | 340 | Defaults to empty (no sound). This is a relative path, and will be looked up in (a) the application's .local/share/<pkgname>, and (b) | ||
737 | 341 | standard xdg dirs. | ||
738 | 342 | |||
739 | 343 | The notification can contain a **vibrate** field, causing haptic feedback, that has the following content: | ||
740 | 344 | |||
741 | 345 | :pattern: a list of integers describing a vibration pattern. | ||
742 | 346 | :duration: duration in milliseconds. Is equivalent to setting pattern to [duration], and overrides pattern. | ||
743 | 347 | :repeat: number of times the pattern has to be repeated (defaults to 1, 0 is the same as 1). | ||
744 | 348 | |||
745 | 349 | The notification can contain a **emblem-counter** field, with the following content: | ||
746 | 350 | |||
747 | 351 | :count: a number to be displayed over the application's icon in the launcher. | ||
748 | 352 | :visible: set to true to show the counter, or false to hide it. | ||
749 | 353 | |||
750 | 354 | .. note:: Unlike other notifications, emblem-counter needs to be cleaned by the app itself. | ||
751 | 355 | Please see `the persistent notification management section. <#persistent-notification-management>`__ | ||
752 | 356 | |||
753 | 357 | .. FIXME crosslink to hello example app on each method | ||
754 | 358 | |||
755 | 359 | Security | ||
756 | 360 | ~~~~~~~~ | ||
757 | 361 | |||
758 | 362 | To use the push API, applications need to request permission in their security profile, using something like this:: | ||
759 | 363 | |||
760 | 364 | { | ||
761 | 365 | "policy_groups": [ | ||
762 | 366 | "networking", | ||
763 | 367 | "push-notification-client" | ||
764 | 368 | ], | ||
765 | 369 | "policy_version": 1.2 | ||
766 | 370 | } | ||
767 | 371 | |||
768 | 372 | |||
769 | 373 | Ubuntu Push Server API | ||
770 | 374 | ---------------------- | ||
771 | 375 | |||
772 | 376 | The Ubuntu Push server is located at https://push.ubuntu.com and has a single endpoint: ``/notify``. | ||
773 | 377 | To notify a user, your application has to do a POST with ``Content-type: application/json``. | ||
774 | 378 | |||
775 | 379 | Here is an example of the POST body using all the fields:: | ||
776 | 380 | |||
777 | 381 | { | ||
778 | 382 | "appid": "com.ubuntu.music_music", | ||
779 | 383 | "expire_on": "2014-10-08T14:48:00.000Z", | ||
780 | 384 | "token": "LeA4tRQG9hhEkuhngdouoA==", | ||
781 | 385 | "clear_pending": true, | ||
782 | 386 | "replace_tag": "tagname", | ||
783 | 387 | "data": { | ||
784 | 388 | "message": "foobar", | ||
785 | 389 | "notification": { | ||
786 | 390 | "card": { | ||
787 | 391 | "summary": "yes", | ||
788 | 392 | "body": "hello", | ||
789 | 393 | "popup": true, | ||
790 | 394 | "persist": true | ||
791 | 395 | } | ||
792 | 396 | "sound": "buzz.mp3", | ||
793 | 397 | "tag": "foo", | ||
794 | 398 | "vibrate": { | ||
795 | 399 | "duration": 200, | ||
796 | 400 | "pattern": (200, 100), | ||
797 | 401 | "repeat": 2 | ||
798 | 402 | } | ||
799 | 403 | "emblem-counter": { | ||
800 | 404 | "count": 12, | ||
801 | 405 | "visible": true | ||
802 | 406 | } | ||
803 | 407 | } | ||
804 | 408 | } | ||
805 | 409 | } | ||
806 | 410 | |||
807 | 411 | |||
808 | 412 | :appid: ID of the application that will receive the notification, as described in the client side documentation. | ||
809 | 413 | :expire_on: Expiration date/time for this message, in `ISO8601 Extendend format <http://en.wikipedia.org/wiki/ISO_8601>`__ | ||
810 | 414 | :token: The token identifying the user+device to which the message is directed, as described in the client side documentation. | ||
811 | 415 | :clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error. | ||
812 | 416 | :replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one. | ||
813 | 417 | :data: A JSON object. | ||
814 | 418 | |||
815 | 419 | In this example, data is `what a helper would output <#helper-output-format>`__ but that's not necessarily the case. | ||
816 | 420 | The content of the data field will be passed to the helper application which **has** to produce output in that format. | ||
817 | 0 | 421 | ||
818 | === added file 'docs/push.svg' | |||
819 | --- docs/push.svg 1970-01-01 00:00:00 +0000 | |||
820 | +++ docs/push.svg 2014-08-04 15:37:24 +0000 | |||
821 | @@ -0,0 +1,439 @@ | |||
822 | 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
823 | 2 | <svg | ||
824 | 3 | xmlns:dc="http://purl.org/dc/elements/1.1/" | ||
825 | 4 | xmlns:cc="http://creativecommons.org/ns#" | ||
826 | 5 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||
827 | 6 | xmlns:svg="http://www.w3.org/2000/svg" | ||
828 | 7 | xmlns="http://www.w3.org/2000/svg" | ||
829 | 8 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
830 | 9 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
831 | 10 | width="41cm" | ||
832 | 11 | height="41cm" | ||
833 | 12 | viewBox="381 56 811 811" | ||
834 | 13 | id="svg2" | ||
835 | 14 | version="1.1" | ||
836 | 15 | inkscape:version="0.48.4 r9939" | ||
837 | 16 | sodipodi:docname="push.svg"> | ||
838 | 17 | <metadata | ||
839 | 18 | id="metadata88"> | ||
840 | 19 | <rdf:RDF> | ||
841 | 20 | <cc:Work | ||
842 | 21 | rdf:about=""> | ||
843 | 22 | <dc:format>image/svg+xml</dc:format> | ||
844 | 23 | <dc:type | ||
845 | 24 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||
846 | 25 | </cc:Work> | ||
847 | 26 | </rdf:RDF> | ||
848 | 27 | </metadata> | ||
849 | 28 | <defs | ||
850 | 29 | id="defs86"> | ||
851 | 30 | <marker | ||
852 | 31 | inkscape:stockid="Arrow1Lstart" | ||
853 | 32 | orient="auto" | ||
854 | 33 | refY="0.0" | ||
855 | 34 | refX="0.0" | ||
856 | 35 | id="Arrow1Lstart" | ||
857 | 36 | style="overflow:visible"> | ||
858 | 37 | <path | ||
859 | 38 | id="path3984" | ||
860 | 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 " | ||
861 | 40 | style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt" | ||
862 | 41 | transform="scale(0.8) translate(12.5,0)" /> | ||
863 | 42 | </marker> | ||
864 | 43 | <marker | ||
865 | 44 | inkscape:stockid="Arrow1Lend" | ||
866 | 45 | orient="auto" | ||
867 | 46 | refY="0.0" | ||
868 | 47 | refX="0.0" | ||
869 | 48 | id="Arrow1Lend" | ||
870 | 49 | style="overflow:visible;"> | ||
871 | 50 | <path | ||
872 | 51 | id="path3987" | ||
873 | 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 " | ||
874 | 53 | style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;" | ||
875 | 54 | transform="scale(0.8) rotate(180) translate(12.5,0)" /> | ||
876 | 55 | </marker> | ||
877 | 56 | </defs> | ||
878 | 57 | <sodipodi:namedview | ||
879 | 58 | pagecolor="#ffffff" | ||
880 | 59 | bordercolor="#666666" | ||
881 | 60 | borderopacity="1" | ||
882 | 61 | objecttolerance="10" | ||
883 | 62 | gridtolerance="10" | ||
884 | 63 | guidetolerance="10" | ||
885 | 64 | inkscape:pageopacity="0" | ||
886 | 65 | inkscape:pageshadow="2" | ||
887 | 66 | inkscape:window-width="1855" | ||
888 | 67 | inkscape:window-height="1056" | ||
889 | 68 | id="namedview84" | ||
890 | 69 | showgrid="false" | ||
891 | 70 | showguides="true" | ||
892 | 71 | inkscape:guide-bbox="true" | ||
893 | 72 | inkscape:zoom="0.62854741" | ||
894 | 73 | inkscape:cx="311.03462" | ||
895 | 74 | inkscape:cy="472.71845" | ||
896 | 75 | inkscape:window-x="65" | ||
897 | 76 | inkscape:window-y="24" | ||
898 | 77 | inkscape:window-maximized="1" | ||
899 | 78 | inkscape:current-layer="svg2"> | ||
900 | 79 | <sodipodi:guide | ||
901 | 80 | orientation="0,1" | ||
902 | 81 | position="820.94047,984.81037" | ||
903 | 82 | id="guide4555" /> | ||
904 | 83 | </sodipodi:namedview> | ||
905 | 84 | <line | ||
906 | 85 | style="fill:none;fill-opacity:0;stroke-width:2;stroke-dasharray:4;stroke:#000000;stroke-opacity:0.3037037" | ||
907 | 86 | x1="786" | ||
908 | 87 | y1="57" | ||
909 | 88 | x2="786" | ||
910 | 89 | y2="865" | ||
911 | 90 | id="line10" /> | ||
912 | 91 | <line | ||
913 | 92 | style="fill:none;fill-opacity:0;stroke-width:2;stroke-dasharray:4;stroke:#000000;stroke-opacity:0.3037037" | ||
914 | 93 | x1="382" | ||
915 | 94 | y1="461" | ||
916 | 95 | x2="1190" | ||
917 | 96 | y2="461" | ||
918 | 97 | id="line12" /> | ||
919 | 98 | <g | ||
920 | 99 | id="g6336" | ||
921 | 100 | transform="translate(695.42763,-47.960526)"> | ||
922 | 101 | <rect | ||
923 | 102 | transform="matrix(0.55824934,0,0,0.55824934,381,56)" | ||
924 | 103 | y="124.296" | ||
925 | 104 | x="-641.16089" | ||
926 | 105 | height="87.503342" | ||
927 | 106 | width="240.23645" | ||
928 | 107 | id="rect6311" | ||
929 | 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" /> | ||
930 | 109 | <text | ||
931 | 110 | id="text14" | ||
932 | 111 | y="154.5843" | ||
933 | 112 | x="53.172604" | ||
934 | 113 | style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif" | ||
935 | 114 | font-size="12.7998"> | ||
936 | 115 | <tspan | ||
937 | 116 | id="tspan16" | ||
938 | 117 | y="154.5843" | ||
939 | 118 | x="53.172604">Server Side</tspan> | ||
940 | 119 | </text> | ||
941 | 120 | </g> | ||
942 | 121 | <g | ||
943 | 122 | id="g6331" | ||
944 | 123 | transform="translate(679.44079,315.29605)"> | ||
945 | 124 | <rect | ||
946 | 125 | y="473.10199" | ||
947 | 126 | x="39.503288" | ||
948 | 127 | height="48.848682" | ||
949 | 128 | width="134.11185" | ||
950 | 129 | id="rect6311-7" | ||
951 | 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" /> | ||
952 | 131 | <text | ||
953 | 132 | id="text18" | ||
954 | 133 | y="502.29813" | ||
955 | 134 | x="72.128502" | ||
956 | 135 | style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif" | ||
957 | 136 | font-size="12.7998"> | ||
958 | 137 | <tspan | ||
959 | 138 | id="tspan20" | ||
960 | 139 | y="502.29813" | ||
961 | 140 | x="72.128502">Client Side</tspan> | ||
962 | 141 | </text> | ||
963 | 142 | </g> | ||
964 | 143 | <g | ||
965 | 144 | id="g30" | ||
966 | 145 | transform="translate(-13.351976,0)"> | ||
967 | 146 | <rect | ||
968 | 147 | style="fill:#ffffff" | ||
969 | 148 | x="467.5" | ||
970 | 149 | y="594" | ||
971 | 150 | width="238" | ||
972 | 151 | height="152" | ||
973 | 152 | id="rect32" /> | ||
974 | 153 | <rect | ||
975 | 154 | style="fill:none;stroke:#000000;stroke-width:2" | ||
976 | 155 | x="467.5" | ||
977 | 156 | y="594" | ||
978 | 157 | width="238" | ||
979 | 158 | height="152" | ||
980 | 159 | id="rect34" /> | ||
981 | 160 | </g> | ||
982 | 161 | <text | ||
983 | 162 | font-size="12.7998" | ||
984 | 163 | style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif" | ||
985 | 164 | x="535.41882" | ||
986 | 165 | y="674.77179" | ||
987 | 166 | id="text36"> | ||
988 | 167 | <tspan | ||
989 | 168 | x="535.41882" | ||
990 | 169 | y="674.77179" | ||
991 | 170 | id="tspan38">Push Client</tspan> | ||
992 | 171 | </text> | ||
993 | 172 | <g | ||
994 | 173 | id="g3102" | ||
995 | 174 | transform="translate(-26.16447,-47.960526)"> | ||
996 | 175 | <g | ||
997 | 176 | id="g40"> | ||
998 | 177 | <rect | ||
999 | 178 | id="rect42" | ||
1000 | 179 | height="84" | ||
1001 | 180 | width="205" | ||
1002 | 181 | y="725" | ||
1003 | 182 | x="909.5" | ||
1004 | 183 | style="fill:#ffffff" /> | ||
1005 | 184 | <rect | ||
1006 | 185 | id="rect44" | ||
1007 | 186 | height="84" | ||
1008 | 187 | width="205" | ||
1009 | 188 | y="725" | ||
1010 | 189 | x="909.5" | ||
1011 | 190 | style="fill:none;stroke:#000000;stroke-width:2" /> | ||
1012 | 191 | </g> | ||
1013 | 192 | <text | ||
1014 | 193 | id="text46" | ||
1015 | 194 | y="770.53119" | ||
1016 | 195 | x="972.96625" | ||
1017 | 196 | style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif" | ||
1018 | 197 | font-size="12.7998"> | ||
1019 | 198 | <tspan | ||
1020 | 199 | id="tspan48" | ||
1021 | 200 | y="770.53119" | ||
1022 | 201 | x="972.96625">Push Helper</tspan> | ||
1023 | 202 | </text> | ||
1024 | 203 | </g> | ||
1025 | 204 | <g | ||
1026 | 205 | id="g3972" | ||
1027 | 206 | transform="translate(-10.46581,-2.6644735)"> | ||
1028 | 207 | <g | ||
1029 | 208 | transform="translate(-15.098684,-4.4407892)" | ||
1030 | 209 | id="g3095"> | ||
1031 | 210 | <g | ||
1032 | 211 | id="g50"> | ||
1033 | 212 | <rect | ||
1034 | 213 | style="fill:#ffffff" | ||
1035 | 214 | x="908.90002" | ||
1036 | 215 | y="534.70001" | ||
1037 | 216 | width="205" | ||
1038 | 217 | height="84" | ||
1039 | 218 | id="rect52" /> | ||
1040 | 219 | <rect | ||
1041 | 220 | style="fill:none;stroke:#000000;stroke-width:2" | ||
1042 | 221 | x="908.90002" | ||
1043 | 222 | y="534.70001" | ||
1044 | 223 | width="205" | ||
1045 | 224 | height="84" | ||
1046 | 225 | id="rect54" /> | ||
1047 | 226 | </g> | ||
1048 | 227 | <text | ||
1049 | 228 | font-size="12.7998" | ||
1050 | 229 | style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif" | ||
1051 | 230 | x="976.13806" | ||
1052 | 231 | y="580.2312" | ||
1053 | 232 | id="text56"> | ||
1054 | 233 | <tspan | ||
1055 | 234 | x="976.13806" | ||
1056 | 235 | y="580.2312" | ||
1057 | 236 | id="tspan58">Application</tspan> | ||
1058 | 237 | </text> | ||
1059 | 238 | </g> | ||
1060 | 239 | </g> | ||
1061 | 240 | <text | ||
1062 | 241 | xml:space="preserve" | ||
1063 | 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" | ||
1064 | 243 | x="825.89801" | ||
1065 | 244 | y="214.20395" | ||
1066 | 245 | id="text4424" | ||
1067 | 246 | sodipodi:linespacing="125%"><tspan | ||
1068 | 247 | sodipodi:role="line" | ||
1069 | 248 | id="tspan4426" | ||
1070 | 249 | x="825.89801" | ||
1071 | 250 | y="214.20395">/notify</tspan></text> | ||
1072 | 251 | <g | ||
1073 | 252 | id="g4546" | ||
1074 | 253 | transform="translate(8.2993444,1.4703926)"> | ||
1075 | 254 | <g | ||
1076 | 255 | transform="translate(-21.65132,-445.13816)" | ||
1077 | 256 | id="g30-8-0"> | ||
1078 | 257 | <rect | ||
1079 | 258 | id="rect32-6-7" | ||
1080 | 259 | height="152" | ||
1081 | 260 | width="238" | ||
1082 | 261 | y="594" | ||
1083 | 262 | x="467.5" | ||
1084 | 263 | style="fill:#ffffff" /> | ||
1085 | 264 | <rect | ||
1086 | 265 | id="rect34-62-9" | ||
1087 | 266 | height="152" | ||
1088 | 267 | width="238" | ||
1089 | 268 | y="594" | ||
1090 | 269 | x="467.5" | ||
1091 | 270 | style="fill:none;stroke:#000000;stroke-width:2" /> | ||
1092 | 271 | </g> | ||
1093 | 272 | <text | ||
1094 | 273 | font-size="12.7998" | ||
1095 | 274 | style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif" | ||
1096 | 275 | x="528.01489" | ||
1097 | 276 | y="229.63364" | ||
1098 | 277 | id="text80-9"> | ||
1099 | 278 | <tspan | ||
1100 | 279 | x="528.01489" | ||
1101 | 280 | y="229.63364" | ||
1102 | 281 | id="tspan82-1">PushServer</tspan> | ||
1103 | 282 | </text> | ||
1104 | 283 | </g> | ||
1105 | 284 | <g | ||
1106 | 285 | id="g4994"> | ||
1107 | 286 | <g | ||
1108 | 287 | transform="translate(399.33553,-442.47368)" | ||
1109 | 288 | id="g30-8-0-2"> | ||
1110 | 289 | <rect | ||
1111 | 290 | id="rect32-6-7-0" | ||
1112 | 291 | height="152" | ||
1113 | 292 | width="238" | ||
1114 | 293 | y="594" | ||
1115 | 294 | x="467.5" | ||
1116 | 295 | style="fill:#ffffff" /> | ||
1117 | 296 | <rect | ||
1118 | 297 | id="rect34-62-9-0" | ||
1119 | 298 | height="152" | ||
1120 | 299 | width="238" | ||
1121 | 300 | y="594" | ||
1122 | 301 | x="467.5" | ||
1123 | 302 | style="fill:none;stroke:#000000;stroke-width:2" /> | ||
1124 | 303 | </g> | ||
1125 | 304 | <text | ||
1126 | 305 | font-size="12.7998" | ||
1127 | 306 | style="font-size:12.79979992px;font-style:normal;font-weight:normal;text-anchor:start;fill:#000000;font-family:sans-serif" | ||
1128 | 307 | x="950.25482" | ||
1129 | 308 | y="232.29811" | ||
1130 | 309 | id="text80-9-1"> | ||
1131 | 310 | <tspan | ||
1132 | 311 | x="950.25482" | ||
1133 | 312 | y="232.29811" | ||
1134 | 313 | id="tspan82-1-7">App Server</tspan> | ||
1135 | 314 | </text> | ||
1136 | 315 | </g> | ||
1137 | 316 | <path | ||
1138 | 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)" | ||
1139 | 318 | d="M 870.28411,306.64076 557.36389,305.73534" | ||
1140 | 319 | id="path5001" | ||
1141 | 320 | inkscape:connector-type="polyline" | ||
1142 | 321 | inkscape:connector-curvature="0" | ||
1143 | 322 | inkscape:connection-start="#g4994" | ||
1144 | 323 | inkscape:connection-start-point="d4" | ||
1145 | 324 | inkscape:connection-end="#g4546" | ||
1146 | 325 | inkscape:connection-end-point="d4" | ||
1147 | 326 | transform="matrix(0.55824934,0,0,0.55824934,381,56)" /> | ||
1148 | 327 | <path | ||
1149 | 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)" | ||
1150 | 329 | d="m 1083.4505,443.39743 0,401.37697" | ||
1151 | 330 | id="path5003" | ||
1152 | 331 | inkscape:connector-type="polyline" | ||
1153 | 332 | inkscape:connector-curvature="0" | ||
1154 | 333 | inkscape:connection-start="#g4994" | ||
1155 | 334 | inkscape:connection-start-point="d4" | ||
1156 | 335 | inkscape:connection-end="#g3972" | ||
1157 | 336 | inkscape:connection-end-point="d4" | ||
1158 | 337 | transform="matrix(0.55824934,0,0,0.55824934,381,56)" /> | ||
1159 | 338 | <path | ||
1160 | 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" | ||
1161 | 340 | d="M 899.8408,964.68112 557.36389,1048.0044" | ||
1162 | 341 | id="path5005" | ||
1163 | 342 | inkscape:connector-type="polyline" | ||
1164 | 343 | inkscape:connector-curvature="0" | ||
1165 | 344 | inkscape:connection-start="#g3972" | ||
1166 | 345 | inkscape:connection-start-point="d4" | ||
1167 | 346 | inkscape:connection-end="#g30" | ||
1168 | 347 | inkscape:connection-end-point="d4" | ||
1169 | 348 | transform="matrix(0.55824934,0,0,0.55824934,381,56)" /> | ||
1170 | 349 | <path | ||
1171 | 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)" | ||
1172 | 351 | d="m 344.19749,441.25844 0,522.46863" | ||
1173 | 352 | id="path5009" | ||
1174 | 353 | inkscape:connector-type="polyline" | ||
1175 | 354 | inkscape:connector-curvature="0" | ||
1176 | 355 | inkscape:connection-start="#g4546" | ||
1177 | 356 | inkscape:connection-start-point="d4" | ||
1178 | 357 | inkscape:connection-end="#g30" | ||
1179 | 358 | inkscape:connection-end-point="d4" | ||
1180 | 359 | transform="matrix(0.55824934,0,0,0.55824934,381,56)" /> | ||
1181 | 360 | <text | ||
1182 | 361 | xml:space="preserve" | ||
1183 | 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" | ||
1184 | 363 | x="825.62708" | ||
1185 | 364 | y="589.00653" | ||
1186 | 365 | id="text6709" | ||
1187 | 366 | sodipodi:linespacing="125%"><tspan | ||
1188 | 367 | sodipodi:role="line" | ||
1189 | 368 | id="tspan6711" | ||
1190 | 369 | x="825.62708" | ||
1191 | 370 | y="589.00653">PopAll</tspan></text> | ||
1192 | 371 | <text | ||
1193 | 372 | xml:space="preserve" | ||
1194 | 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" | ||
1195 | 374 | x="491.13156" | ||
1196 | 375 | y="412.44592" | ||
1197 | 376 | id="text6717" | ||
1198 | 377 | sodipodi:linespacing="125%"><tspan | ||
1199 | 378 | sodipodi:role="line" | ||
1200 | 379 | id="tspan6719" | ||
1201 | 380 | x="491.13156" | ||
1202 | 381 | y="412.44592">Push Notifications</tspan></text> | ||
1203 | 382 | <text | ||
1204 | 383 | xml:space="preserve" | ||
1205 | 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" | ||
1206 | 385 | x="1041.7894" | ||
1207 | 386 | y="410.93091" | ||
1208 | 387 | id="text6721" | ||
1209 | 388 | sodipodi:linespacing="125%"><tspan | ||
1210 | 389 | sodipodi:role="line" | ||
1211 | 390 | id="tspan6723" | ||
1212 | 391 | x="1041.7894" | ||
1213 | 392 | y="410.93091">App-specific</tspan></text> | ||
1214 | 393 | <text | ||
1215 | 394 | xml:space="preserve" | ||
1216 | 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" | ||
1217 | 396 | x="459.1579" | ||
1218 | 397 | y="104.07236" | ||
1219 | 398 | id="text3045" | ||
1220 | 399 | sodipodi:linespacing="125%"><tspan | ||
1221 | 400 | sodipodi:role="line" | ||
1222 | 401 | id="tspan3047" | ||
1223 | 402 | x="459.1579" | ||
1224 | 403 | y="104.07236">Ubuntu Push System</tspan></text> | ||
1225 | 404 | <text | ||
1226 | 405 | xml:space="preserve" | ||
1227 | 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" | ||
1228 | 407 | x="919.22369" | ||
1229 | 408 | y="101.4079" | ||
1230 | 409 | id="text3049" | ||
1231 | 410 | sodipodi:linespacing="125%"><tspan | ||
1232 | 411 | sodipodi:role="line" | ||
1233 | 412 | id="tspan3051" | ||
1234 | 413 | x="919.22369" | ||
1235 | 414 | y="101.4079">Application</tspan></text> | ||
1236 | 415 | <path | ||
1237 | 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)" | ||
1238 | 417 | d="m 556.83946,1145.6987 c 0,0 254.55518,138.4143 340.46755,62.0478" | ||
1239 | 418 | id="path3051" | ||
1240 | 419 | inkscape:connector-curvature="0" | ||
1241 | 420 | transform="matrix(0.55824934,0,0,0.55824934,381,56)" | ||
1242 | 421 | sodipodi:nodetypes="cc" /> | ||
1243 | 422 | <path | ||
1244 | 423 | sodipodi:nodetypes="cc" | ||
1245 | 424 | inkscape:connector-curvature="0" | ||
1246 | 425 | id="path3053" | ||
1247 | 426 | d="m 882.80916,712.85627 c 0,0 -142.10524,-77.26969 -190.06574,-34.63814" | ||
1248 | 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)" /> | ||
1249 | 428 | <text | ||
1250 | 429 | xml:space="preserve" | ||
1251 | 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" | ||
1252 | 431 | x="811.3623" | ||
1253 | 432 | y="717.78949" | ||
1254 | 433 | id="text4019" | ||
1255 | 434 | sodipodi:linespacing="125%"><tspan | ||
1256 | 435 | sodipodi:role="line" | ||
1257 | 436 | id="tspan4021" | ||
1258 | 437 | x="811.3623" | ||
1259 | 438 | y="717.78949">Call</tspan></text> | ||
1260 | 439 | </svg> | ||
1261 | 0 | 440 | ||
1262 | === modified file 'launch_helper/cual/cual.go' | |||
1263 | --- launch_helper/cual/cual.go 2014-07-18 20:45:21 +0000 | |||
1264 | +++ launch_helper/cual/cual.go 2014-08-04 15:37:24 +0000 | |||
1265 | @@ -31,6 +31,7 @@ | |||
1266 | 31 | "unsafe" | 31 | "unsafe" |
1267 | 32 | 32 | ||
1268 | 33 | "launchpad.net/ubuntu-push/click" | 33 | "launchpad.net/ubuntu-push/click" |
1269 | 34 | "launchpad.net/ubuntu-push/launch_helper/helper_finder" | ||
1270 | 34 | "launchpad.net/ubuntu-push/logger" | 35 | "launchpad.net/ubuntu-push/logger" |
1271 | 35 | ) | 36 | ) |
1272 | 36 | 37 | ||
1273 | @@ -77,7 +78,7 @@ | |||
1274 | 77 | } | 78 | } |
1275 | 78 | 79 | ||
1276 | 79 | func (hs *helperState) HelperInfo(app *click.AppId) (string, string) { | 80 | func (hs *helperState) HelperInfo(app *click.AppId) (string, string) { |
1278 | 80 | return app.Helper() | 81 | return helper_finder.Helper(app, hs.log) |
1279 | 81 | } | 82 | } |
1280 | 82 | 83 | ||
1281 | 83 | func (hs *helperState) Launch(appId, exec, f1, f2 string) (string, error) { | 84 | func (hs *helperState) Launch(appId, exec, f1, f2 string) (string, error) { |
1282 | 84 | 85 | ||
1283 | === added directory 'launch_helper/helper_finder' | |||
1284 | === added file 'launch_helper/helper_finder/helper_finder.go' | |||
1285 | --- launch_helper/helper_finder/helper_finder.go 1970-01-01 00:00:00 +0000 | |||
1286 | +++ launch_helper/helper_finder/helper_finder.go 2014-08-04 15:37:24 +0000 | |||
1287 | @@ -0,0 +1,107 @@ | |||
1288 | 1 | package helper_finder | ||
1289 | 2 | |||
1290 | 3 | import ( | ||
1291 | 4 | "encoding/json" | ||
1292 | 5 | "io/ioutil" | ||
1293 | 6 | "os" | ||
1294 | 7 | "path/filepath" | ||
1295 | 8 | "sync" | ||
1296 | 9 | "time" | ||
1297 | 10 | |||
1298 | 11 | "launchpad.net/go-xdg/v0" | ||
1299 | 12 | |||
1300 | 13 | "launchpad.net/ubuntu-push/click" | ||
1301 | 14 | "launchpad.net/ubuntu-push/logger" | ||
1302 | 15 | ) | ||
1303 | 16 | |||
1304 | 17 | type helperValue struct { | ||
1305 | 18 | HelperId string `json:"helper_id"` | ||
1306 | 19 | Exec string `json:"exec"` | ||
1307 | 20 | } | ||
1308 | 21 | |||
1309 | 22 | type hookFile struct { | ||
1310 | 23 | AppId string `json:"app_id"` | ||
1311 | 24 | Exec string `json:"exec"` | ||
1312 | 25 | } | ||
1313 | 26 | |||
1314 | 27 | var mapLock sync.Mutex | ||
1315 | 28 | var helpersInfo = make(map[string]helperValue) | ||
1316 | 29 | var helpersDataMtime time.Time | ||
1317 | 30 | |||
1318 | 31 | var helpersDataPath = filepath.Join(xdg.Data.Home(), "ubuntu-push-client", "helpers_data.json") | ||
1319 | 32 | var hookPath = filepath.Join(xdg.Data.Home(), "ubuntu-push-client", "helpers") | ||
1320 | 33 | var hookExt = ".json" | ||
1321 | 34 | |||
1322 | 35 | // helperFromHookfile figures out the app id and executable of the untrusted | ||
1323 | 36 | // helper for this app. | ||
1324 | 37 | func helperFromHookFile(app *click.AppId) (helperAppId string, helperExec string) { | ||
1325 | 38 | matches, err := filepath.Glob(filepath.Join(hookPath, app.Package+"_*"+hookExt)) | ||
1326 | 39 | if err != nil { | ||
1327 | 40 | return "", "" | ||
1328 | 41 | } | ||
1329 | 42 | var v hookFile | ||
1330 | 43 | for _, m := range matches { | ||
1331 | 44 | abs, err := filepath.EvalSymlinks(m) | ||
1332 | 45 | if err != nil { | ||
1333 | 46 | continue | ||
1334 | 47 | } | ||
1335 | 48 | data, err := ioutil.ReadFile(abs) | ||
1336 | 49 | if err != nil { | ||
1337 | 50 | continue | ||
1338 | 51 | } | ||
1339 | 52 | err = json.Unmarshal(data, &v) | ||
1340 | 53 | if err != nil { | ||
1341 | 54 | continue | ||
1342 | 55 | } | ||
1343 | 56 | if v.Exec != "" && (v.AppId == "" || v.AppId == app.Base()) { | ||
1344 | 57 | basename := filepath.Base(m) | ||
1345 | 58 | helperAppId = basename[:len(basename)-len(hookExt)] | ||
1346 | 59 | helperExec = filepath.Join(filepath.Dir(abs), v.Exec) | ||
1347 | 60 | return helperAppId, helperExec | ||
1348 | 61 | } | ||
1349 | 62 | } | ||
1350 | 63 | return "", "" | ||
1351 | 64 | } | ||
1352 | 65 | |||
1353 | 66 | // Helper figures out the id and executable of the untrusted | ||
1354 | 67 | // helper for this app. | ||
1355 | 68 | func Helper(app *click.AppId, log logger.Logger) (helperAppId string, helperExec string) { | ||
1356 | 69 | if !app.Click { | ||
1357 | 70 | return "", "" | ||
1358 | 71 | } | ||
1359 | 72 | fInfo, err := os.Stat(helpersDataPath) | ||
1360 | 73 | if err != nil { | ||
1361 | 74 | // cache file is missing, go via the slow route | ||
1362 | 75 | log.Infof("Cache file not found, falling back to .json file lookup") | ||
1363 | 76 | return helperFromHookFile(app) | ||
1364 | 77 | } | ||
1365 | 78 | // get the lock as the map can be changed while we read | ||
1366 | 79 | mapLock.Lock() | ||
1367 | 80 | defer mapLock.Unlock() | ||
1368 | 81 | if helpersInfo == nil || fInfo.ModTime().After(helpersDataMtime) { | ||
1369 | 82 | data, err := ioutil.ReadFile(helpersDataPath) | ||
1370 | 83 | if err != nil { | ||
1371 | 84 | return "", "" | ||
1372 | 85 | } | ||
1373 | 86 | err = json.Unmarshal(data, &helpersInfo) | ||
1374 | 87 | if err != nil { | ||
1375 | 88 | return "", "" | ||
1376 | 89 | } | ||
1377 | 90 | helpersDataMtime = fInfo.ModTime() | ||
1378 | 91 | } | ||
1379 | 92 | var info helperValue | ||
1380 | 93 | info, ok := helpersInfo[app.Base()] | ||
1381 | 94 | if !ok { | ||
1382 | 95 | // ok, appid wasn't there, try with the package | ||
1383 | 96 | info, ok = helpersInfo[app.Package] | ||
1384 | 97 | if !ok { | ||
1385 | 98 | return "", "" | ||
1386 | 99 | } | ||
1387 | 100 | } | ||
1388 | 101 | if info.Exec != "" { | ||
1389 | 102 | helperAppId = info.HelperId | ||
1390 | 103 | helperExec = info.Exec | ||
1391 | 104 | return helperAppId, helperExec | ||
1392 | 105 | } | ||
1393 | 106 | return "", "" | ||
1394 | 107 | } | ||
1395 | 0 | 108 | ||
1396 | === added file 'launch_helper/helper_finder/helper_finder_test.go' | |||
1397 | --- launch_helper/helper_finder/helper_finder_test.go 1970-01-01 00:00:00 +0000 | |||
1398 | +++ launch_helper/helper_finder/helper_finder_test.go 2014-08-04 15:37:24 +0000 | |||
1399 | @@ -0,0 +1,193 @@ | |||
1400 | 1 | package helper_finder | ||
1401 | 2 | |||
1402 | 3 | import ( | ||
1403 | 4 | "os" | ||
1404 | 5 | "path/filepath" | ||
1405 | 6 | "testing" | ||
1406 | 7 | "time" | ||
1407 | 8 | |||
1408 | 9 | . "launchpad.net/gocheck" | ||
1409 | 10 | helpers "launchpad.net/ubuntu-push/testing" | ||
1410 | 11 | |||
1411 | 12 | "launchpad.net/ubuntu-push/click" | ||
1412 | 13 | ) | ||
1413 | 14 | |||
1414 | 15 | type helperSuite struct { | ||
1415 | 16 | oldHookPath string | ||
1416 | 17 | symlinkPath string | ||
1417 | 18 | oldHelpersDataPath string | ||
1418 | 19 | log *helpers.TestLogger | ||
1419 | 20 | } | ||
1420 | 21 | |||
1421 | 22 | func TestHelperFinder(t *testing.T) { TestingT(t) } | ||
1422 | 23 | |||
1423 | 24 | var _ = Suite(&helperSuite{}) | ||
1424 | 25 | |||
1425 | 26 | func (s *helperSuite) SetUpTest(c *C) { | ||
1426 | 27 | s.oldHookPath = hookPath | ||
1427 | 28 | hookPath = c.MkDir() | ||
1428 | 29 | s.symlinkPath = c.MkDir() | ||
1429 | 30 | s.oldHelpersDataPath = helpersDataPath | ||
1430 | 31 | helpersDataPath = filepath.Join(c.MkDir(), "helpers_data.json") | ||
1431 | 32 | s.log = helpers.NewTestLogger(c, "debug") | ||
1432 | 33 | } | ||
1433 | 34 | |||
1434 | 35 | func (s *helperSuite) createHookfile(name string, content string) error { | ||
1435 | 36 | symlink := filepath.Join(hookPath, name) + ".json" | ||
1436 | 37 | filename := filepath.Join(s.symlinkPath, name) | ||
1437 | 38 | f, err := os.Create(filename) | ||
1438 | 39 | if err != nil { | ||
1439 | 40 | return err | ||
1440 | 41 | } | ||
1441 | 42 | _, err = f.WriteString(content) | ||
1442 | 43 | if err != nil { | ||
1443 | 44 | return err | ||
1444 | 45 | } | ||
1445 | 46 | err = os.Symlink(filename, symlink) | ||
1446 | 47 | if err != nil { | ||
1447 | 48 | return err | ||
1448 | 49 | } | ||
1449 | 50 | return nil | ||
1450 | 51 | } | ||
1451 | 52 | |||
1452 | 53 | func (s *helperSuite) createHelpersDatafile(content string) error { | ||
1453 | 54 | f, err := os.Create(helpersDataPath) | ||
1454 | 55 | if err != nil { | ||
1455 | 56 | return err | ||
1456 | 57 | } | ||
1457 | 58 | _, err = f.WriteString(content) | ||
1458 | 59 | if err != nil { | ||
1459 | 60 | return err | ||
1460 | 61 | } | ||
1461 | 62 | return nil | ||
1462 | 63 | } | ||
1463 | 64 | |||
1464 | 65 | func (s *helperSuite) TearDownTest(c *C) { | ||
1465 | 66 | hookPath = s.oldHookPath | ||
1466 | 67 | os.Remove(helpersDataPath) | ||
1467 | 68 | helpersDataPath = s.oldHelpersDataPath | ||
1468 | 69 | helpersDataMtime = time.Now().Add(-1 * time.Hour) | ||
1469 | 70 | helpersInfo = nil | ||
1470 | 71 | } | ||
1471 | 72 | |||
1472 | 73 | func (s *helperSuite) TestHelperBasic(c *C) { | ||
1473 | 74 | c.Assert(s.createHelpersDatafile(`{"com.example.test": {"helper_id": "com.example.test_test-helper_1", "exec": "tsthlpr"}}`), IsNil) | ||
1474 | 75 | app, err := click.ParseAppId("com.example.test_test-app_1") | ||
1475 | 76 | c.Assert(err, IsNil) | ||
1476 | 77 | hid, hex := Helper(app, s.log) | ||
1477 | 78 | c.Check(hid, Equals, "com.example.test_test-helper_1") | ||
1478 | 79 | c.Check(hex, Equals, "tsthlpr") | ||
1479 | 80 | } | ||
1480 | 81 | |||
1481 | 82 | func (s *helperSuite) TestHelperFindsSpecific(c *C) { | ||
1482 | 83 | fileContent := `{"com.example.test_test-other-app": {"exec": "aaaaaaa", "helper_id": "com.example.test_aaaa-helper_1"}, | ||
1483 | 84 | "com.example.test_test-app": {"exec": "tsthlpr", "helper_id": "com.example.test_test-helper_1"}}` | ||
1484 | 85 | c.Assert(s.createHelpersDatafile(fileContent), IsNil) | ||
1485 | 86 | |||
1486 | 87 | app, err := click.ParseAppId("com.example.test_test-app_1") | ||
1487 | 88 | c.Assert(err, IsNil) | ||
1488 | 89 | hid, hex := Helper(app, s.log) | ||
1489 | 90 | c.Check(hid, Equals, "com.example.test_test-helper_1") | ||
1490 | 91 | c.Check(hex, Equals, "tsthlpr") | ||
1491 | 92 | } | ||
1492 | 93 | |||
1493 | 94 | func (s *helperSuite) TestHelperCanFail(c *C) { | ||
1494 | 95 | fileContent := `{"com.example.test_test-other-app": {"exec": "aaaaaaa", "helper_id": "com.example.test_aaaa-helper_1"}}` | ||
1495 | 96 | c.Assert(s.createHelpersDatafile(fileContent), IsNil) | ||
1496 | 97 | app, err := click.ParseAppId("com.example.test_test-app_1") | ||
1497 | 98 | c.Assert(err, IsNil) | ||
1498 | 99 | hid, hex := Helper(app, s.log) | ||
1499 | 100 | c.Check(hid, Equals, "") | ||
1500 | 101 | c.Check(hex, Equals, "") | ||
1501 | 102 | } | ||
1502 | 103 | |||
1503 | 104 | func (s *helperSuite) TestHelperFailInvalidJson(c *C) { | ||
1504 | 105 | fileContent := `{invalid json"com.example.test_test-other-app": {"exec": "aaaaaaa", "helper_id": "com.example.test_aaaa-helper_1"}}` | ||
1505 | 106 | c.Assert(s.createHelpersDatafile(fileContent), IsNil) | ||
1506 | 107 | app, err := click.ParseAppId("com.example.test_test-app_1") | ||
1507 | 108 | c.Assert(err, IsNil) | ||
1508 | 109 | hid, hex := Helper(app, s.log) | ||
1509 | 110 | c.Check(hid, Equals, "") | ||
1510 | 111 | c.Check(hex, Equals, "") | ||
1511 | 112 | } | ||
1512 | 113 | |||
1513 | 114 | func (s *helperSuite) TestHelperFailMissingExec(c *C) { | ||
1514 | 115 | fileContent := `{"com.example.test_test-app": {"helper_id": "com.example.test_aaaa-helper_1"}}` | ||
1515 | 116 | c.Assert(s.createHelpersDatafile(fileContent), IsNil) | ||
1516 | 117 | app, err := click.ParseAppId("com.example.test_test-app_1") | ||
1517 | 118 | c.Assert(err, IsNil) | ||
1518 | 119 | hid, hex := Helper(app, s.log) | ||
1519 | 120 | c.Check(hid, Equals, "") | ||
1520 | 121 | c.Check(hex, Equals, "") | ||
1521 | 122 | } | ||
1522 | 123 | |||
1523 | 124 | func (s *helperSuite) TestHelperlegacy(c *C) { | ||
1524 | 125 | appname := "ubuntu-system-settings" | ||
1525 | 126 | app, err := click.ParseAppId("_" + appname) | ||
1526 | 127 | c.Assert(err, IsNil) | ||
1527 | 128 | hid, hex := Helper(app, s.log) | ||
1528 | 129 | c.Check(hid, Equals, "") | ||
1529 | 130 | c.Check(hex, Equals, "") | ||
1530 | 131 | } | ||
1531 | 132 | |||
1532 | 133 | // Missing Cache file test | ||
1533 | 134 | |||
1534 | 135 | func (s *helperSuite) TestHelperMissingCacheFile(c *C) { | ||
1535 | 136 | c.Assert(s.createHookfile("com.example.test_test-helper_1", `{"exec": "tsthlpr"}`), IsNil) | ||
1536 | 137 | app, err := click.ParseAppId("com.example.test_test-app_1") | ||
1537 | 138 | c.Assert(err, IsNil) | ||
1538 | 139 | hid, hex := Helper(app, s.log) | ||
1539 | 140 | c.Check(hid, Equals, "com.example.test_test-helper_1") | ||
1540 | 141 | c.Check(hex, Equals, filepath.Join(s.symlinkPath, "tsthlpr")) | ||
1541 | 142 | c.Check(s.log.Captured(), Matches, ".*Cache file not found, falling back to .json file lookup\n") | ||
1542 | 143 | } | ||
1543 | 144 | |||
1544 | 145 | func (s *helperSuite) TestHelperFromHookBasic(c *C) { | ||
1545 | 146 | c.Assert(s.createHookfile("com.example.test_test-helper_1", `{"exec": "tsthlpr"}`), IsNil) | ||
1546 | 147 | app, err := click.ParseAppId("com.example.test_test-app_1") | ||
1547 | 148 | c.Assert(err, IsNil) | ||
1548 | 149 | hid, hex := Helper(app, s.log) | ||
1549 | 150 | c.Check(hid, Equals, "com.example.test_test-helper_1") | ||
1550 | 151 | c.Check(hex, Equals, filepath.Join(s.symlinkPath, "tsthlpr")) | ||
1551 | 152 | } | ||
1552 | 153 | |||
1553 | 154 | func (s *helperSuite) TestHelperFromHookFindsSpecific(c *C) { | ||
1554 | 155 | // Glob() sorts, so the first one will come first | ||
1555 | 156 | c.Assert(s.createHookfile("com.example.test_aaaa-helper_1", `{"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil) | ||
1556 | 157 | c.Assert(s.createHookfile("com.example.test_test-helper_1", `{"exec": "tsthlpr", "app_id": "com.example.test_test-app"}`), IsNil) | ||
1557 | 158 | app, err := click.ParseAppId("com.example.test_test-app_1") | ||
1558 | 159 | c.Assert(err, IsNil) | ||
1559 | 160 | hid, hex := Helper(app, s.log) | ||
1560 | 161 | c.Check(hid, Equals, "com.example.test_test-helper_1") | ||
1561 | 162 | c.Check(hex, Equals, filepath.Join(s.symlinkPath, "tsthlpr")) | ||
1562 | 163 | } | ||
1563 | 164 | |||
1564 | 165 | func (s *helperSuite) TestHelperFromHookCanFail(c *C) { | ||
1565 | 166 | c.Assert(s.createHookfile("com.example.test_aaaa-helper_1", `{"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil) | ||
1566 | 167 | app, err := click.ParseAppId("com.example.test_test-app_1") | ||
1567 | 168 | c.Assert(err, IsNil) | ||
1568 | 169 | hid, hex := Helper(app, s.log) | ||
1569 | 170 | c.Check(hid, Equals, "") | ||
1570 | 171 | c.Check(hex, Equals, "") | ||
1571 | 172 | } | ||
1572 | 173 | |||
1573 | 174 | func (s *helperSuite) TestHelperFromHookInvalidJson(c *C) { | ||
1574 | 175 | c.Assert(s.createHookfile("com.example.test_aaaa-helper_1", `invalid json {"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil) | ||
1575 | 176 | app, err := click.ParseAppId("com.example.test_test-app_1") | ||
1576 | 177 | c.Assert(err, IsNil) | ||
1577 | 178 | hid, hex := Helper(app, s.log) | ||
1578 | 179 | c.Check(hid, Equals, "") | ||
1579 | 180 | c.Check(hex, Equals, "") | ||
1580 | 181 | } | ||
1581 | 182 | |||
1582 | 183 | func (s *helperSuite) TestHelperFromHooFailBrokenSymlink(c *C) { | ||
1583 | 184 | name := "com.example.test_aaaa-helper_1" | ||
1584 | 185 | c.Assert(s.createHookfile(name, `{"exec": "aaaaaaa", "app_id": "com.example.test_test-other-app"}`), IsNil) | ||
1585 | 186 | filename := filepath.Join(s.symlinkPath, name) | ||
1586 | 187 | os.Remove(filename) | ||
1587 | 188 | app, err := click.ParseAppId("com.example.test_test-app_1") | ||
1588 | 189 | c.Assert(err, IsNil) | ||
1589 | 190 | hid, hex := Helper(app, s.log) | ||
1590 | 191 | c.Check(hid, Equals, "") | ||
1591 | 192 | c.Check(hex, Equals, "") | ||
1592 | 193 | } | ||
1593 | 0 | 194 | ||
1594 | === modified file 'launch_helper/kindpool_test.go' | |||
1595 | --- launch_helper/kindpool_test.go 2014-07-29 15:24:01 +0000 | |||
1596 | +++ launch_helper/kindpool_test.go 2014-08-04 15:37:24 +0000 | |||
1597 | @@ -542,31 +542,28 @@ | |||
1598 | 542 | } | 542 | } |
1599 | 543 | 543 | ||
1600 | 544 | func (s *poolSuite) TestBigBacklogShrinks(c *C) { | 544 | func (s *poolSuite) TestBigBacklogShrinks(c *C) { |
1601 | 545 | oldBufSz := InputBufferSize | ||
1602 | 546 | InputBufferSize = 0 | ||
1603 | 547 | defer func() { InputBufferSize = oldBufSz }() | ||
1604 | 545 | s.pool.(*kindHelperPool).maxNum = 1 | 548 | s.pool.(*kindHelperPool).maxNum = 1 |
1605 | 546 | ch := s.pool.Start() | 549 | ch := s.pool.Start() |
1606 | 547 | defer s.pool.Stop() | 550 | defer s.pool.Stop() |
1607 | 548 | numBad := 10 | ||
1608 | 549 | 551 | ||
1609 | 550 | app := clickhelp.MustParseAppId("com.example.test_test-app") | 552 | app := clickhelp.MustParseAppId("com.example.test_test-app") |
1610 | 551 | s.pool.Run("fake", &HelperInput{App: app, NotificationId: "0", Payload: []byte(`""`)}) | 553 | s.pool.Run("fake", &HelperInput{App: app, NotificationId: "0", Payload: []byte(`""`)}) |
1611 | 552 | |||
1612 | 553 | for i := 0; i < numBad; i++ { | ||
1613 | 554 | s.pool.Run("NOT-THERE", &HelperInput{App: app}) | ||
1614 | 555 | } | ||
1615 | 556 | s.pool.Run("fake", &HelperInput{App: app, NotificationId: "1", Payload: []byte(`""`)}) | 554 | s.pool.Run("fake", &HelperInput{App: app, NotificationId: "1", Payload: []byte(`""`)}) |
1616 | 557 | s.pool.Run("fake", &HelperInput{App: app, NotificationId: "2", Payload: []byte(`""`)}) | 555 | s.pool.Run("fake", &HelperInput{App: app, NotificationId: "2", Payload: []byte(`""`)}) |
1617 | 558 | s.waitForArgs(c, "Launch") | 556 | s.waitForArgs(c, "Launch") |
1618 | 559 | go s.fakeLauncher.done("0") | 557 | go s.fakeLauncher.done("0") |
1623 | 560 | // now we should get the fake + all the bad ones | 558 | takeNext(ch, c) |
1624 | 561 | for i := 0; i < numBad+1; i++ { | 559 | // so now there's one done, one "running", and one more waiting. |
1625 | 562 | takeNext(ch, c) | 560 | // kicking it forward one more notch before checking the logs: |
1622 | 563 | } | ||
1626 | 564 | s.waitForArgs(c, "Launch") | 561 | s.waitForArgs(c, "Launch") |
1627 | 565 | go s.fakeLauncher.done("1") | 562 | go s.fakeLauncher.done("1") |
1628 | 566 | takeNext(ch, c) | 563 | takeNext(ch, c) |
1630 | 567 | // so now there's one good one "running", and one more waiting. | 564 | // (two done, one "running") |
1631 | 568 | c.Check(s.log.Captured(), Matches, `(?ms).* shrunk to 1 entries\.$`) | 565 | c.Check(s.log.Captured(), Matches, `(?ms).* shrunk to 1 entries\.$`) |
1633 | 569 | // and the shrinker shrunk | 566 | // and the backlog shrinker shrunk the backlog |
1634 | 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 .*`) |
1635 | 571 | } | 568 | } |
1636 | 572 | 569 | ||
1637 | 573 | 570 | ||
1638 | === added file 'scripts/click-hook' | |||
1639 | --- scripts/click-hook 1970-01-01 00:00:00 +0000 | |||
1640 | +++ scripts/click-hook 2014-08-04 15:37:24 +0000 | |||
1641 | @@ -0,0 +1,113 @@ | |||
1642 | 1 | #!/usr/bin/python3 | ||
1643 | 2 | """Collect helpers hook data into a single json file""" | ||
1644 | 3 | |||
1645 | 4 | import argparse | ||
1646 | 5 | import json | ||
1647 | 6 | import os | ||
1648 | 7 | import sys | ||
1649 | 8 | import time | ||
1650 | 9 | |||
1651 | 10 | import xdg.BaseDirectory | ||
1652 | 11 | |||
1653 | 12 | from gi.repository import GLib | ||
1654 | 13 | from gi.repository import Gio | ||
1655 | 14 | from gi.repository import Click | ||
1656 | 15 | |||
1657 | 16 | hook_ext = '.json' | ||
1658 | 17 | |||
1659 | 18 | |||
1660 | 19 | def tup2variant(tup): | ||
1661 | 20 | builder = GLib.VariantBuilder.new(GLib.VariantType.new("(ss)")) | ||
1662 | 21 | builder.add_value(GLib.Variant.new_string(tup[0])) | ||
1663 | 22 | builder.add_value(GLib.Variant.new_string(tup[1])) | ||
1664 | 23 | return builder.end() | ||
1665 | 24 | |||
1666 | 25 | |||
1667 | 26 | def cleanup_settings(): | ||
1668 | 27 | clickdb = Click.DB.new() | ||
1669 | 28 | clickdb.read() | ||
1670 | 29 | |||
1671 | 30 | pkgnames = [] | ||
1672 | 31 | for package in clickdb.get_packages(False): | ||
1673 | 32 | pkgnames.append(package.get_property('package')) | ||
1674 | 33 | |||
1675 | 34 | settings = Gio.Settings.new('com.ubuntu.notifications.hub') | ||
1676 | 35 | goodapps = GLib.VariantBuilder.new(GLib.VariantType.new("a(ss)")) | ||
1677 | 36 | |||
1678 | 37 | for appname in settings.get_value('blacklist').unpack(): | ||
1679 | 38 | if appname[0] in pkgnames: | ||
1680 | 39 | goodapps.add_value(tup2variant(appname)) | ||
1681 | 40 | elif (appname[0] == appname[1]): | ||
1682 | 41 | appinfo = Gio.DesktopAppInfo.new(appname[0] + ".desktop") | ||
1683 | 42 | if not appinfo is None: | ||
1684 | 43 | goodapps.add_value(tup2variant(appname)) | ||
1685 | 44 | |||
1686 | 45 | settings.set_value('blacklist', goodapps.end()) | ||
1687 | 46 | |||
1688 | 47 | |||
1689 | 48 | def collect_helpers(helpers_data_path, helpers_data_path_tmp, hooks_path): | ||
1690 | 49 | helpers_data = {} | ||
1691 | 50 | for hook_fname in os.listdir(hooks_path): | ||
1692 | 51 | if not hook_fname.endswith(hook_ext): | ||
1693 | 52 | continue | ||
1694 | 53 | try: | ||
1695 | 54 | with open(os.path.join(hooks_path, hook_fname), 'r') as fd: | ||
1696 | 55 | data = json.load(fd) | ||
1697 | 56 | except Exception: | ||
1698 | 57 | continue | ||
1699 | 58 | else: | ||
1700 | 59 | helper_id = os.path.splitext(hook_fname)[0] | ||
1701 | 60 | exec_path = data['exec'] | ||
1702 | 61 | if exec_path != "": | ||
1703 | 62 | realpath = os.path.realpath(os.path.join(hooks_path, | ||
1704 | 63 | hook_fname)) | ||
1705 | 64 | exec_path = os.path.join(os.path.dirname(realpath), exec_path) | ||
1706 | 65 | app_id = data.get('app_id', None) | ||
1707 | 66 | if app_id is None: | ||
1708 | 67 | # no app_id, use the package name from the helper_id | ||
1709 | 68 | app_id = helper_id.split('_')[0] | ||
1710 | 69 | elif app_id.count('_') >= 3: | ||
1711 | 70 | # remove the version from the app_id | ||
1712 | 71 | app_id = app_id.rsplit('_', 1)[0] | ||
1713 | 72 | helpers_data[app_id] = {'exec': exec_path, 'helper_id': helper_id} | ||
1714 | 73 | |||
1715 | 74 | # write the collected data to a temp file and rename the original once | ||
1716 | 75 | # everything is on disk | ||
1717 | 76 | try: | ||
1718 | 77 | tmp_filename = helpers_data_path_tmp % (time.time(),) | ||
1719 | 78 | with open(tmp_filename, 'w') as dest: | ||
1720 | 79 | json.dump(helpers_data, dest) | ||
1721 | 80 | dest.flush() | ||
1722 | 81 | os.rename(tmp_filename, helpers_data_path) | ||
1723 | 82 | except Exception: | ||
1724 | 83 | return True | ||
1725 | 84 | return False | ||
1726 | 85 | |||
1727 | 86 | |||
1728 | 87 | def main(helpers_data_path=None, helpers_data_path_tmp=None, hooks_path=None): | ||
1729 | 88 | collect_fail = collect_helpers(helpers_data_path, helpers_data_path_tmp, | ||
1730 | 89 | hooks_path) | ||
1731 | 90 | clean_settings_fail = False | ||
1732 | 91 | try: | ||
1733 | 92 | cleanup_settings() | ||
1734 | 93 | except Exception: | ||
1735 | 94 | clean_settings_fail = True | ||
1736 | 95 | return int(collect_fail or clean_settings_fail) | ||
1737 | 96 | |||
1738 | 97 | |||
1739 | 98 | if __name__ == "__main__": | ||
1740 | 99 | xdg_data_home = xdg.BaseDirectory.xdg_data_home | ||
1741 | 100 | parser = argparse.ArgumentParser(description=__doc__) | ||
1742 | 101 | parser.add_argument('-d', '--data-home', | ||
1743 | 102 | help='The Path to the (xdg) data home', | ||
1744 | 103 | default=xdg_data_home) | ||
1745 | 104 | args = parser.parse_args() | ||
1746 | 105 | xdg_data_home = args.data_home | ||
1747 | 106 | helpers_data_path = os.path.join(xdg_data_home, 'ubuntu-push-client', | ||
1748 | 107 | 'helpers_data.json') | ||
1749 | 108 | helpers_data_path_tmp = os.path.join(xdg_data_home, 'ubuntu-push-client', | ||
1750 | 109 | '.helpers_data_%s.tmp') | ||
1751 | 110 | hooks_path = os.path.join(xdg_data_home, 'ubuntu-push-client', 'helpers') | ||
1752 | 111 | sys.exit(main(helpers_data_path=helpers_data_path, | ||
1753 | 112 | helpers_data_path_tmp=helpers_data_path_tmp, | ||
1754 | 113 | hooks_path=hooks_path)) |