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