~ted/ubuntu-app-launch/pids-tools

« back to all changes in this revision

Viewing changes to desktop-hook.c

  • Committer: Tarmac
  • Author(s): Ted Gould
  • Date: 2013-08-11 20:23:52 UTC
  • mfrom: (33.2.41 click-hook)
  • Revision ID: tarmac-20130811202352-4gw8h4rod1yhdt0n
Add a click package hook to build desktop files.

Approved by PS Jenkins bot, Ted Gould.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright 2013 Canonical Ltd.
 
3
 *
 
4
 * This program is free software: you can redistribute it and/or modify it
 
5
 * under the terms of the GNU General Public License version 3, as published
 
6
 * by the Free Software Foundation.
 
7
 *
 
8
 * This program is distributed in the hope that it will be useful, but
 
9
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 
10
 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 
11
 * PURPOSE.  See the GNU General Public License for more details.
 
12
 *
 
13
 * You should have received a copy of the GNU General Public License along
 
14
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 
15
 *
 
16
 * Authors:
 
17
 *     Ted Gould <ted.gould@canonical.com>
 
18
 */
 
19
 
 
20
/*
 
21
 
 
22
INTRODUCTION:
 
23
 
 
24
This is a hook for Click packages.  You can find information on Click package hooks in
 
25
the click documentation:
 
26
 
 
27
https://click-package.readthedocs.org/en/latest/
 
28
 
 
29
Probably the biggest thing to understand for how this code works is that you need to
 
30
understand that this hook is run after one, or many packages are installed.  A set of
 
31
symbolic links are made to the desktop files per-application (not per-package) in the
 
32
directory specified in upstart-app-launcher-desktop.click-hook.in.  Those desktop files
 
33
give us the App ID of the packages that are installed and have applications needing
 
34
desktop files in them.  We then operate on each of them ensuring that they are synchronized
 
35
with the desktop files in ~/.local/share/applications/.
 
36
 
 
37
The desktop files that we're creating there ARE NOT used for execution by the
 
38
upstart-app-launch Upstart jobs.  They are there so that Unity can know which applications
 
39
are installed for this user and they provide an Exec line to allow compatibility with
 
40
desktop environments that are not using upstart-app-launch for launching applications.
 
41
You should not modify them and expect any executing under Unity to change.
 
42
 
 
43
*/
 
44
 
 
45
#include <gio/gio.h>
 
46
#include <glib/gstdio.h>
 
47
#include <string.h>
 
48
 
 
49
#include "helpers.h"
 
50
 
 
51
typedef struct _app_state_t app_state_t;
 
52
struct _app_state_t {
 
53
        gchar * app_id;
 
54
        gboolean has_click;
 
55
        gboolean has_desktop;
 
56
        guint64 click_modified;
 
57
        guint64 desktop_modified;
 
58
};
 
59
 
 
60
/* Find an entry in the app array */
 
61
app_state_t *
 
62
find_app_entry (const gchar * name, GArray * app_array)
 
63
{
 
64
        int i;
 
65
        for (i = 0; i < app_array->len; i++) {
 
66
                app_state_t * state = &g_array_index(app_array, app_state_t, i);
 
67
 
 
68
                if (g_strcmp0(state->app_id, name) == 0) {
 
69
                        return state;
 
70
                }
 
71
        }
 
72
 
 
73
        app_state_t newstate;
 
74
        newstate.has_click = FALSE;
 
75
        newstate.has_desktop = FALSE;
 
76
        newstate.click_modified = 0;
 
77
        newstate.desktop_modified = 0;
 
78
        newstate.app_id = g_strdup(name);
 
79
 
 
80
        g_array_append_val(app_array, newstate);
 
81
 
 
82
        /* Note: The pointer needs to be the entry in the array, not the
 
83
           one that we have on the stack.  Criticaly important. */
 
84
        app_state_t * statepntr = &g_array_index(app_array, app_state_t, app_array->len - 1);
 
85
        return statepntr;
 
86
}
 
87
 
 
88
/* Looks up the file creation time, which seems harder with GLib
 
89
   than it should be */
 
90
guint64
 
91
modified_time (const gchar * dir, const gchar * filename)
 
92
{
 
93
        gchar * path = g_build_filename(dir, filename, NULL);
 
94
        GFile * file = g_file_new_for_path(path);
 
95
        GFileInfo * info = g_file_query_info(file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL);
 
96
 
 
97
        guint64 time = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
 
98
 
 
99
        g_object_unref(info);
 
100
        g_object_unref(file);
 
101
        g_free(path);
 
102
 
 
103
        return time;
 
104
}
 
105
 
 
106
/* Look at an click package entry */
 
107
void
 
108
add_click_package (const gchar * dir, const gchar * name, GArray * app_array)
 
109
{
 
110
        if (!g_str_has_suffix(name, ".desktop")) {
 
111
                return;
 
112
        }
 
113
 
 
114
        gchar * appid = g_strdup(name);
 
115
        g_strstr_len(appid, -1, ".desktop")[0] = '\0';
 
116
 
 
117
        app_state_t * state = find_app_entry(appid, app_array);
 
118
        state->has_click = TRUE;
 
119
        state->click_modified = modified_time(dir, name);
 
120
 
 
121
        g_free(appid);
 
122
 
 
123
        return;
 
124
}
 
125
 
 
126
/* Look at an desktop file entry */
 
127
void
 
128
add_desktop_file (const gchar * dir, const gchar * name, GArray * app_array)
 
129
{
 
130
        if (!g_str_has_suffix(name, ".desktop")) {
 
131
                return;
 
132
        }
 
133
 
 
134
        gchar * appid = g_strdup(name);
 
135
        g_strstr_len(appid, -1, ".desktop")[0] = '\0';
 
136
 
 
137
        /* We only want valid APP IDs as desktop files */
 
138
        if (!app_id_to_triplet(appid, NULL, NULL, NULL)) {
 
139
                g_free(appid);
 
140
                return;
 
141
        }
 
142
 
 
143
        app_state_t * state = find_app_entry(appid, app_array);
 
144
        state->has_desktop = TRUE;
 
145
        state->desktop_modified = modified_time(dir, name);
 
146
 
 
147
        g_free(appid);
 
148
        return;
 
149
}
 
150
 
 
151
/* Open a directory and look at all the entries */
 
152
void
 
153
dir_for_each (const gchar * dirname, void(*func)(const gchar * dir, const gchar * name, GArray * app_array), GArray * app_array)
 
154
{
 
155
        GError * error = NULL;
 
156
        GDir * directory = g_dir_open(dirname, 0, &error);
 
157
 
 
158
        if (error != NULL) {
 
159
                g_warning("Unable to read directory '%s': %s", dirname, error->message);
 
160
                g_error_free(error);
 
161
                return;
 
162
        }
 
163
 
 
164
        const gchar * filename = NULL;
 
165
        while ((filename = g_dir_read_name(directory)) != NULL) {
 
166
                func(dirname, filename, app_array);
 
167
        }
 
168
 
 
169
        g_dir_close(directory);
 
170
        return;
 
171
}
 
172
 
 
173
/* Function to take the source Desktop file and build a new
 
174
   one with similar, but not the same data in it */
 
175
static void
 
176
copy_desktop_file (const gchar * from, const gchar * to, const gchar * appdir, const gchar * app_id)
 
177
{
 
178
        GError * error = NULL;
 
179
        GKeyFile * keyfile = g_key_file_new();
 
180
        g_key_file_load_from_file(keyfile,
 
181
                from,
 
182
                G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
 
183
                &error);
 
184
 
 
185
        if (error != NULL) {
 
186
                g_warning("Unable to read the desktop file '%s' in the application directory: %s", from, error->message);
 
187
                g_error_free(error);
 
188
                g_key_file_unref(keyfile);
 
189
                return;
 
190
        }
 
191
 
 
192
        gchar * oldexec = desktop_to_exec(keyfile, from);
 
193
        if (oldexec == NULL) {
 
194
                g_key_file_unref(keyfile);
 
195
                return;
 
196
        }
 
197
 
 
198
        if (g_key_file_has_key(keyfile, "Desktop Entry", "Path", NULL)) {
 
199
                gchar * oldpath = g_key_file_get_string(keyfile, "Desktop Entry", "Path", NULL);
 
200
                g_debug("Desktop file '%s' has a Path set to '%s'.  Setting as X-Ubuntu-Old-Path.", from, oldpath);
 
201
 
 
202
                g_key_file_set_string(keyfile, "Desktop Entry", "X-Ubuntu-Old-Path", oldpath);
 
203
 
 
204
                g_free(oldpath);
 
205
        }
 
206
 
 
207
        gchar * path = g_build_filename(appdir, NULL);
 
208
        g_key_file_set_string(keyfile, "Desktop Entry", "Path", path);
 
209
        g_free(path);
 
210
 
 
211
        gchar * newexec = g_strdup_printf("aa-exec -p %s -- %s", app_id, oldexec);
 
212
        g_key_file_set_string(keyfile, "Desktop Entry", "Exec", newexec);
 
213
        g_free(newexec);
 
214
        g_free(oldexec);
 
215
 
 
216
        g_key_file_set_string(keyfile, "Desktop Entry", "X-Ubuntu-Application-ID", app_id);
 
217
 
 
218
        gsize datalen = 0;
 
219
        gchar * data = g_key_file_to_data(keyfile, &datalen, &error);
 
220
        g_key_file_unref(keyfile);
 
221
 
 
222
        if (error != NULL) {
 
223
                g_warning("Unable serialize keyfile built from '%s': %s", from, error->message);
 
224
                g_error_free(error);
 
225
                return;
 
226
        }
 
227
 
 
228
        g_file_set_contents(to, data, datalen, &error);
 
229
        g_free(data);
 
230
 
 
231
        if (error != NULL) {
 
232
                g_warning("Unable to write out desktop file to '%s': %s", to, error->message);
 
233
                g_error_free(error);
 
234
                return;
 
235
        }
 
236
 
 
237
        return;
 
238
}
 
239
 
 
240
/* Build a desktop file in the user's home directory */
 
241
static void
 
242
build_desktop_file (app_state_t * state, const gchar * symlinkdir, const gchar * desktopdir)
 
243
{
 
244
        GError * error = NULL;
 
245
        gchar * package = NULL;
 
246
        /* 'Parse' the App ID */
 
247
        if (!app_id_to_triplet(state->app_id, &package, NULL, NULL)) {
 
248
                return;
 
249
        }
 
250
 
 
251
        /* Check click to find out where the files are */
 
252
        gchar * cmdline = g_strdup_printf("click pkgdir \"%s\"", package);
 
253
        g_free(package);
 
254
 
 
255
        gchar * output = NULL;
 
256
        g_spawn_command_line_sync(cmdline, &output, NULL, NULL, &error);
 
257
        g_free(cmdline);
 
258
 
 
259
        /* If we have an extra newline, we can hide it. */
 
260
        if (output != NULL) {
 
261
                gchar * newline = NULL;
 
262
 
 
263
                newline = g_strstr_len(output, -1, "\n");
 
264
 
 
265
                if (newline != NULL) {
 
266
                        newline[0] = '\0';
 
267
                }
 
268
        }
 
269
 
 
270
        if (error != NULL) {
 
271
                g_warning("Unable to get the package directory from click: %s", error->message);
 
272
                g_error_free(error);
 
273
                g_free(output); /* Probably not set, but just in case */
 
274
                return;
 
275
        }
 
276
 
 
277
        if (!g_file_test(output, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
 
278
                g_warning("Dirctory returned by click '%s' couldn't be found", output);
 
279
                g_free(output);
 
280
                return;
 
281
        }
 
282
 
 
283
        gchar * indesktop = manifest_to_desktop(output, state->app_id);
 
284
        if (indesktop == NULL) {
 
285
                g_free(output);
 
286
                return;
 
287
        }
 
288
 
 
289
        /* Determine the desktop file name */
 
290
        gchar * desktopfile = g_strdup_printf("%s.desktop", state->app_id);
 
291
        gchar * desktoppath = g_build_filename(desktopdir, desktopfile, NULL);
 
292
        g_free(desktopfile);
 
293
 
 
294
        copy_desktop_file(indesktop, desktoppath, output, state->app_id);
 
295
 
 
296
        g_free(desktoppath);
 
297
        g_free(indesktop);
 
298
        g_free(output);
 
299
 
 
300
        return;
 
301
}
 
302
 
 
303
/* Remove the desktop file from the user's home directory */
 
304
static gboolean
 
305
remove_desktop_file (app_state_t * state, const gchar * desktopdir)
 
306
{
 
307
        gchar * desktopfile = g_strdup_printf("%s.desktop", state->app_id);
 
308
        gchar * desktoppath = g_build_filename(desktopdir, desktopfile, NULL);
 
309
        g_free(desktopfile);
 
310
 
 
311
        GKeyFile * keyfile = g_key_file_new();
 
312
        g_key_file_load_from_file(keyfile,
 
313
                desktoppath,
 
314
                G_KEY_FILE_NONE,
 
315
                NULL);
 
316
 
 
317
        if (!g_key_file_has_key(keyfile, "Desktop Entry", "X-Ubuntu-Application-ID", NULL)) {
 
318
                g_debug("Desktop file '%s' is not one created by us.", desktoppath);
 
319
                g_key_file_unref(keyfile);
 
320
                g_free(desktoppath);
 
321
                return FALSE;
 
322
        }
 
323
        g_key_file_unref(keyfile);
 
324
 
 
325
        if (g_unlink(desktoppath) != 0) {
 
326
                g_warning("Unable to delete desktop file: %s", desktoppath);
 
327
        }
 
328
 
 
329
        g_free(desktoppath);
 
330
 
 
331
        return TRUE;
 
332
}
 
333
 
 
334
/* The main function */
 
335
int
 
336
main (int argc, char * argv[])
 
337
{
 
338
        if (argc != 1) {
 
339
                g_error("Shouldn't have arguments");
 
340
                return 1;
 
341
        }
 
342
 
 
343
        GArray * apparray = g_array_new(FALSE, FALSE, sizeof(app_state_t));
 
344
 
 
345
        /* Find all the symlinks of desktop files */
 
346
        gchar * symlinkdir = g_build_filename(g_get_user_cache_dir(), "upstart-app-launch", "desktop", NULL);
 
347
        if (!g_file_test(symlinkdir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
 
348
                g_warning("No installed click packages");
 
349
        } else {
 
350
                dir_for_each(symlinkdir, add_click_package, apparray);
 
351
        }
 
352
 
 
353
        /* Find all the click desktop files */
 
354
        gchar * desktopdir = g_build_filename(g_get_user_data_dir(), "applications", NULL);
 
355
        gboolean desktopdirexists = FALSE;
 
356
        if (!g_file_test(symlinkdir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
 
357
                g_warning("No applications defined");
 
358
        } else {
 
359
                dir_for_each(desktopdir, add_desktop_file, apparray);
 
360
                desktopdirexists = TRUE;
 
361
        }
 
362
 
 
363
        /* Process the merge */
 
364
        int i;
 
365
        for (i = 0; i < apparray->len; i++) {
 
366
                app_state_t * state = &g_array_index(apparray, app_state_t, i);
 
367
                g_debug("Processing App ID: %s", state->app_id);
 
368
 
 
369
                if (state->has_click && state->has_desktop) {
 
370
                        if (state->click_modified > state->desktop_modified) {
 
371
                                g_debug("\tClick updated more recently");
 
372
                                g_debug("\tRemoving desktop file");
 
373
                                if (remove_desktop_file(state, desktopdir)) {
 
374
                                        g_debug("\tBuilding desktop file");
 
375
                                        build_desktop_file(state, symlinkdir, desktopdir);
 
376
                                }
 
377
                        } else {
 
378
                                g_debug("\tAlready synchronized");
 
379
                        }
 
380
                } else if (state->has_click) {
 
381
                        if (!desktopdirexists) {
 
382
                                if (g_mkdir_with_parents(desktopdir, 0755) == 0) {
 
383
                                        g_debug("\tCreated applications directory");
 
384
                                        desktopdirexists = TRUE;
 
385
                                } else {
 
386
                                        g_warning("\tUnable to create applications directory");
 
387
                                }
 
388
                        }
 
389
                        if (desktopdirexists) {
 
390
                                g_debug("\tBuilding desktop file");
 
391
                                build_desktop_file(state, symlinkdir, desktopdir);
 
392
                        }
 
393
                } else if (state->has_desktop) {
 
394
                        g_debug("\tRemoving desktop file");
 
395
                        remove_desktop_file(state, desktopdir);
 
396
                }
 
397
 
 
398
                g_free(state->app_id);
 
399
        }
 
400
 
 
401
        g_array_free(apparray, TRUE);
 
402
        g_free(desktopdir);
 
403
        g_free(symlinkdir);
 
404
 
 
405
        return 0;
 
406
}