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