Merge lp:~ted/ubuntu-app-launch/click-hook into lp:ubuntu-app-launch/13.10
- click-hook
- Merge into trunk.13.10
Status: | Merged |
---|---|
Approved by: | Ted Gould |
Approved revision: | 74 |
Merged at revision: | 36 |
Proposed branch: | lp:~ted/ubuntu-app-launch/click-hook |
Merge into: | lp:ubuntu-app-launch/13.10 |
Prerequisite: | lp:~ted/ubuntu-app-launch/click-deps |
Diff against target: |
799 lines (+685/-16) 10 files modified
Makefile (+15/-8) application-click.conf.in (+2/-2) application-legacy.conf.in (+3/-3) debian/control (+3/-1) debian/rules (+5/-1) desktop-hook.c (+406/-0) helpers.c (+216/-0) helpers.h (+30/-0) lsapp.c (+1/-1) upstart-app-launch-desktop.click-hook.in (+4/-0) |
To merge this branch: | bzr merge lp:~ted/ubuntu-app-launch/click-hook |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Approve | |
Ted Gould (community) | Approve | ||
Charles Kerr (community) | Needs Fixing | ||
Colin Watson | Pending | ||
Review via email:
|
Commit message
Add a click package hook to build desktop files
Description of the change
Adds a click packaging hook in it's current form. Likely to change overall but this is working on the proof-of-concept right now to make things mostly work.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Charles Kerr (charlesk) wrote : | # |
Needs Fixing in desktop-hook.c:
* A couple of paragraphs at the top of this file explaining its intent would be damn helpful. "This generates $XDG_DATA_
* in build_desktop_
* There is a bug that overwrites a user's .desktop file with a generated ones. remove_
if (state->has_desktop
&& desktop_
&& (!state->has_click || (state-
{
if (remove_
{
}
}
if (state->has_click && !state-
{
if (!desktopdirexists)
{
// mkdir code goes here...
}
if (desktopdirexists)
{
}
}
}
Needs Information:
* Would it be better to compare mtimes instead of creation times?
Comment Only:
* The app_state_t collection doesn't make sense as a GArray. It's a prime candidate for a GHashTable: it's keyed off a string, and find_app_entry()'s lookup algorithm is a currently linear search. The only win I see for the GArray implementation is the simplicity of cleanup (freeing the app_id in a loop, then calling one g_free() for the whole struct), but a GHashTable would be simpler than that, literally only one call.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Charles Kerr (charlesk) wrote : | # |
Ted, the only TODO items I found for you in these three MPs {click-hook, click-exec, libupstart-
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ted Gould (ted) wrote : | # |
On Sat, 2013-08-10 at 14:06 +0000, Charles Kerr wrote:
> * A couple of paragraphs at the top of this file explaining its intent
> would be damn helpful. "This generates
> $XDG_DATA_
> $XDG_CACHE_
> is based on the app's original .desktop (which is located by querying
> click for the app's manifest) but the Desktop Entry's Path and Exec
> are changed s.t. Path matches click's pkgdir for this app and the exec
> is wrapped in aa-exec. This app should be run when..."
Fixed r73.
> * in build_desktop_
> output==NULL. Need to test the error before stripping the newline
Fixed r70.
> * There is a bug that overwrites a user's .desktop file with a
> generated ones. remove_
> unlinking files that desktop-hook didn't create, but main()'s merge
> code, calls build_desktop_
> better to extract out remove_
> s.t. it could be like this:
Fixed in r71. I did it a little differently. I felt like checking
through things twice made the whole thing a little more confusing. I
like the "You'll do one of these three" type structure of the original
if statements. What I did was make remove_
boolean and only built the new one if it was able to delete it. It
fixes the bug you mentioned, let's talk more if you think the logic
needs to be reworked as well.
> * Would it be better to compare mtimes instead of creation times?
I can't come up with a case where it'd make a difference in normal
operation. But it seems like a developer working on their own machine
might find it handy, and it shouldn't hurt, so I changed it in r72.
> Comment Only:
>
> * The app_state_t collection doesn't make sense as a GArray. It's a
> prime candidate for a GHashTable: it's keyed off a string, and
> find_app_entry()'s lookup algorithm is a currently linear search. The
> only win I see for the GArray implementation is the simplicity of
> cleanup (freeing the app_id in a loop, then calling one g_free() for
> the whole struct), but a GHashTable would be simpler than that,
> literally only one call.
The reason that I like GArrays in this case is that I don't have
allocate per-instance for the individual items. The GArray makes the
slice and then just allocates them as we go along. Each new one is
allocated on the stack and then memcpy'd into the array. Definitely
could see both ways though. And if you did a HashTable with a slice
allocator you'd get the best of both worlds. Might be fun to benchmark
someday when we don't have deadlines :-)
status approved
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:74
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'Makefile' |
2 | --- Makefile 2013-07-04 03:20:53 +0000 |
3 | +++ Makefile 2013-08-11 19:54:50 +0000 |
4 | @@ -1,28 +1,35 @@ |
5 | -default: desktop-exec lsapp zg-report-app application.conf application-click.conf application-legacy.conf |
6 | +default: desktop-exec desktop-hook lsapp zg-report-app application.conf application-click.conf application-legacy.conf debian/upstart-app-launch-desktop.click-hook |
7 | @echo "Building" |
8 | |
9 | desktop-exec: desktop-exec.c |
10 | - gcc -o desktop-exec desktop-exec.c `pkg-config --cflags --libs glib-2.0 gio-2.0` |
11 | + gcc -o desktop-exec desktop-exec.c `pkg-config --cflags --libs glib-2.0 gio-2.0` -Wall -Werror |
12 | + |
13 | +desktop-hook: desktop-hook.c helpers.c helpers.h |
14 | + gcc -o desktop-hook desktop-hook.c helpers.c `pkg-config --cflags --libs glib-2.0 gio-2.0 json-glib-1.0` -Wall -Werror |
15 | |
16 | lsapp: lsapp.c |
17 | - gcc -o lsapp lsapp.c `pkg-config --cflags --libs gio-2.0` |
18 | + gcc -o lsapp lsapp.c `pkg-config --cflags --libs gio-2.0` -Wall -Werror |
19 | |
20 | zg-report-app: zg-report-app.c |
21 | - gcc -o zg-report-app zg-report-app.c `pkg-config --cflags --libs zeitgeist-1.0` |
22 | + gcc -o zg-report-app zg-report-app.c `pkg-config --cflags --libs zeitgeist-1.0` -Wall -Werror |
23 | |
24 | application-legacy.conf: application-legacy.conf.in |
25 | - sed -e "s|\@libexecdir\@|/usr/lib/$(DEB_BUILD_MULTIARCH)/upstart-app-launch/|" application-legacy.conf.in > application-legacy.conf |
26 | + sed -e "s|\@pkglibexecdir\@|/usr/lib/$(DEB_BUILD_MULTIARCH)/upstart-app-launch/|" application-legacy.conf.in > application-legacy.conf |
27 | |
28 | application-click.conf: application-click.conf.in |
29 | - sed -e "s|\@libexecdir\@|/usr/lib/$(DEB_BUILD_MULTIARCH)/upstart-app-launch/|" application-click.conf.in > application-click.conf |
30 | - |
31 | -install: application-legacy.conf application-legacy.conf desktop-exec |
32 | + sed -e "s|\@pkglibexecdir\@|/usr/lib/$(DEB_BUILD_MULTIARCH)/upstart-app-launch/|" application-click.conf.in > application-click.conf |
33 | + |
34 | +debian/upstart-app-launch-desktop.click-hook: upstart-app-launch-desktop.click-hook.in |
35 | + sed -e "s|\@pkglibexecdir\@|/usr/lib/$(DEB_BUILD_MULTIARCH)/upstart-app-launch/|" upstart-app-launch-desktop.click-hook.in > debian/upstart-app-launch-desktop.click-hook |
36 | + |
37 | +install: application-legacy.conf application-legacy.conf desktop-exec desktop-hook |
38 | mkdir -p $(DESTDIR)/usr/share/upstart/sessions |
39 | install -m 644 application.conf $(DESTDIR)/usr/share/upstart/sessions/ |
40 | install -m 644 application-legacy.conf $(DESTDIR)/usr/share/upstart/sessions/ |
41 | install -m 644 application-click.conf $(DESTDIR)/usr/share/upstart/sessions/ |
42 | mkdir -p $(DESTDIR)/usr/lib/$(DEB_BUILD_MULTIARCH)/upstart-app-launch/ |
43 | install -m 755 desktop-exec $(DESTDIR)/usr/lib/$(DEB_BUILD_MULTIARCH)/upstart-app-launch/ |
44 | + install -m 755 desktop-hook $(DESTDIR)/usr/lib/$(DEB_BUILD_MULTIARCH)/upstart-app-launch/ |
45 | install -m 755 zg-report-app $(DESTDIR)/usr/lib/$(DEB_BUILD_MULTIARCH)/upstart-app-launch/ |
46 | mkdir -p $(DESTDIR)/usr/bin/ |
47 | install -m 755 lsapp $(DESTDIR)/usr/bin/ |
48 | |
49 | === modified file 'application-click.conf.in' |
50 | --- application-click.conf.in 2013-06-28 19:56:40 +0000 |
51 | +++ application-click.conf.in 2013-08-11 19:54:50 +0000 |
52 | @@ -19,5 +19,5 @@ |
53 | fi |
54 | end script |
55 | |
56 | -post-start exec @libexecdir@/zg-report-app open application://${APP_ID}.desktop |
57 | -post-stop exec @libexecdir@/zg-report-app close application://${APP_ID}.desktop |
58 | +post-start exec @pkglibexecdir@/zg-report-app open application://${APP_ID}.desktop |
59 | +post-stop exec @pkglibexecdir@/zg-report-app close application://${APP_ID}.desktop |
60 | |
61 | === modified file 'application-legacy.conf.in' |
62 | --- application-legacy.conf.in 2013-07-10 16:01:23 +0000 |
63 | +++ application-legacy.conf.in 2013-08-11 19:54:50 +0000 |
64 | @@ -9,7 +9,7 @@ |
65 | env APP_EXEC="echo Error" |
66 | env APP_EXEC_POLICY |
67 | |
68 | -pre-start exec @libexecdir@/desktop-exec ${APP_ID} |
69 | +pre-start exec @pkglibexecdir@/desktop-exec ${APP_ID} |
70 | |
71 | script |
72 | if [ -z $APP_EXEC_POLICY ]; then |
73 | @@ -19,5 +19,5 @@ |
74 | fi |
75 | end script |
76 | |
77 | -post-start exec @libexecdir@/zg-report-app open application://${APP_ID}.desktop |
78 | -post-stop exec @libexecdir@/zg-report-app close application://${APP_ID}.desktop |
79 | +post-start exec @pkglibexecdir@/zg-report-app open application://${APP_ID}.desktop |
80 | +post-stop exec @pkglibexecdir@/zg-report-app close application://${APP_ID}.desktop |
81 | |
82 | === modified file 'debian/control' |
83 | --- debian/control 2013-07-12 02:57:37 +0000 |
84 | +++ debian/control 2013-08-11 19:54:50 +0000 |
85 | @@ -2,8 +2,10 @@ |
86 | Section: gnome |
87 | Priority: optional |
88 | Maintainer: Ted Gould <ted@ubuntu.com> |
89 | -Build-Depends: debhelper (>= 9), |
90 | +Build-Depends: click-dev (>= 0.2.2), |
91 | + debhelper (>= 9), |
92 | libglib2.0-dev, |
93 | + libjson-glib-dev, |
94 | dbus-x11, |
95 | libzeitgeist-dev, |
96 | Standards-Version: 3.9.4 |
97 | |
98 | === modified file 'debian/rules' |
99 | --- debian/rules 2013-06-18 20:02:55 +0000 |
100 | +++ debian/rules 2013-08-11 19:54:50 +0000 |
101 | @@ -5,4 +5,8 @@ |
102 | #export DH_VERBOSE=1 |
103 | |
104 | %: |
105 | - dh $@ |
106 | + dh $@ --with click |
107 | + |
108 | +override_dh_click: |
109 | + dh_click --name upstart-app-launch-desktop |
110 | + |
111 | |
112 | === added file 'desktop-hook.c' |
113 | --- desktop-hook.c 1970-01-01 00:00:00 +0000 |
114 | +++ desktop-hook.c 2013-08-11 19:54:50 +0000 |
115 | @@ -0,0 +1,406 @@ |
116 | +/* |
117 | + * Copyright 2013 Canonical Ltd. |
118 | + * |
119 | + * This program is free software: you can redistribute it and/or modify it |
120 | + * under the terms of the GNU General Public License version 3, as published |
121 | + * by the Free Software Foundation. |
122 | + * |
123 | + * This program is distributed in the hope that it will be useful, but |
124 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
125 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
126 | + * PURPOSE. See the GNU General Public License for more details. |
127 | + * |
128 | + * You should have received a copy of the GNU General Public License along |
129 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
130 | + * |
131 | + * Authors: |
132 | + * Ted Gould <ted.gould@canonical.com> |
133 | + */ |
134 | + |
135 | +/* |
136 | + |
137 | +INTRODUCTION: |
138 | + |
139 | +This is a hook for Click packages. You can find information on Click package hooks in |
140 | +the click documentation: |
141 | + |
142 | +https://click-package.readthedocs.org/en/latest/ |
143 | + |
144 | +Probably the biggest thing to understand for how this code works is that you need to |
145 | +understand that this hook is run after one, or many packages are installed. A set of |
146 | +symbolic links are made to the desktop files per-application (not per-package) in the |
147 | +directory specified in upstart-app-launcher-desktop.click-hook.in. Those desktop files |
148 | +give us the App ID of the packages that are installed and have applications needing |
149 | +desktop files in them. We then operate on each of them ensuring that they are synchronized |
150 | +with the desktop files in ~/.local/share/applications/. |
151 | + |
152 | +The desktop files that we're creating there ARE NOT used for execution by the |
153 | +upstart-app-launch Upstart jobs. They are there so that Unity can know which applications |
154 | +are installed for this user and they provide an Exec line to allow compatibility with |
155 | +desktop environments that are not using upstart-app-launch for launching applications. |
156 | +You should not modify them and expect any executing under Unity to change. |
157 | + |
158 | +*/ |
159 | + |
160 | +#include <gio/gio.h> |
161 | +#include <glib/gstdio.h> |
162 | +#include <string.h> |
163 | + |
164 | +#include "helpers.h" |
165 | + |
166 | +typedef struct _app_state_t app_state_t; |
167 | +struct _app_state_t { |
168 | + gchar * app_id; |
169 | + gboolean has_click; |
170 | + gboolean has_desktop; |
171 | + guint64 click_modified; |
172 | + guint64 desktop_modified; |
173 | +}; |
174 | + |
175 | +/* Find an entry in the app array */ |
176 | +app_state_t * |
177 | +find_app_entry (const gchar * name, GArray * app_array) |
178 | +{ |
179 | + int i; |
180 | + for (i = 0; i < app_array->len; i++) { |
181 | + app_state_t * state = &g_array_index(app_array, app_state_t, i); |
182 | + |
183 | + if (g_strcmp0(state->app_id, name) == 0) { |
184 | + return state; |
185 | + } |
186 | + } |
187 | + |
188 | + app_state_t newstate; |
189 | + newstate.has_click = FALSE; |
190 | + newstate.has_desktop = FALSE; |
191 | + newstate.click_modified = 0; |
192 | + newstate.desktop_modified = 0; |
193 | + newstate.app_id = g_strdup(name); |
194 | + |
195 | + g_array_append_val(app_array, newstate); |
196 | + |
197 | + /* Note: The pointer needs to be the entry in the array, not the |
198 | + one that we have on the stack. Criticaly important. */ |
199 | + app_state_t * statepntr = &g_array_index(app_array, app_state_t, app_array->len - 1); |
200 | + return statepntr; |
201 | +} |
202 | + |
203 | +/* Looks up the file creation time, which seems harder with GLib |
204 | + than it should be */ |
205 | +guint64 |
206 | +modified_time (const gchar * dir, const gchar * filename) |
207 | +{ |
208 | + gchar * path = g_build_filename(dir, filename, NULL); |
209 | + GFile * file = g_file_new_for_path(path); |
210 | + GFileInfo * info = g_file_query_info(file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL); |
211 | + |
212 | + guint64 time = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_TIME_MODIFIED); |
213 | + |
214 | + g_object_unref(info); |
215 | + g_object_unref(file); |
216 | + g_free(path); |
217 | + |
218 | + return time; |
219 | +} |
220 | + |
221 | +/* Look at an click package entry */ |
222 | +void |
223 | +add_click_package (const gchar * dir, const gchar * name, GArray * app_array) |
224 | +{ |
225 | + if (!g_str_has_suffix(name, ".desktop")) { |
226 | + return; |
227 | + } |
228 | + |
229 | + gchar * appid = g_strdup(name); |
230 | + g_strstr_len(appid, -1, ".desktop")[0] = '\0'; |
231 | + |
232 | + app_state_t * state = find_app_entry(appid, app_array); |
233 | + state->has_click = TRUE; |
234 | + state->click_modified = modified_time(dir, name); |
235 | + |
236 | + g_free(appid); |
237 | + |
238 | + return; |
239 | +} |
240 | + |
241 | +/* Look at an desktop file entry */ |
242 | +void |
243 | +add_desktop_file (const gchar * dir, const gchar * name, GArray * app_array) |
244 | +{ |
245 | + if (!g_str_has_suffix(name, ".desktop")) { |
246 | + return; |
247 | + } |
248 | + |
249 | + gchar * appid = g_strdup(name); |
250 | + g_strstr_len(appid, -1, ".desktop")[0] = '\0'; |
251 | + |
252 | + /* We only want valid APP IDs as desktop files */ |
253 | + if (!app_id_to_triplet(appid, NULL, NULL, NULL)) { |
254 | + g_free(appid); |
255 | + return; |
256 | + } |
257 | + |
258 | + app_state_t * state = find_app_entry(appid, app_array); |
259 | + state->has_desktop = TRUE; |
260 | + state->desktop_modified = modified_time(dir, name); |
261 | + |
262 | + g_free(appid); |
263 | + return; |
264 | +} |
265 | + |
266 | +/* Open a directory and look at all the entries */ |
267 | +void |
268 | +dir_for_each (const gchar * dirname, void(*func)(const gchar * dir, const gchar * name, GArray * app_array), GArray * app_array) |
269 | +{ |
270 | + GError * error = NULL; |
271 | + GDir * directory = g_dir_open(dirname, 0, &error); |
272 | + |
273 | + if (error != NULL) { |
274 | + g_warning("Unable to read directory '%s': %s", dirname, error->message); |
275 | + g_error_free(error); |
276 | + return; |
277 | + } |
278 | + |
279 | + const gchar * filename = NULL; |
280 | + while ((filename = g_dir_read_name(directory)) != NULL) { |
281 | + func(dirname, filename, app_array); |
282 | + } |
283 | + |
284 | + g_dir_close(directory); |
285 | + return; |
286 | +} |
287 | + |
288 | +/* Function to take the source Desktop file and build a new |
289 | + one with similar, but not the same data in it */ |
290 | +static void |
291 | +copy_desktop_file (const gchar * from, const gchar * to, const gchar * appdir, const gchar * app_id) |
292 | +{ |
293 | + GError * error = NULL; |
294 | + GKeyFile * keyfile = g_key_file_new(); |
295 | + g_key_file_load_from_file(keyfile, |
296 | + from, |
297 | + G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, |
298 | + &error); |
299 | + |
300 | + if (error != NULL) { |
301 | + g_warning("Unable to read the desktop file '%s' in the application directory: %s", from, error->message); |
302 | + g_error_free(error); |
303 | + g_key_file_unref(keyfile); |
304 | + return; |
305 | + } |
306 | + |
307 | + gchar * oldexec = desktop_to_exec(keyfile, from); |
308 | + if (oldexec == NULL) { |
309 | + g_key_file_unref(keyfile); |
310 | + return; |
311 | + } |
312 | + |
313 | + if (g_key_file_has_key(keyfile, "Desktop Entry", "Path", NULL)) { |
314 | + gchar * oldpath = g_key_file_get_string(keyfile, "Desktop Entry", "Path", NULL); |
315 | + g_debug("Desktop file '%s' has a Path set to '%s'. Setting as X-Ubuntu-Old-Path.", from, oldpath); |
316 | + |
317 | + g_key_file_set_string(keyfile, "Desktop Entry", "X-Ubuntu-Old-Path", oldpath); |
318 | + |
319 | + g_free(oldpath); |
320 | + } |
321 | + |
322 | + gchar * path = g_build_filename(appdir, NULL); |
323 | + g_key_file_set_string(keyfile, "Desktop Entry", "Path", path); |
324 | + g_free(path); |
325 | + |
326 | + gchar * newexec = g_strdup_printf("aa-exec -p %s -- %s", app_id, oldexec); |
327 | + g_key_file_set_string(keyfile, "Desktop Entry", "Exec", newexec); |
328 | + g_free(newexec); |
329 | + g_free(oldexec); |
330 | + |
331 | + g_key_file_set_string(keyfile, "Desktop Entry", "X-Ubuntu-Application-ID", app_id); |
332 | + |
333 | + gsize datalen = 0; |
334 | + gchar * data = g_key_file_to_data(keyfile, &datalen, &error); |
335 | + g_key_file_unref(keyfile); |
336 | + |
337 | + if (error != NULL) { |
338 | + g_warning("Unable serialize keyfile built from '%s': %s", from, error->message); |
339 | + g_error_free(error); |
340 | + return; |
341 | + } |
342 | + |
343 | + g_file_set_contents(to, data, datalen, &error); |
344 | + g_free(data); |
345 | + |
346 | + if (error != NULL) { |
347 | + g_warning("Unable to write out desktop file to '%s': %s", to, error->message); |
348 | + g_error_free(error); |
349 | + return; |
350 | + } |
351 | + |
352 | + return; |
353 | +} |
354 | + |
355 | +/* Build a desktop file in the user's home directory */ |
356 | +static void |
357 | +build_desktop_file (app_state_t * state, const gchar * symlinkdir, const gchar * desktopdir) |
358 | +{ |
359 | + GError * error = NULL; |
360 | + gchar * package = NULL; |
361 | + /* 'Parse' the App ID */ |
362 | + if (!app_id_to_triplet(state->app_id, &package, NULL, NULL)) { |
363 | + return; |
364 | + } |
365 | + |
366 | + /* Check click to find out where the files are */ |
367 | + gchar * cmdline = g_strdup_printf("click pkgdir \"%s\"", package); |
368 | + g_free(package); |
369 | + |
370 | + gchar * output = NULL; |
371 | + g_spawn_command_line_sync(cmdline, &output, NULL, NULL, &error); |
372 | + g_free(cmdline); |
373 | + |
374 | + /* If we have an extra newline, we can hide it. */ |
375 | + if (output != NULL) { |
376 | + gchar * newline = NULL; |
377 | + |
378 | + newline = g_strstr_len(output, -1, "\n"); |
379 | + |
380 | + if (newline != NULL) { |
381 | + newline[0] = '\0'; |
382 | + } |
383 | + } |
384 | + |
385 | + if (error != NULL) { |
386 | + g_warning("Unable to get the package directory from click: %s", error->message); |
387 | + g_error_free(error); |
388 | + g_free(output); /* Probably not set, but just in case */ |
389 | + return; |
390 | + } |
391 | + |
392 | + if (!g_file_test(output, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { |
393 | + g_warning("Dirctory returned by click '%s' couldn't be found", output); |
394 | + g_free(output); |
395 | + return; |
396 | + } |
397 | + |
398 | + gchar * indesktop = manifest_to_desktop(output, state->app_id); |
399 | + if (indesktop == NULL) { |
400 | + g_free(output); |
401 | + return; |
402 | + } |
403 | + |
404 | + /* Determine the desktop file name */ |
405 | + gchar * desktopfile = g_strdup_printf("%s.desktop", state->app_id); |
406 | + gchar * desktoppath = g_build_filename(desktopdir, desktopfile, NULL); |
407 | + g_free(desktopfile); |
408 | + |
409 | + copy_desktop_file(indesktop, desktoppath, output, state->app_id); |
410 | + |
411 | + g_free(desktoppath); |
412 | + g_free(indesktop); |
413 | + g_free(output); |
414 | + |
415 | + return; |
416 | +} |
417 | + |
418 | +/* Remove the desktop file from the user's home directory */ |
419 | +static gboolean |
420 | +remove_desktop_file (app_state_t * state, const gchar * desktopdir) |
421 | +{ |
422 | + gchar * desktopfile = g_strdup_printf("%s.desktop", state->app_id); |
423 | + gchar * desktoppath = g_build_filename(desktopdir, desktopfile, NULL); |
424 | + g_free(desktopfile); |
425 | + |
426 | + GKeyFile * keyfile = g_key_file_new(); |
427 | + g_key_file_load_from_file(keyfile, |
428 | + desktoppath, |
429 | + G_KEY_FILE_NONE, |
430 | + NULL); |
431 | + |
432 | + if (!g_key_file_has_key(keyfile, "Desktop Entry", "X-Ubuntu-Application-ID", NULL)) { |
433 | + g_debug("Desktop file '%s' is not one created by us.", desktoppath); |
434 | + g_key_file_unref(keyfile); |
435 | + g_free(desktoppath); |
436 | + return FALSE; |
437 | + } |
438 | + g_key_file_unref(keyfile); |
439 | + |
440 | + if (g_unlink(desktoppath) != 0) { |
441 | + g_warning("Unable to delete desktop file: %s", desktoppath); |
442 | + } |
443 | + |
444 | + g_free(desktoppath); |
445 | + |
446 | + return TRUE; |
447 | +} |
448 | + |
449 | +/* The main function */ |
450 | +int |
451 | +main (int argc, char * argv[]) |
452 | +{ |
453 | + if (argc != 1) { |
454 | + g_error("Shouldn't have arguments"); |
455 | + return 1; |
456 | + } |
457 | + |
458 | + GArray * apparray = g_array_new(FALSE, FALSE, sizeof(app_state_t)); |
459 | + |
460 | + /* Find all the symlinks of desktop files */ |
461 | + gchar * symlinkdir = g_build_filename(g_get_user_cache_dir(), "upstart-app-launch", "desktop", NULL); |
462 | + if (!g_file_test(symlinkdir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { |
463 | + g_warning("No installed click packages"); |
464 | + } else { |
465 | + dir_for_each(symlinkdir, add_click_package, apparray); |
466 | + } |
467 | + |
468 | + /* Find all the click desktop files */ |
469 | + gchar * desktopdir = g_build_filename(g_get_user_data_dir(), "applications", NULL); |
470 | + gboolean desktopdirexists = FALSE; |
471 | + if (!g_file_test(symlinkdir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { |
472 | + g_warning("No applications defined"); |
473 | + } else { |
474 | + dir_for_each(desktopdir, add_desktop_file, apparray); |
475 | + desktopdirexists = TRUE; |
476 | + } |
477 | + |
478 | + /* Process the merge */ |
479 | + int i; |
480 | + for (i = 0; i < apparray->len; i++) { |
481 | + app_state_t * state = &g_array_index(apparray, app_state_t, i); |
482 | + g_debug("Processing App ID: %s", state->app_id); |
483 | + |
484 | + if (state->has_click && state->has_desktop) { |
485 | + if (state->click_modified > state->desktop_modified) { |
486 | + g_debug("\tClick updated more recently"); |
487 | + g_debug("\tRemoving desktop file"); |
488 | + if (remove_desktop_file(state, desktopdir)) { |
489 | + g_debug("\tBuilding desktop file"); |
490 | + build_desktop_file(state, symlinkdir, desktopdir); |
491 | + } |
492 | + } else { |
493 | + g_debug("\tAlready synchronized"); |
494 | + } |
495 | + } else if (state->has_click) { |
496 | + if (!desktopdirexists) { |
497 | + if (g_mkdir_with_parents(desktopdir, 0755) == 0) { |
498 | + g_debug("\tCreated applications directory"); |
499 | + desktopdirexists = TRUE; |
500 | + } else { |
501 | + g_warning("\tUnable to create applications directory"); |
502 | + } |
503 | + } |
504 | + if (desktopdirexists) { |
505 | + g_debug("\tBuilding desktop file"); |
506 | + build_desktop_file(state, symlinkdir, desktopdir); |
507 | + } |
508 | + } else if (state->has_desktop) { |
509 | + g_debug("\tRemoving desktop file"); |
510 | + remove_desktop_file(state, desktopdir); |
511 | + } |
512 | + |
513 | + g_free(state->app_id); |
514 | + } |
515 | + |
516 | + g_array_free(apparray, TRUE); |
517 | + g_free(desktopdir); |
518 | + g_free(symlinkdir); |
519 | + |
520 | + return 0; |
521 | +} |
522 | |
523 | === added file 'helpers.c' |
524 | --- helpers.c 1970-01-01 00:00:00 +0000 |
525 | +++ helpers.c 2013-08-11 19:54:50 +0000 |
526 | @@ -0,0 +1,216 @@ |
527 | +/* |
528 | + * Copyright 2013 Canonical Ltd. |
529 | + * |
530 | + * This program is free software: you can redistribute it and/or modify it |
531 | + * under the terms of the GNU General Public License version 3, as published |
532 | + * by the Free Software Foundation. |
533 | + * |
534 | + * This program is distributed in the hope that it will be useful, but |
535 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
536 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
537 | + * PURPOSE. See the GNU General Public License for more details. |
538 | + * |
539 | + * You should have received a copy of the GNU General Public License along |
540 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
541 | + * |
542 | + * Authors: |
543 | + * Ted Gould <ted.gould@canonical.com> |
544 | + */ |
545 | + |
546 | +#include <json-glib/json-glib.h> |
547 | +#include "helpers.h" |
548 | + |
549 | +/* Take an app ID and validate it and then break it up |
550 | + and spit it out. These are newly allocated strings */ |
551 | +gboolean |
552 | +app_id_to_triplet (const gchar * app_id, gchar ** package, gchar ** application, gchar ** version) |
553 | +{ |
554 | + /* 'Parse' the App ID */ |
555 | + gchar ** app_id_segments = g_strsplit(app_id, "_", 4); |
556 | + if (g_strv_length(app_id_segments) != 3) { |
557 | + g_warning("Unable to parse Application ID: %s", app_id); |
558 | + g_strfreev(app_id_segments); |
559 | + return FALSE; |
560 | + } |
561 | + |
562 | + if (package != NULL) { |
563 | + *package = app_id_segments[0]; |
564 | + } else { |
565 | + g_free(app_id_segments[0]); |
566 | + } |
567 | + |
568 | + if (application != NULL) { |
569 | + *application = app_id_segments[1]; |
570 | + } else { |
571 | + g_free(app_id_segments[1]); |
572 | + } |
573 | + |
574 | + if (version != NULL) { |
575 | + *version = app_id_segments[2]; |
576 | + } else { |
577 | + g_free(app_id_segments[2]); |
578 | + } |
579 | + |
580 | + g_free(app_id_segments); |
581 | + return TRUE; |
582 | +} |
583 | + |
584 | +/* Take a manifest, parse it, find the application and |
585 | + and then return the path to the desktop file */ |
586 | +gchar * |
587 | +manifest_to_desktop (const gchar * app_dir, const gchar * app_id) |
588 | +{ |
589 | + gchar * package = NULL; |
590 | + gchar * application = NULL; |
591 | + gchar * version = NULL; |
592 | + JsonParser * parser = NULL; |
593 | + GError * error = NULL; |
594 | + gchar * desktoppath = NULL; |
595 | + |
596 | + if (!app_id_to_triplet(app_id, &package, &application, &version)) { |
597 | + return NULL; |
598 | + } |
599 | + |
600 | + gchar * manifestfile = g_strdup_printf("%s.manifest", package); |
601 | + gchar * manifestpath = g_build_filename(app_dir, ".click", "info", manifestfile, NULL); |
602 | + g_free(manifestfile); |
603 | + |
604 | + if (!g_file_test(manifestpath, G_FILE_TEST_EXISTS)) { |
605 | + g_warning("Unable to find manifest file: %s", manifestpath); |
606 | + goto manifest_out; |
607 | + } |
608 | + |
609 | + parser = json_parser_new(); |
610 | + |
611 | + json_parser_load_from_file(parser, manifestpath, &error); |
612 | + if (error != NULL) { |
613 | + g_warning("Unable to load manifest file '%s': %s", manifestpath, error->message); |
614 | + g_error_free(error); |
615 | + goto manifest_out; |
616 | + } |
617 | + |
618 | + JsonNode * root = json_parser_get_root(parser); |
619 | + if (json_node_get_node_type(root) != JSON_NODE_OBJECT) { |
620 | + g_warning("Manifest '%s' doesn't start with an object", manifestpath); |
621 | + goto manifest_out; |
622 | + } |
623 | + |
624 | + JsonObject * rootobj = json_node_get_object(root); |
625 | + if (!json_object_has_member(rootobj, "version")) { |
626 | + g_warning("Manifest '%s' doesn't have a version", manifestpath); |
627 | + goto manifest_out; |
628 | + } |
629 | + |
630 | + if (g_strcmp0(json_object_get_string_member(rootobj, "version"), version) != 0) { |
631 | + g_warning("Manifest '%s' version '%s' doesn't match AppID version '%s'", manifestpath, json_object_get_string_member(rootobj, "version"), version); |
632 | + goto manifest_out; |
633 | + } |
634 | + |
635 | + if (!json_object_has_member(rootobj, "hooks")) { |
636 | + g_warning("Manifest '%s' doesn't have an hooks section", manifestpath); |
637 | + goto manifest_out; |
638 | + } |
639 | + |
640 | + JsonObject * appsobj = json_object_get_object_member(rootobj, "hooks"); |
641 | + if (appsobj == NULL) { |
642 | + g_warning("Manifest '%s' has an hooks section that is not a JSON object", manifestpath); |
643 | + goto manifest_out; |
644 | + } |
645 | + |
646 | + if (!json_object_has_member(appsobj, application)) { |
647 | + g_warning("Manifest '%s' doesn't have the application '%s' defined", manifestpath, application); |
648 | + goto manifest_out; |
649 | + } |
650 | + |
651 | + JsonObject * appobj = json_object_get_object_member(appsobj, application); |
652 | + if (appobj == NULL) { |
653 | + g_warning("Manifest '%s' has a definition for application '%s' that is not an object", manifestpath, application); |
654 | + goto manifest_out; |
655 | + } |
656 | + |
657 | + gchar * filename = NULL; |
658 | + if (json_object_has_member(appobj, "desktop")) { |
659 | + filename = g_strdup(json_object_get_string_member(appobj, "desktop")); |
660 | + } else { |
661 | + filename = g_strdup_printf("%s.desktop", application); |
662 | + } |
663 | + |
664 | + desktoppath = g_build_filename(app_dir, filename, NULL); |
665 | + g_free(filename); |
666 | + |
667 | + if (!g_file_test(desktoppath, G_FILE_TEST_EXISTS)) { |
668 | + g_warning("Application desktop file '%s' doesn't exist", desktoppath); |
669 | + g_free(desktoppath); |
670 | + desktoppath = NULL; |
671 | + } |
672 | + |
673 | +manifest_out: |
674 | + g_clear_object(&parser); |
675 | + g_free(manifestpath); |
676 | + g_free(package); |
677 | + g_free(application); |
678 | + g_free(version); |
679 | + |
680 | + return desktoppath; |
681 | +} |
682 | + |
683 | +/* Take a desktop file, make sure that it makes sense and |
684 | + then return the exec line */ |
685 | +gchar * |
686 | +desktop_to_exec (GKeyFile * desktop_file, const gchar * from) |
687 | +{ |
688 | + GError * error = NULL; |
689 | + |
690 | + if (!g_key_file_has_group(desktop_file, "Desktop Entry")) { |
691 | + g_warning("Desktop file '%s' does not have a 'Desktop Entry' group", from); |
692 | + return NULL; |
693 | + } |
694 | + |
695 | + gchar * type = g_key_file_get_string(desktop_file, "Desktop Entry", "Type", &error); |
696 | + if (error != NULL) { |
697 | + g_warning("Desktop file '%s' unable to get type: %s", from, error->message); |
698 | + g_error_free(error); |
699 | + g_free(type); |
700 | + return NULL; |
701 | + } |
702 | + |
703 | + if (g_strcmp0(type, "Application") != 0) { |
704 | + g_warning("Desktop file '%s' has a type of '%s' instead of 'Application'", from, type); |
705 | + g_free(type); |
706 | + return NULL; |
707 | + } |
708 | + g_free(type); |
709 | + |
710 | + if (g_key_file_has_key(desktop_file, "Desktop Entry", "NoDisplay", NULL)) { |
711 | + gboolean nodisplay = g_key_file_get_boolean(desktop_file, "Desktop Entry", "NoDisplay", NULL); |
712 | + if (nodisplay) { |
713 | + g_warning("Desktop file '%s' is set to not display, not copying", from); |
714 | + return NULL; |
715 | + } |
716 | + } |
717 | + |
718 | + if (g_key_file_has_key(desktop_file, "Desktop Entry", "Hidden", NULL)) { |
719 | + gboolean hidden = g_key_file_get_boolean(desktop_file, "Desktop Entry", "Hidden", NULL); |
720 | + if (hidden) { |
721 | + g_warning("Desktop file '%s' is set to be hidden, not copying", from); |
722 | + return NULL; |
723 | + } |
724 | + } |
725 | + |
726 | + if (g_key_file_has_key(desktop_file, "Desktop Entry", "Terminal", NULL)) { |
727 | + gboolean terminal = g_key_file_get_boolean(desktop_file, "Desktop Entry", "Terminal", NULL); |
728 | + if (terminal) { |
729 | + g_warning("Desktop file '%s' is set to run in a terminal, not copying", from); |
730 | + return NULL; |
731 | + } |
732 | + } |
733 | + |
734 | + if (!g_key_file_has_key(desktop_file, "Desktop Entry", "Exec", NULL)) { |
735 | + g_warning("Desktop file '%s' has no 'Exec' key", from); |
736 | + return NULL; |
737 | + } |
738 | + |
739 | + gchar * exec = g_key_file_get_string(desktop_file, "Desktop Entry", "Exec", NULL); |
740 | + return exec; |
741 | +} |
742 | + |
743 | |
744 | === added file 'helpers.h' |
745 | --- helpers.h 1970-01-01 00:00:00 +0000 |
746 | +++ helpers.h 2013-08-11 19:54:50 +0000 |
747 | @@ -0,0 +1,30 @@ |
748 | +/* |
749 | + * Copyright 2013 Canonical Ltd. |
750 | + * |
751 | + * This program is free software: you can redistribute it and/or modify it |
752 | + * under the terms of the GNU General Public License version 3, as published |
753 | + * by the Free Software Foundation. |
754 | + * |
755 | + * This program is distributed in the hope that it will be useful, but |
756 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
757 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
758 | + * PURPOSE. See the GNU General Public License for more details. |
759 | + * |
760 | + * You should have received a copy of the GNU General Public License along |
761 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
762 | + * |
763 | + * Authors: |
764 | + * Ted Gould <ted.gould@canonical.com> |
765 | + */ |
766 | + |
767 | +#include <glib.h> |
768 | + |
769 | +gboolean app_id_to_triplet (const gchar * app_id, |
770 | + gchar ** package, |
771 | + gchar ** application, |
772 | + gchar ** version); |
773 | +gchar * manifest_to_desktop (const gchar * app_dir, |
774 | + const gchar * app_id); |
775 | +gchar * desktop_to_exec (GKeyFile * desktop_file, |
776 | + const gchar * from); |
777 | + |
778 | |
779 | === modified file 'lsapp.c' |
780 | --- lsapp.c 2013-06-18 20:03:21 +0000 |
781 | +++ lsapp.c 2013-08-11 19:54:50 +0000 |
782 | @@ -57,7 +57,7 @@ |
783 | if (error != NULL) { |
784 | g_error("Unable to list instances: %s", error->message); |
785 | g_error_free(error); |
786 | - return; |
787 | + return 1; |
788 | } |
789 | |
790 | /* Header */ |
791 | |
792 | === added file 'upstart-app-launch-desktop.click-hook.in' |
793 | --- upstart-app-launch-desktop.click-hook.in 1970-01-01 00:00:00 +0000 |
794 | +++ upstart-app-launch-desktop.click-hook.in 2013-08-11 19:54:50 +0000 |
795 | @@ -0,0 +1,4 @@ |
796 | +Pattern: ${home}/.cache/upstart-app-launch/desktop/${id}.desktop |
797 | +Exec: @pkglibexecdir@/desktop-hook ${id} |
798 | +User-Level: yes |
799 | +Hook-Name: desktop |
PASSED: Continuous integration, rev:69 jenkins. qa.ubuntu. com/job/ upstart- app-launch- ci/51/ jenkins. qa.ubuntu. com/job/ upstart- app-launch- saucy-amd64- ci/53 jenkins. qa.ubuntu. com/job/ upstart- app-launch- saucy-armhf- ci/52 jenkins. qa.ubuntu. com/job/ upstart- app-launch- saucy-i386- ci/51
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ upstart- app-launch- ci/51/rebuild
http://