1 /*
2 * Copyright (C) 2019, Matthias Clasen
3 *
4 * This file is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as
6 * published by the Free Software Foundation, version 3.0 of the
7 * License.
8 *
9 * This file is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * SPDX-License-Identifier: LGPL-3.0-only
18 */
19
20 #include "config.h"
21
22 #include "updates.h"
23 #include "portal-private.h"
24
25 #define UPDATE_MONITOR_INTERFACE "org.freedesktop.portal.Flatpak.UpdateMonitor"
26 #define UPDATE_MONITOR_PATH_PREFIX "/org/freedesktop/portal/Flatpak/update_monitor/"
27
28 typedef struct {
29 XdpPortal *portal;
30 GTask *task;
31 char *request_path;
32 char *id;
33 } CreateMonitorCall;
34
35 static void
create_monitor_call_free(CreateMonitorCall * call)36 create_monitor_call_free (CreateMonitorCall *call)
37 {
38 g_free (call->request_path);
39 g_free (call->id);
40
41 g_object_unref (call->portal);
42 g_object_unref (call->task);
43
44 g_free (call);
45 }
46
47 static void
update_available_received(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)48 update_available_received (GDBusConnection *bus,
49 const char *sender_name,
50 const char *object_path,
51 const char *interface_name,
52 const char *signal_name,
53 GVariant *parameters,
54 gpointer data)
55 {
56 XdpPortal *portal = data;
57 g_autoptr(GVariant) update_info = NULL;
58 const char *running_commit;
59 const char *local_commit;
60 const char *remote_commit;
61
62 g_variant_get (parameters, "(@a{sv})", &update_info);
63 g_variant_lookup (update_info, "running-commit", "&s", &running_commit);
64 g_variant_lookup (update_info, "local-commit", "&s", &local_commit);
65 g_variant_lookup (update_info, "remote-commit", "&s", &remote_commit);
66
67 g_signal_emit_by_name (portal, "update-available",
68 running_commit,
69 local_commit,
70 remote_commit);
71 }
72
73 static void
update_progress_received(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)74 update_progress_received (GDBusConnection *bus,
75 const char *sender_name,
76 const char *object_path,
77 const char *interface_name,
78 const char *signal_name,
79 GVariant *parameters,
80 gpointer data)
81 {
82 XdpPortal *portal = data;
83 g_autoptr(GVariant) info = NULL;
84 guint n_ops;
85 guint op;
86 guint progress;
87 XdpUpdateStatus status;
88 const char *error = NULL;
89 const char *error_message = NULL;
90
91 g_variant_get (parameters, "(@a{sv})", &info);
92 g_variant_lookup (info, "n_ops", "u", &n_ops);
93 g_variant_lookup (info, "op", "u", &op);
94 g_variant_lookup (info, "progress", "u", &progress);
95 g_variant_lookup (info, "status", "u", &status);
96 if (status == XDP_UPDATE_STATUS_FAILED)
97 {
98 g_variant_lookup (info, "error", "&s", &error);
99 g_variant_lookup (info, "error_message", "&s", &error_message);
100 }
101 g_debug ("update progress received %u/%u %u%% %d", op, n_ops, progress, status);
102
103 g_signal_emit_by_name (portal, "update-progress",
104 n_ops,
105 op,
106 progress,
107 status,
108 error,
109 error_message);
110 }
111
112 static void
ensure_update_monitor_connection(XdpPortal * portal)113 ensure_update_monitor_connection (XdpPortal *portal)
114 {
115 if (portal->update_available_signal == 0)
116 portal->update_available_signal =
117 g_dbus_connection_signal_subscribe (portal->bus,
118 FLATPAK_PORTAL_BUS_NAME,
119 UPDATE_MONITOR_INTERFACE,
120 "UpdateAvailable",
121 portal->update_monitor_handle,
122 NULL,
123 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
124 update_available_received,
125 portal,
126 NULL);
127
128 if (portal->update_progress_signal == 0)
129 portal->update_progress_signal =
130 g_dbus_connection_signal_subscribe (portal->bus,
131 FLATPAK_PORTAL_BUS_NAME,
132 UPDATE_MONITOR_INTERFACE,
133 "Progress",
134 portal->update_monitor_handle,
135 NULL,
136 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
137 update_progress_received,
138 portal,
139 NULL);
140 }
141
142 static void
monitor_created(GObject * object,GAsyncResult * result,gpointer data)143 monitor_created (GObject *object,
144 GAsyncResult *result,
145 gpointer data)
146 {
147 CreateMonitorCall *call = data;
148 GError *error = NULL;
149 g_autoptr(GVariant) ret = NULL;
150
151 ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
152 if (error)
153 {
154 g_task_return_error (call->task, error);
155 }
156 else
157 {
158 call->portal->update_monitor_handle = g_strdup (call->id);
159 ensure_update_monitor_connection (call->portal);
160 g_task_return_boolean (call->task, TRUE);
161 }
162
163 create_monitor_call_free (call);
164 }
165
166 static void
create_monitor(CreateMonitorCall * call)167 create_monitor (CreateMonitorCall *call)
168 {
169 GVariantBuilder options;
170 g_autofree char *token = NULL;
171 g_autofree char *session_token = NULL;
172 GCancellable *cancellable;
173
174 if (call->portal->update_monitor_handle)
175 {
176 g_task_return_boolean (call->task, TRUE);
177 create_monitor_call_free (call);
178 return;
179 }
180
181 token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
182 call->id = g_strconcat (UPDATE_MONITOR_PATH_PREFIX, call->portal->sender, "/", token, NULL);
183
184 cancellable = g_task_get_cancellable (call->task);
185
186 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
187 g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
188 g_dbus_connection_call (call->portal->bus,
189 FLATPAK_PORTAL_BUS_NAME,
190 FLATPAK_PORTAL_OBJECT_PATH,
191 FLATPAK_PORTAL_INTERFACE,
192 "CreateUpdateMonitor",
193 g_variant_new ("(a{sv})", &options),
194 NULL,
195 G_DBUS_CALL_FLAGS_NONE,
196 -1,
197 cancellable,
198 monitor_created,
199 call);
200
201 }
202
203 /**
204 * xdp_portal_update_monitor_start:
205 * @portal: a [class@Portal]
206 * @flags: options for this cal..
207 * @cancellable: (nullable): optional [class@Gio.Cancellable]
208 * @callback: (scope async): a callback to call when the request is done
209 * @data: (closure): data to pass to @callback
210 *
211 * Makes XdpPortal start monitoring for available software updates.
212 *
213 * When a new update is available, the [signal@Portal::update-available].
214 * signal is emitted.
215 *
216 * Use [method@Portal.update_monitor_stop] to stop monitoring.
217 */
218 void
xdp_portal_update_monitor_start(XdpPortal * portal,XdpUpdateMonitorFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)219 xdp_portal_update_monitor_start (XdpPortal *portal,
220 XdpUpdateMonitorFlags flags,
221 GCancellable *cancellable,
222 GAsyncReadyCallback callback,
223 gpointer data)
224 {
225 CreateMonitorCall *call;
226
227 g_return_if_fail (XDP_IS_PORTAL (portal));
228 g_return_if_fail (flags == XDP_UPDATE_MONITOR_FLAG_NONE);
229
230 call = g_new0 (CreateMonitorCall, 1);
231 call->portal = g_object_ref (portal);
232 call->task = g_task_new (portal, cancellable, callback, data);
233 g_task_set_source_tag (call->task, xdp_portal_update_monitor_start);
234
235 create_monitor (call);
236 }
237
238 /**
239 * xdp_portal_update_monitor_start_finish:
240 * @portal: a [class@Portal]
241 * @result: a [iface@Gio.AsyncResult]
242 * @error: return location for an error
243 *
244 * Finishes an update-monitor request, and returns
245 * the result in the form of boolean.
246 *
247 * Returns: `TRUE` if the request succeeded
248 */
249 gboolean
xdp_portal_update_monitor_start_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)250 xdp_portal_update_monitor_start_finish (XdpPortal *portal,
251 GAsyncResult *result,
252 GError **error)
253 {
254 g_return_val_if_fail (XDP_IS_PORTAL (portal), FALSE);
255 g_return_val_if_fail (g_task_is_valid (result, portal), FALSE);
256 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_update_monitor_start, FALSE);
257
258 return g_task_propagate_boolean (G_TASK (result), error);
259 }
260
261 /**
262 * xdp_portal_update_monitor_stop:
263 * @portal: a [class@Portal]
264 *
265 * Stops update monitoring that was started with
266 * [method@Portal.update_monitor_start].
267 */
268 void
xdp_portal_update_monitor_stop(XdpPortal * portal)269 xdp_portal_update_monitor_stop (XdpPortal *portal)
270 {
271 g_return_if_fail (XDP_IS_PORTAL (portal));
272
273 if (portal->update_available_signal)
274 {
275 g_dbus_connection_signal_unsubscribe (portal->bus, portal->update_available_signal);
276 portal->update_available_signal = 0;
277 }
278
279 if (portal->update_progress_signal)
280 {
281 g_dbus_connection_signal_unsubscribe (portal->bus, portal->update_progress_signal);
282 portal->update_progress_signal = 0;
283 }
284
285 if (portal->update_monitor_handle)
286 {
287 g_dbus_connection_call (portal->bus,
288 FLATPAK_PORTAL_BUS_NAME,
289 portal->update_monitor_handle,
290 UPDATE_MONITOR_INTERFACE,
291 "Close",
292 NULL,
293 NULL, 0, -1, NULL, NULL, NULL);
294 g_clear_pointer (&portal->update_monitor_handle, g_free);
295 }
296 }
297
298 typedef struct {
299 XdpPortal *portal;
300 XdpParent *parent;
301 char *parent_handle;
302 GTask *task;
303 } InstallUpdateCall;
304
305 static void
install_update_call_free(InstallUpdateCall * call)306 install_update_call_free (InstallUpdateCall *call)
307 {
308 if (call->parent)
309 {
310 call->parent->parent_unexport (call->parent);
311 xdp_parent_free (call->parent);
312 }
313 g_free (call->parent_handle);
314
315 g_object_unref (call->portal);
316 g_object_unref (call->task);
317
318 g_free (call);
319 }
320
321 static void install_update (InstallUpdateCall *call);
322
323 static void
create_parent_exported(XdpParent * parent,const char * handle,gpointer data)324 create_parent_exported (XdpParent *parent,
325 const char *handle,
326 gpointer data)
327 {
328 InstallUpdateCall *call = data;
329 call->parent_handle = g_strdup (handle);
330 install_update (call);
331 }
332
333 static void
update_started(GObject * object,GAsyncResult * result,gpointer data)334 update_started (GObject *object,
335 GAsyncResult *result,
336 gpointer data)
337 {
338 InstallUpdateCall *call = data;
339 g_autoptr(GError) error = NULL;
340 g_autoptr(GVariant) ret = NULL;
341
342 ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
343 if (error)
344 g_task_return_error (call->task, error);
345 else
346 g_task_return_boolean (call->task, TRUE);
347
348 install_update_call_free (call);
349 }
350
351 static void
install_update(InstallUpdateCall * call)352 install_update (InstallUpdateCall *call)
353 {
354 GVariantBuilder options;
355 GCancellable *cancellable;
356
357 if (call->portal->update_monitor_handle == NULL)
358 {
359 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Not monitoring updates");
360 install_update_call_free (call);
361 return;
362 }
363
364 if (call->parent_handle == NULL)
365 {
366 call->parent->parent_export (call->parent, create_parent_exported, call);
367 return;
368 }
369
370 cancellable = g_task_get_cancellable (call->task);
371
372 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
373 g_dbus_connection_call (call->portal->bus,
374 FLATPAK_PORTAL_BUS_NAME,
375 call->portal->update_monitor_handle,
376 UPDATE_MONITOR_INTERFACE,
377 "Update",
378 g_variant_new ("(sa{sv})",
379 call->parent_handle,
380 &options),
381 NULL, 0, -1, cancellable, update_started, call);
382 }
383
384 /**
385 * xdp_portal_update_install:
386 * @portal: a [class@Portal]
387 * @parent: a [struct@Parent]
388 * @flags: options for this call
389 * @cancellable: (nullable): optional [class@Gio.Cancellable]
390 * @callback: (scope async): a callback to call when the request is done
391 * @data: (closure): data to pass to @callback
392 *
393 * Installs an available software update. This should be
394 * called in response to a [signal@Portal::update-available] signal.
395 *
396 * During the update installation, the [signal@Portal::update-progress]
397 * signal will be emitted to provide progress information.
398 */
399 void
xdp_portal_update_install(XdpPortal * portal,XdpParent * parent,XdpUpdateInstallFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)400 xdp_portal_update_install (XdpPortal *portal,
401 XdpParent *parent,
402 XdpUpdateInstallFlags flags,
403 GCancellable *cancellable,
404 GAsyncReadyCallback callback,
405 gpointer data)
406 {
407 InstallUpdateCall *call;
408
409 g_return_if_fail (XDP_IS_PORTAL (portal));
410 g_return_if_fail (flags == XDP_UPDATE_INSTALL_FLAG_NONE);
411
412 call = g_new0 (InstallUpdateCall, 1);
413 call->portal = g_object_ref (portal);
414 if (parent)
415 call->parent = xdp_parent_copy (parent);
416 else
417 call->parent_handle = g_strdup ("");
418 call->task = g_task_new (portal, cancellable, callback, data);
419 g_task_set_source_tag (call->task, xdp_portal_update_install);
420
421 install_update (call);
422 }
423
424 /**
425 * xdp_portal_update_install_finish:
426 * @portal: a [class@Portal]
427 * @result: a [iface@Gio.AsyncResult]
428 * @error: return location for an error
429 *
430 * Finishes an update-installation request, and returns
431 * the result in the form of boolean.
432 *
433 * Note that the update may not be completely installed
434 * by the time this function is called. You need to
435 * listen to the [signal@Portal::update-progress] signal
436 * to learn when the installation is complete.
437 *
438 * Returns: `TRUE` if the update is being installed
439 */
440 gboolean
xdp_portal_update_install_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)441 xdp_portal_update_install_finish (XdpPortal *portal,
442 GAsyncResult *result,
443 GError **error)
444 {
445 g_return_val_if_fail (XDP_IS_PORTAL (portal), FALSE);
446 g_return_val_if_fail (g_task_is_valid (result, portal), FALSE);
447 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_update_install, FALSE);
448
449 return g_task_propagate_boolean (G_TASK (result), error);
450 }
451