-
Notifications
You must be signed in to change notification settings - Fork 0
/
gs-plugin-appimage.c
358 lines (307 loc) · 17 KB
/
gs-plugin-appimage.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
/* GNOME Software AppImage plugin
* Licensing to be determined (MIT like AppImageKit or GPLv2 like GNOME Software)
*/
#include <gnome-software.h>
#include <appimage/appimage.h> // From https://github.com/AppImage/AppImageKit
#include <sys/stat.h>
void
gs_plugin_initialize (GsPlugin *plugin)
{
g_debug("AppImage gs_plugin_initialize");
gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_BEFORE, "appstream");
/* prioritize over packages */
gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_BETTER_THAN, "packagekit");
}
/* Claim AppImages that should be handled by this plugin.
* hughsie: gnome-software doesn't understand what a desktop file is but appstrem-glib does, so we need
* to let appstream-glib discover them and then adopt them in the plugin
*/
void
gs_plugin_adopt_app (GsPlugin *plugin, GsApp *app)
{
if (gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_APPIMAGE)
gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
}
/* Launch AppImages
* QUESTION: Do I even need to do something special? Why doesn't it just launch the desktop file?
* hughsie: don't implement gs_plugin_launch and see if it works (it should)
*/
gboolean
gs_plugin_launch (GsPlugin *plugin,
GsApp *app,
GCancellable *cancellable,
GError **error)
{
if (gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_APPIMAGE) {
g_debug("AppImage shall be launched");
g_autofree gchar *md5 = appimage_get_md5(g_file_get_path (gs_app_get_local_file (app)));
g_autofree gchar *partial_path = g_strdup_printf("applications/appimagekit_%s-%s", md5, "*");
g_autofree const gchar *data_home = g_get_user_data_dir();
g_autofree gchar *appimage_integrated_desktop_file_path = g_build_filename(data_home, partial_path, NULL);
if (g_file_test (appimage_integrated_desktop_file_path, G_FILE_TEST_EXISTS))
{
g_debug("AppImage is already integrated at %s", appimage_integrated_desktop_file_path);
} else {
g_debug("AppImage is not integrated yet");
}
return TRUE;
} else {
g_debug("Not an AppImage");
return FALSE; // FIXME: Proper error handling using GError
}
}
/*
* Handle AppImages "opened" with GNOME Software
* This works; it does show the product detail page when launched like this:
* XDG_DATA_DIRS=install/share:$XDG_DATA_DIRS ../install/bin/gnome-software --verbose --local-filename=/isodevice/Applications/XChat-2.8.8-x86_64.AppImage 2>&1 | grep AppImage
*
*/
gboolean
gs_plugin_file_to_app (GsPlugin *plugin,
GsAppList *list,
GFile *file,
GCancellable *cancellable,
GError **error)
{
g_debug("AppImage gs_plugin_url_to_app");
g_debug("file: %s", g_file_get_path(file));
int appimage_type = appimage_get_type(g_file_get_path(file), TRUE);
g_debug("AppImage type: %i", appimage_type);
/* Error if we cannot determine the type of the AppImage */
if(appimage_type <0) {
return FALSE;
}
/* Extract the desktop file from the AppImage */
char** files = appimage_list_files(g_file_get_path(file));
g_autofree gchar *desktop_file = NULL;
gchar *extracted_desktop_file = "/tmp/gs-plugin-appimage.desktop";
int i = 0;
for (; files[i] != NULL ; i++) {
// g_debug("AppImage file: %s", files[i]);
if (g_str_has_suffix (files[i],".desktop")) {
desktop_file = files[i];
g_debug("AppImage desktop file: %s", desktop_file);
break;
}
}
/* Exit if we cannot find the desktop file */
if(desktop_file == NULL) {
g_debug("AppImage desktop file not found");
appimage_string_list_free(files);
return FALSE;
}
/* Extract desktop file to temporary location */
appimage_extract_file_following_symlinks(g_file_get_path(file), desktop_file,
extracted_desktop_file);
/* Load contents of desktop file */
gboolean success = FALSE;
GKeyFile* key_file_structure = g_key_file_new();
/* QUESTION: Do we need to load desktop files like this?
* hughsie: appstream-glib can do that work
*/
g_debug("Loading AppImage desktop file from %s", extracted_desktop_file);
success = g_key_file_load_from_file(key_file_structure, extracted_desktop_file, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
if (!success) {
g_debug("AppImage desktop file could not be loaded");
appimage_string_list_free(files);
return FALSE;
} else {
g_debug("Loaded AppImage desktop file"); // QUESTION: Can we directly gs_app_new() from this? hughsie: use appstream-glib
}
/* TODO: AppStream
* Save an AppStream-format XML file in either /usr/share/app-info/xmls/, /var/cache/app-info/xmls/ or ~/.local/share/app-info/xmls/. GNOME Software will immediately notice any new files, or changes to existing files as it has set up the various inotify watches.
*
* QUESTION: Do we need to copy XML there for AppImages that are not integrated into the system?
* Can we load in data from an xml file which we delete immediately afterwards?
* Because we don't want to litter the system with XML files.
* hughsie: Don't need to copy; just use the helpers in gs-appstream.c;
* e.g. the Flatpak plugin loads the AppStream xml from a nonstandard path
*
* QUESTION: If we need to ensure that our AppStream information is not mixed with AppStream
* information from other locations or from version to version of an application, is it OK to place it in
* /home/me/.local/share/app-info/xmls/appimagekit_98080cfc981c9098c6a5e41794add640-deepin-screenshot.appdata.xml
* hughsie: the filename is almost unimportant, it's the <id> that has to match
*
* QUESTION: If we have appimagekit_98080cfc981c9098c6a5e41794add640-deepin-screenshot.desktop in one of
* the well-known locations for desktop files, will it automatically be associated with the AppStream metadata from above?
* hughsie: the filename is almost unimportant, it's the <id> that has to match
*
* QUESTION: Is the AppStream ID the same as the gs ID?
* hughsie: In most cases, yes. The appstream file can also have a <launchable> tab pointing to the desktop file.
* If there's no launchable then we use a heuristic to try and create one
*
* QUESTION: Is there a screenshot where i could see the effect of using different IDs for gs_app_new() vs. gs_app_set_branch()?
* hughsie: Not really, but you can use gnome-software-cmd like this: /usr/libexec/gnome-software-cmd search --refine-flags=icon --show-results
*
* I see:
* 08:20:32:0181 Gs searching appstream for user / * / * / desktop / appimagekit_98080cfc981c9098c6a5e41794add640-deepin-screenshot.desktop / *
*
* QUESTION: Should we have appimaged (also) integrate AppStream files to ~/.local/share/app-info/xmls/?
* hughsie: I think using reverse DNS style IDs everywhere is a very good idea; I also think it's too early to optimise anything
*/
g_autofree gchar *md5 = appimage_get_md5(g_file_get_path (file));
/* FIXME: This block is commented out because it crashes.
* I suspect it has to do with some g_free(), g_autofree, g_autoptr and/or appimage_string_list_free()
* stuff. Any help greatly appreciated.
// QUESTION: Why does glib not have a str_replace()? hughsie: If you have a GString, can use as_utils_string_replace()
g_autoptr(GString) appstream_filename = g_string_new(g_path_get_basename(desktop_file));
as_utils_string_replace(appstream_filename, ".desktop", ".metainfo.xml");
gchar *extracted_appstream_file = g_strconcat(g_get_user_data_dir(), "/app-info/xmls/", "appimagekit_", md5, "-", g_string_free(appstream_filename, FALSE), NULL);
// g_free(appstream_filename); // Would lead to a crash
g_debug("AppImage AppStream file to be extracted to: %s", extracted_appstream_file);
// Extract the AppStream file from the AppImage
files = appimage_list_files(g_file_get_path(file));
g_autofree gchar *appstream_file = NULL;
i = 0;
for (; files[i] != NULL ; i++) {
// g_debug("AppImage file: %s", files[i]);
if (g_str_has_suffix (files[i],".metainfo.xml")) {
appstream_file = files[i];
g_debug("AppImage AppStream file: %s", appstream_file);
break;
}
}
if(appstream_file == NULL) {
g_debug("AppImage AppStream file not found in AppImage; hence not extracting");
appimage_string_list_free(files);
} else {
// Extract AppStream file
g_debug("AppImage extracting AppStream file");
appimage_extract_file_following_symlinks(g_file_get_path(file), appstream_file,
extracted_appstream_file);
}
g_free(extracted_appstream_file); // FIXME: SOMETIMES crashes around here, regardless of this being called or not
*/
g_autofree gchar *fn = NULL;
g_autoptr(GsApp) app = NULL;
g_autoptr(AsIcon) icon = NULL;
app = gs_app_new ("NULL"); // NOTE: We set the ID down below, including the md5 from appimage_get_md5. hughsie recommends reverse DNS, and it should match the desktop file and the id in the AppStream XML
gs_app_set_scope (app, AS_APP_SCOPE_USER);
gs_app_set_management_plugin (app, "appimage");
gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_APPIMAGE);
gs_app_set_local_file (app, file);
// gs_app_add_quirk (app, AS_APP_QUIRK_PROVENANCE); // QUESTION: How to mark "3rd party"? hughsie: PROVENANCE usually means the opposite, e.g. it's from the distro
// gs_app_set_state (app, AS_APP_STATE_AVAILABLE_LOCAL);
gs_app_set_name (app, GS_APP_QUALITY_NORMAL, g_key_file_get_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, NULL));
gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, g_key_file_get_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_COMMENT, NULL));
gs_app_set_description (app, GS_APP_QUALITY_NORMAL, g_key_file_get_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_COMMENT, NULL));
/* these are all optional, but make details page look better */
gs_app_set_version (app, g_key_file_get_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, "X-AppImage-Version", NULL));
/* Get the size of the AppImage on disk */
struct stat st;
if (lstat(g_file_get_path(file), &st) == -1) {
g_debug("Could not determine AppImage file size");
return FALSE;
}
gs_app_set_size_installed (app, st.st_size);
gs_app_set_size_download (app, st.st_size);
/* Check if this AppImage is already integrated in the system in which case we treat it as "installed */
g_autofree gchar *partial_path = g_strdup_printf("applications/appimagekit_%s-%s", md5, g_path_get_basename(desktop_file));
g_autofree gchar *appimage_integrated_desktop_file_path = g_build_filename(g_get_user_data_dir(), partial_path, NULL);
g_autofree const gchar *appimage_id = g_path_get_basename(partial_path);
gs_app_set_id (app, appimage_id); // This makes it use the desktop file and icon already integrated into the system
if (g_file_test (appimage_integrated_desktop_file_path, G_FILE_TEST_EXISTS))
{
g_debug("AppImage is already integrated at %s", appimage_integrated_desktop_file_path);
gs_app_set_state (app, AS_APP_STATE_INSTALLED);
} else {
g_debug("AppImage is not integrated yet");
gs_app_set_state (app, AS_APP_STATE_AVAILABLE_LOCAL);
}
g_debug("AppImage gs_app_is_installed: %i", gs_app_is_installed (app));
gs_app_set_origin (app, "AppImage");
// QUESTION: "Install" doesn't really cut it. For AppImages, we would want
// "Move to /Applications", "Move to $HOME/Applications", etc. Is there a way to cusomize this?
// hughsie: In gnome-software they'd be different "scope" and therefore different GsApp
// others want the same kind of feature, so it's probably something we ought to support
/* return new app */
gs_app_list_add (list, app);
g_key_file_free(key_file_structure);
// appimage_string_list_free(files); // FIXME: This results in a segfault!
return TRUE;
}
/*
*
* This works; it DOES show up in the installed list!
*
* QUESTION: How can I achieve a similar thing for all the desktop files of already integrated AppImages that show up like this:
*
* 08:25:05:0456 As adding existing file: /home/me/.local/share/applications/appimagekit_96c121fd6971e6073de75aa0cdad64dd-AppImageUpdate.desktop
* 08:25:05:0456 As adding existing file: /home/me/.local/share/applications/appimagekit_d6d51dc8061f0166e11ecd040af70e8e-AppImageUpdate.desktop
*
* 08:26:09:0696 GsPluginPackageKit ignoring /usr/share/applications/appimagekit_96c121fd6971e6073de75aa0cdad64dd-AppImageUpdate.desktop as does not exist
* 08:26:09:0696 GsPluginPackageKit ignoring /usr/share/applications/appimagekit_d6d51dc8061f0166e11ecd040af70e8e-AppImageUpdate.desktop as does not exist
*
* 08:26:13:0491 Gs app invalid as state unknown user / * / * / desktop / appimagekit_96c121fd6971e6073de75aa0cdad64dd-AppImageUpdate.desktop / *
* 08:26:13:0491 Gs app invalid as state unknown user / * / * / desktop / appimagekit_d6d51dc8061f0166e11ecd040af70e8e-AppImageUpdate.desktop / *
*
* QUESTION: What do I have to do in order to make these show up as installed?
* QUESTION: Why does it say "ignoring... as does not exist"?
* QUESTION: What does "app invalid as state unknown user" mean? Probably I have to set them to AS_APP_STATE_INSTALLED, but how?
*
*/
gboolean
gs_plugin_add_installed (GsPlugin *plugin,
GsAppList *list,
GCancellable *cancellable,
GError **error)
{
g_debug("AppImage gs_plugin_add_installed");
g_autofree gchar *fn = NULL;
g_autoptr(GsApp) app = NULL;
g_autoptr(AsIcon) icon = NULL;
// One entry will show up in the list of installed files for each DIFFERENTLY NAMED desktop file
// "System appstream" will be searched for metadata with matching desktop file names
app = gs_app_new ("aaatestapp.desktop"); // "Launch" button only available if ID is passed in
gs_app_set_scope (app, AS_APP_SCOPE_USER); // TODO: Distinguish system-wide AppImages. Those which are read-only for the current user?
gs_app_set_management_plugin (app, "appimage");
gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_APPIMAGE);
gs_app_set_state (app, AS_APP_STATE_INSTALLED);
gs_app_set_name (app, GS_APP_QUALITY_NORMAL, "AAATestApp 1.2.3a"); // QUESTION: How to handle multiple versions properly?
gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, "A teaching application");
gs_app_set_description (app, GS_APP_QUALITY_NORMAL,
"AAATestApp is the name of an application.\n\n"
"It can be used to demo some of our features");
/* create a stock icon which will be loaded by the 'icons' plugin
* NOTE: Without doing this, it will NOT show up in the list of installed files.
* QUESTION: Is this intentional? hughsie: yes, an app needs an icon
*/
icon = as_icon_new ();
as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
as_icon_set_name (icon, "application-x-executable");
gs_app_add_icon (app, icon);
/* return new app */
gs_app_list_add (list, app);
return TRUE;
}
/*
* QUESTION: Can plugins have their own settings, and where would the GUI for these be?
* hughsie: There is a gsetting key to be shared by plugins for this
*
* QUESTION: How do I populate the list of installable apps?
* hughsie: You just return the GsApps when the frontend wants results;
* typically that'll be add_popular, add_featured or search()
*
* QUESTION: I assume it uses the XDG desktop categories?
* hughsie: It does; just make sure it has the right "categories"
*
* QUESTION: How to make all of https://appimage.github.io/feed.json
* show up in gs in the respective categories?
* hughsie: Is that data available as appstream xml too? That's the easiest way;
* otherwise you have to handle the add_categories()
* and add_categories_ap()
*
* QUESTION: do you have a minimal viable example of the kind of distro appstream xml needed that you were referring to?
* hughsie: yaml is supported, but it's had nowhere near the memory optimisation work
* it's the same XML as an appdata file, wraped up in <components>
*
* Just return them as results for the various query methods
* e.g. we never show "all" the apps that can be installed
*
* What most plugins do is use gs_plugin_search() and then if they need some shared resource
* call into some plugin_specific_ensure() thing to open store, parse xml etc.,
* e.g. not do it in gs_plugin_setup() if it's going to take time
*
*/