Merge lp:~ted/ubuntu-app-launch/click-hook into lp:ubuntu-app-launch/13.10

Proposed by Ted Gould
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
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: mp+179537@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
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_HOME/applications/app_id.desktop files for each app found in $XDG_CACHE_HOME/upstart-app-launch/desktop/. This generated .desktop 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..."

* in build_desktop_file(), possible call of g_strstr_len(output) when output==NULL. Need to test the error before stripping the newline

* There is a bug that overwrites a user's .desktop file with a generated ones. remove_desktop_file() includes a test to avoid unlinking files that desktop-hook didn't create, but main()'s merge code, calls build_desktop_file() on top of it anyway. IMO it would be better to extract out remove_desktop_file's you-didn't-build-that test s.t. it could be like this:

  if (state->has_desktop
    && desktop_was_generated_by_us (state, desktopdir)
    && (!state->has_click || (state->click_created > state->desktop_created)))
  {
    if (remove_desktop_file (state, desktopdir))
      {
        state->has_desktop = FALSE;
        state->desktop_created = 0;
      }
  }

  if (state->has_click && !state->has_desktop)
  {
    if (!desktopdirexists)
      {
        // mkdir code goes here...
      }

    if (desktopdirexists)
      {
        g_debug("\tBuilding desktop file");
        build_desktop_file(state, symlinkdir, desktopdir);
      }
    }
  }

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.

review: Needs Fixing
Revision history for this message
Charles Kerr (charlesk) wrote :

Ted, the only TODO items I found for you in these three MPs {click-hook, click-exec, libupstart-app-launch} are the ones listed above and one nice-to-have and click-exec, but I'm leaving all three of them non-top-approved in case you want to sequence their landing instead of leaving it up to the automerge bot. I'm fine with you top-approving after the TODO list is cleared.

Revision history for this message
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_HOME/applications/app_id.desktop files for each app found in
> $XDG_CACHE_HOME/upstart-app-launch/desktop/. This generated .desktop
> 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_file(), possible call of g_strstr_len(output) when
> 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_desktop_file() includes a test to avoid
> unlinking files that desktop-hook didn't create, but main()'s merge
> code, calls build_desktop_file() on top of it anyway. IMO it would be
> better to extract out remove_desktop_file's you-didn't-build-that test
> 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_desktop_file() return a
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

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~ted/ubuntu-app-launch/click-hook updated
74. By Ted Gould

Merging trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches