Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright © 2018 Endless Mobile, Inc.
4 : : *
5 : : * This library is free software; you can redistribute it and/or
6 : : * modify it under the terms of the GNU Lesser General Public
7 : : * License as published by the Free Software Foundation; either
8 : : * version 2.1 of the License, or (at your option) any later version.
9 : : *
10 : : * This library is distributed in the hope that it will be useful,
11 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : : * Lesser General Public License for more details.
14 : : *
15 : : * You should have received a copy of the GNU Lesser General Public
16 : : * License along with this library; if not, write to the Free Software
17 : : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 : : *
19 : : * Authors:
20 : : * - Philip Withnall <withnall@endlessm.com>
21 : : */
22 : :
23 : : #include "config.h"
24 : :
25 : : #include <glib.h>
26 : : #include <glib-object.h>
27 : : #include <glib/gi18n-lib.h>
28 : : #include <gio/gio.h>
29 : : #include <libmogwai-schedule/connection-monitor.h>
30 : : #include <libmogwai-schedule/connection-monitor-nm.h>
31 : : #include <libmogwai-tariff/tariff-loader.h>
32 : : #include <libmogwai-tariff/tariff.h>
33 : : #include <NetworkManager.h>
34 : :
35 : :
36 : : static void mws_connection_monitor_nm_connection_monitor_init (MwsConnectionMonitorInterface *iface);
37 : : static void mws_connection_monitor_nm_initable_init (GInitableIface *iface);
38 : : static void mws_connection_monitor_nm_async_initable_init (GAsyncInitableIface *iface);
39 : : static void mws_connection_monitor_nm_dispose (GObject *object);
40 : :
41 : : static void mws_connection_monitor_nm_get_property (GObject *object,
42 : : guint property_id,
43 : : GValue *value,
44 : : GParamSpec *pspec);
45 : : static void mws_connection_monitor_nm_set_property (GObject *object,
46 : : guint property_id,
47 : : const GValue *value,
48 : : GParamSpec *pspec);
49 : :
50 : : static void active_connection_added_cb (NMClient *client,
51 : : NMActiveConnection *active_connection,
52 : : gpointer user_data);
53 : : static void active_connection_removed_cb (NMClient *client,
54 : : NMActiveConnection *active_connection,
55 : : gpointer user_data);
56 : : static void active_connection_notify_cb (GObject *obj,
57 : : GParamSpec *pspec,
58 : : gpointer user_data);
59 : : static void active_connection_state_changed_cb (NMActiveConnection *active_connection,
60 : : guint new_state,
61 : : guint reason,
62 : : gpointer user_data);
63 : : static void device_added_cb (NMClient *client,
64 : : NMDevice *device,
65 : : gpointer user_data);
66 : : static void device_removed_cb (NMClient *client,
67 : : NMDevice *device,
68 : : gpointer user_data);
69 : : static void device_state_changed_cb (NMDevice *device,
70 : : guint new_state,
71 : : guint old_state,
72 : : guint reason,
73 : : gpointer user_data);
74 : : static void device_notify_cb (GObject *obj,
75 : : GParamSpec *pspec,
76 : : gpointer user_data);
77 : : static void connection_changed_cb (NMConnection *connection,
78 : : gpointer user_data);
79 : :
80 : : static gboolean mws_connection_monitor_nm_init_failable (GInitable *initable,
81 : : GCancellable *cancellable,
82 : : GError **error);
83 : : static void mws_connection_monitor_nm_init_async (GAsyncInitable *initable,
84 : : int io_priority,
85 : : GCancellable *cancellable,
86 : : GAsyncReadyCallback callback,
87 : : gpointer user_data);
88 : : static gboolean mws_connection_monitor_nm_init_finish (GAsyncInitable *initable,
89 : : GAsyncResult *result,
90 : : GError **error);
91 : :
92 : : static const gchar * const *mws_connection_monitor_nm_get_connection_ids (MwsConnectionMonitor *monitor);
93 : : static gboolean mws_connection_monitor_nm_get_connection_details (MwsConnectionMonitor *monitor,
94 : : const gchar *id,
95 : : MwsConnectionDetails *out_details);
96 : :
97 : : /**
98 : : * MwsConnectionMonitorNm:
99 : : *
100 : : * An implementation of the #MwsConnectionMonitor interface which draws its data
101 : : * from the NetworkManager D-Bus interface. This implementation is
102 : : * #GAsyncInitable, and must be initialised asynchronously to connect to D-Bus
103 : : * without blocking.
104 : : *
105 : : * The metered status of each connection (#MwsConnectionDetails.metered) is
106 : : * calculated as the pessimisic combination of the metered status of each
107 : : * #NMDevice and #NMSettingConnection associated with that active connection.
108 : : *
109 : : * Several settings from #NMSettingUser are read for each active connection:
110 : : *
111 : : * * `connection.allow-downloads` (boolean): If `1`, big downloads are allowed
112 : : * on this connection. If `0`, they are not, and Mogwai will never schedule
113 : : * downloads on this connection. (Default: `1`.)
114 : : * * `connection.allow-downloads-when-metered` (boolean): If `1`, big downloads
115 : : * may be scheduled on this connection, iff it is not metered. If the
116 : : * connection is metered, or if this setting is `0`, Mogwai will not schedule
117 : : * downloads on this connection. (Default: `0`.)
118 : : * * `connection.tariff-enabled` (boolean): If `1`, the tariff string in
119 : : * `connection.tariff` is parsed and used (and must be present). If `0`, it
120 : : * is not. (Default: `0`.)
121 : : * * `connection.tariff` (string): A serialised tariff (see
122 : : * mwt_tariff_builder_get_tariff_as_variant()) which specifies how the
123 : : * connection’s properties change over time (for example, bandwidth limits
124 : : * at certain times of day, or capacity limits). (Default: unset.)
125 : : *
126 : : * Since: 0.1.0
127 : : */
128 : : struct _MwsConnectionMonitorNm
129 : : {
130 : : GObject parent;
131 : :
132 : : NMClient *client; /* (owned); NULL during initialisation */
133 : :
134 : : /* Exactly one of these will be set after initialisation completes (or fails). */
135 : : GError *init_error; /* (nullable) (owned) */
136 : : gboolean init_success;
137 : : gboolean initialising;
138 : :
139 : : /* Allow cancelling any pending operations during dispose. */
140 : : GCancellable *cancellable; /* (owned) */
141 : :
142 : : /* This cache should be invalidated whenever
143 : : * nm_client_get_active_connections() is changed. */
144 : : gchar **cached_connection_ids; /* (owned) (array zero-terminated=1) */
145 : : };
146 : :
147 : : typedef enum
148 : : {
149 : : PROP_CLIENT = 1,
150 : : } MwsConnectionMonitorNmProperty;
151 : :
152 [ # # # # : 0 : G_DEFINE_TYPE_WITH_CODE (MwsConnectionMonitorNm, mws_connection_monitor_nm, G_TYPE_OBJECT,
# # ]
153 : : G_IMPLEMENT_INTERFACE (MWS_TYPE_CONNECTION_MONITOR,
154 : : mws_connection_monitor_nm_connection_monitor_init)
155 : : G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
156 : : mws_connection_monitor_nm_initable_init)
157 : : G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
158 : : mws_connection_monitor_nm_async_initable_init))
159 : : static void
160 : 0 : mws_connection_monitor_nm_class_init (MwsConnectionMonitorNmClass *klass)
161 : : {
162 : 0 : GObjectClass *object_class = (GObjectClass *) klass;
163 : 0 : GParamSpec *props[PROP_CLIENT + 1] = { NULL, };
164 : :
165 : 0 : object_class->dispose = mws_connection_monitor_nm_dispose;
166 : 0 : object_class->get_property = mws_connection_monitor_nm_get_property;
167 : 0 : object_class->set_property = mws_connection_monitor_nm_set_property;
168 : :
169 : : /**
170 : : * MwsConnectionMonitorNm:client:
171 : : *
172 : : * Proxy to the NetworkManager client interface on D-Bus. This must be
173 : : * provided at construction time, or a new proxy will be built and connected
174 : : * as part of the asynchronous initialization of this class (using
175 : : * #GAsyncInitable).
176 : : *
177 : : * Since: 0.1.0
178 : : */
179 : 0 : props[PROP_CLIENT] =
180 : 0 : g_param_spec_object ("client", "Client",
181 : : "Proxy to the NetworkManager client interface on D-Bus.",
182 : : NM_TYPE_CLIENT,
183 : : G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
184 : :
185 : 0 : g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
186 : 0 : }
187 : :
188 : : static void
189 : 0 : mws_connection_monitor_nm_connection_monitor_init (MwsConnectionMonitorInterface *iface)
190 : : {
191 : 0 : iface->get_connection_ids = mws_connection_monitor_nm_get_connection_ids;
192 : 0 : iface->get_connection_details = mws_connection_monitor_nm_get_connection_details;
193 : 0 : }
194 : :
195 : : static void
196 : 0 : mws_connection_monitor_nm_initable_init (GInitableIface *iface)
197 : : {
198 : 0 : iface->init = mws_connection_monitor_nm_init_failable;
199 : 0 : }
200 : :
201 : : static void
202 : 0 : mws_connection_monitor_nm_async_initable_init (GAsyncInitableIface *iface)
203 : : {
204 : 0 : iface->init_async = mws_connection_monitor_nm_init_async;
205 : 0 : iface->init_finish = mws_connection_monitor_nm_init_finish;
206 : 0 : }
207 : :
208 : : static void
209 : 0 : mws_connection_monitor_nm_init (MwsConnectionMonitorNm *self)
210 : : {
211 : 0 : self->cancellable = g_cancellable_new ();
212 : 0 : self->cached_connection_ids = NULL;
213 : 0 : }
214 : :
215 : : /* Utilities for connection and disconnecting signals on various objects. */
216 : : static void connection_connect (MwsConnectionMonitorNm *self,
217 : : NMConnection *connection,
218 : : NMActiveConnection *active_connection);
219 : : static void connection_disconnect (NMConnection *connection);
220 : :
221 : : static void
222 : 0 : active_connection_connect (MwsConnectionMonitorNm *self,
223 : : NMActiveConnection *active_connection)
224 : : {
225 : 0 : NMConnection *connection = NM_CONNECTION (nm_active_connection_get_connection (active_connection));
226 : :
227 : 0 : g_signal_connect (active_connection, "state-changed",
228 : : (GCallback) active_connection_state_changed_cb, self);
229 : 0 : g_signal_connect (active_connection, "notify", (GCallback) active_connection_notify_cb, self);
230 : :
231 : : /* @connection may be %NULL if the @active_connection is in state
232 : : * #NM_ACTIVE_CONNECTION_STATE_ACTIVATING */
233 [ # # ]: 0 : if (connection != NULL)
234 : 0 : connection_connect (self, connection, active_connection);
235 : 0 : }
236 : :
237 : : static void
238 : 0 : active_connection_disconnect (MwsConnectionMonitorNm *self,
239 : : NMActiveConnection *active_connection)
240 : : {
241 : 0 : NMConnection *connection = NM_CONNECTION (nm_active_connection_get_connection (active_connection));
242 : :
243 [ # # ]: 0 : if (connection != NULL)
244 : 0 : connection_disconnect (connection);
245 : :
246 : 0 : g_signal_handlers_disconnect_by_func (active_connection, active_connection_state_changed_cb, self);
247 : 0 : g_signal_handlers_disconnect_by_func (active_connection, active_connection_notify_cb, self);
248 : 0 : }
249 : :
250 : : static void
251 : 0 : device_connect (MwsConnectionMonitorNm *self,
252 : : NMDevice *device)
253 : : {
254 : 0 : g_signal_connect (device, "state-changed",
255 : : (GCallback) device_state_changed_cb, self);
256 : 0 : g_signal_connect (device, "notify", (GCallback) device_notify_cb, self);
257 : 0 : }
258 : :
259 : : static void
260 : 0 : device_disconnect (MwsConnectionMonitorNm *self,
261 : : NMDevice *device)
262 : : {
263 : 0 : g_signal_handlers_disconnect_by_func (device, device_state_changed_cb, self);
264 : 0 : g_signal_handlers_disconnect_by_func (device, device_notify_cb, self);
265 : 0 : }
266 : :
267 : : /* Closure for the #NMSettingConnection::notify signal callback. */
268 : : typedef struct
269 : : {
270 : : MwsConnectionMonitorNm *connection_monitor; /* (unowned) (not nullable) */
271 : : NMActiveConnection *active_connection; /* (owned) (not nullable) */
272 : : } ConnectionChangedData;
273 : :
274 : : static void connection_changed_data_free (ConnectionChangedData *data);
275 [ # # ]: 0 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (ConnectionChangedData, connection_changed_data_free)
276 : :
277 : : static ConnectionChangedData *
278 : 0 : setting_notify_data_new (MwsConnectionMonitorNm *connection_monitor,
279 : : NMActiveConnection *active_connection)
280 : : {
281 : 0 : g_autoptr(ConnectionChangedData) data = g_new0 (ConnectionChangedData, 1);
282 : 0 : data->connection_monitor = connection_monitor;
283 : 0 : data->active_connection = g_object_ref (active_connection);
284 : 0 : return g_steal_pointer (&data);
285 : : }
286 : :
287 : : static void
288 : 0 : connection_changed_data_free (ConnectionChangedData *data)
289 : : {
290 [ # # ]: 0 : g_clear_object (&data->active_connection);
291 : 0 : g_free (data);
292 : 0 : }
293 : :
294 : : static void
295 : 0 : connection_changed_data_closure_notify (gpointer data,
296 : : GClosure *closure)
297 : : {
298 : 0 : connection_changed_data_free (data);
299 : 0 : }
300 : :
301 : : static void
302 : 0 : connection_connect (MwsConnectionMonitorNm *self,
303 : : NMConnection *connection,
304 : : NMActiveConnection *active_connection)
305 : : {
306 : 0 : g_signal_connect_data (connection, "changed",
307 : : (GCallback) connection_changed_cb,
308 : 0 : setting_notify_data_new (self, active_connection),
309 : : connection_changed_data_closure_notify,
310 : : 0 /* flags */);
311 : 0 : }
312 : :
313 : : static void
314 : 0 : connection_disconnect (NMConnection *connection)
315 : : {
316 : 0 : g_signal_handlers_disconnect_matched (connection,
317 : : G_SIGNAL_MATCH_FUNC,
318 : : 0 /* signal ID */, 0 /* detail */,
319 : : NULL /* closure */,
320 : : connection_changed_cb,
321 : : NULL /* data */);
322 : 0 : }
323 : :
324 : : static void
325 : 0 : mws_connection_monitor_nm_dispose (GObject *object)
326 : : {
327 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (object);
328 : :
329 : 0 : g_cancellable_cancel (self->cancellable);
330 [ # # ]: 0 : g_clear_object (&self->cancellable);
331 : :
332 [ # # ]: 0 : if (self->client != NULL)
333 : : {
334 : 0 : g_signal_handlers_disconnect_by_func (self->client, active_connection_added_cb, self);
335 : 0 : g_signal_handlers_disconnect_by_func (self->client, active_connection_removed_cb, self);
336 : 0 : g_signal_handlers_disconnect_by_func (self->client, device_added_cb, self);
337 : 0 : g_signal_handlers_disconnect_by_func (self->client, device_removed_cb, self);
338 : : }
339 : :
340 : : /* Disconnect from all remaining devices. */
341 : 0 : const GPtrArray *devices = nm_client_get_devices (self->client);
342 [ # # ]: 0 : for (gsize i = 0; i < devices->len; i++)
343 : : {
344 : 0 : NMDevice *device = g_ptr_array_index (devices, i);
345 : 0 : device_removed_cb (self->client, device, self);
346 : : }
347 : :
348 : : /* Disconnect from all remaining active connections. */
349 : 0 : const GPtrArray *active_connections = nm_client_get_active_connections (self->client);
350 [ # # ]: 0 : for (gsize i = 0; i < active_connections->len; i++)
351 : : {
352 : 0 : NMActiveConnection *active_connection = g_ptr_array_index (active_connections, i);
353 : 0 : active_connection_removed_cb (self->client, active_connection, self);
354 : : }
355 : :
356 [ # # ]: 0 : g_clear_object (&self->client);
357 : 0 : g_clear_error (&self->init_error);
358 : :
359 [ # # ]: 0 : g_clear_pointer (&self->cached_connection_ids, g_strfreev);
360 : :
361 : : /* Chain up to the parent class */
362 : 0 : G_OBJECT_CLASS (mws_connection_monitor_nm_parent_class)->dispose (object);
363 : 0 : }
364 : :
365 : : static void
366 : 0 : mws_connection_monitor_nm_get_property (GObject *object,
367 : : guint property_id,
368 : : GValue *value,
369 : : GParamSpec *pspec)
370 : : {
371 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (object);
372 : :
373 [ # # ]: 0 : switch ((MwsConnectionMonitorNmProperty) property_id)
374 : : {
375 : 0 : case PROP_CLIENT:
376 : 0 : g_value_set_object (value, self->client);
377 : 0 : break;
378 : 0 : default:
379 : 0 : g_assert_not_reached ();
380 : : }
381 : 0 : }
382 : :
383 : : static void
384 : 0 : mws_connection_monitor_nm_set_property (GObject *object,
385 : : guint property_id,
386 : : const GValue *value,
387 : : GParamSpec *pspec)
388 : : {
389 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (object);
390 : :
391 [ # # ]: 0 : switch ((MwsConnectionMonitorNmProperty) property_id)
392 : : {
393 : 0 : case PROP_CLIENT:
394 : : /* Construct only. */
395 [ # # ]: 0 : g_assert (self->client == NULL);
396 : 0 : self->client = g_value_dup_object (value);
397 : 0 : break;
398 : 0 : default:
399 : 0 : g_assert_not_reached ();
400 : : }
401 : 0 : }
402 : :
403 : : static gboolean
404 : 0 : set_up_client (MwsConnectionMonitorNm *self,
405 : : GError **error)
406 : : {
407 [ # # ]: 0 : g_assert (self->client != NULL);
408 : :
409 : : /* Subscribe to signals. */
410 : 0 : g_signal_connect (self->client, "active-connection-added",
411 : : (GCallback) active_connection_added_cb, self);
412 : 0 : g_signal_connect (self->client, "active-connection-removed",
413 : : (GCallback) active_connection_removed_cb, self);
414 : 0 : g_signal_connect (self->client, "device-added",
415 : : (GCallback) device_added_cb, self);
416 : 0 : g_signal_connect (self->client, "device-removed",
417 : : (GCallback) device_removed_cb, self);
418 : :
419 : : /* Query for initial connections. */
420 : 0 : const GPtrArray *active_connections = nm_client_get_active_connections (self->client);
421 [ # # ]: 0 : for (gsize i = 0; i < active_connections->len; i++)
422 : : {
423 : 0 : NMActiveConnection *connection = g_ptr_array_index (active_connections, i);
424 : 0 : active_connection_added_cb (self->client, connection, self);
425 : : }
426 : :
427 : : /* …and devices. */
428 : 0 : const GPtrArray *devices = nm_client_get_devices (self->client);
429 [ # # ]: 0 : for (gsize i = 0; i < devices->len; i++)
430 : : {
431 : 0 : NMDevice *device = g_ptr_array_index (devices, i);
432 : 0 : device_added_cb (self->client, device, self);
433 : : }
434 : :
435 : 0 : self->init_success = TRUE;
436 : 0 : return self->init_success;
437 : : }
438 : :
439 : : static gboolean
440 : 0 : mws_connection_monitor_nm_init_failable (GInitable *initable,
441 : : GCancellable *cancellable,
442 : : GError **error)
443 : : {
444 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (initable);
445 : :
446 : : /* For the moment, this only supports the case where we’ve been constructed
447 : : * with a suitable client already. */
448 [ # # ]: 0 : if (self->init_error != NULL)
449 : : {
450 : 0 : g_propagate_error (error, g_error_copy (self->init_error));
451 : 0 : return FALSE;
452 : : }
453 [ # # ]: 0 : else if (self->init_success)
454 : : {
455 : 0 : return TRUE;
456 : : }
457 : : else
458 : : {
459 : 0 : return set_up_client (self, error);
460 : : }
461 : : }
462 : :
463 : : static void client_new_cb (GObject *obj,
464 : : GAsyncResult *result,
465 : : gpointer user_data);
466 : :
467 : : static void
468 : 0 : mws_connection_monitor_nm_init_async (GAsyncInitable *initable,
469 : : int io_priority,
470 : : GCancellable *cancellable,
471 : : GAsyncReadyCallback callback,
472 : : gpointer user_data)
473 : : {
474 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (initable);
475 : :
476 : : /* We don’t support parallel initialisation. */
477 [ # # ]: 0 : g_assert (!self->initialising);
478 : :
479 : 0 : g_autoptr(GTask) task = g_task_new (initable, cancellable, callback, user_data);
480 [ # # ]: 0 : g_task_set_source_tag (task, mws_connection_monitor_nm_init_async);
481 : :
482 [ # # ]: 0 : if (self->init_error != NULL)
483 : 0 : g_task_return_error (task, g_error_copy (self->init_error));
484 [ # # ]: 0 : else if (self->init_success)
485 : 0 : g_task_return_boolean (task, TRUE);
486 : : else
487 : : {
488 : 0 : self->initialising = TRUE;
489 : 0 : nm_client_new_async (cancellable, client_new_cb, g_steal_pointer (&task));
490 : : }
491 : 0 : }
492 : :
493 : : static void
494 : 0 : client_new_cb (GObject *obj,
495 : : GAsyncResult *result,
496 : : gpointer user_data)
497 : : {
498 [ # # ]: 0 : g_autoptr(GTask) task = G_TASK (user_data);
499 : 0 : MwsConnectionMonitorNm *self = g_task_get_source_object (task);
500 [ # # ]: 0 : g_autoptr(GError) error = NULL;
501 : :
502 : : /* Get the client. */
503 [ # # ]: 0 : g_assert (self->client == NULL);
504 : 0 : self->client = nm_client_new_finish (result, &error);
505 : :
506 [ # # ]: 0 : g_assert (self->initialising);
507 : 0 : self->initialising = FALSE;
508 : :
509 [ # # ]: 0 : if (error != NULL)
510 : : {
511 : 0 : self->init_success = FALSE;
512 : 0 : g_task_return_error (task, g_steal_pointer (&error));
513 : 0 : return;
514 : : }
515 : :
516 [ # # ]: 0 : if (set_up_client (self, &error))
517 : 0 : g_task_return_boolean (task, TRUE);
518 : : else
519 : 0 : g_task_return_error (task, g_steal_pointer (&error));
520 : : }
521 : :
522 : : static gboolean
523 : 0 : mws_connection_monitor_nm_init_finish (GAsyncInitable *initable,
524 : : GAsyncResult *result,
525 : : GError **error)
526 : : {
527 : 0 : return g_task_propagate_boolean (G_TASK (result), error);
528 : : }
529 : :
530 : : static const gchar * const *
531 : 0 : mws_connection_monitor_nm_get_connection_ids (MwsConnectionMonitor *monitor)
532 : : {
533 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (monitor);
534 : :
535 : 0 : const GPtrArray *active_connections = nm_client_get_active_connections (self->client);
536 : :
537 [ # # ]: 0 : if (self->cached_connection_ids == NULL)
538 : : {
539 : 0 : g_autoptr(GPtrArray) connection_ids = g_ptr_array_new_full (active_connections->len, g_free);
540 [ # # ]: 0 : for (gsize i = 0; i < active_connections->len; i++)
541 : : {
542 : 0 : NMActiveConnection *active_connection = NM_ACTIVE_CONNECTION (g_ptr_array_index (active_connections, i));
543 : 0 : g_ptr_array_add (connection_ids, g_strdup (nm_active_connection_get_id (active_connection)));
544 : : }
545 : 0 : g_ptr_array_add (connection_ids, NULL); /* NULL terminator */
546 : 0 : self->cached_connection_ids = (gchar **) g_ptr_array_free (g_steal_pointer (&connection_ids), FALSE);
547 : : }
548 : :
549 : : /* Sanity check. */
550 [ # # ]: 0 : g_warn_if_fail (g_strv_length ((gchar **) self->cached_connection_ids) == active_connections->len);
551 : :
552 : 0 : return (const gchar * const *) self->cached_connection_ids;
553 : : }
554 : :
555 : : /* Convert a #NMMetered to a #MwsMetered. */
556 : : static MwsMetered
557 : 0 : mws_metered_from_nm_metered (NMMetered m)
558 : : {
559 [ # # # # : 0 : switch (m)
# # ]
560 : : {
561 : 0 : case NM_METERED_UNKNOWN:
562 : 0 : return MWS_METERED_UNKNOWN;
563 : 0 : case NM_METERED_YES:
564 : 0 : return MWS_METERED_YES;
565 : 0 : case NM_METERED_NO:
566 : 0 : return MWS_METERED_NO;
567 : 0 : case NM_METERED_GUESS_YES:
568 : 0 : return MWS_METERED_GUESS_YES;
569 : 0 : case NM_METERED_GUESS_NO:
570 : 0 : return MWS_METERED_GUESS_NO;
571 : 0 : default:
572 : 0 : g_assert_not_reached ();
573 : : }
574 : : }
575 : :
576 : : /* Get a boolean configuration value from an #NMSettingUser. Valid values are
577 : : * `0` or `1`. Returns the value; if it wasn’t set in the @setting_user, the
578 : : * @default_value is returned. There is no way to distinguish an unset value
579 : : * from a set value equal to the @default_value. */
580 : : static gboolean
581 : 0 : setting_user_get_boolean (NMSettingUser *setting_user,
582 : : const gchar *key,
583 : : gboolean default_value)
584 : : {
585 : 0 : const gchar *str = nm_setting_user_get_data (setting_user, key);
586 [ # # ]: 0 : if (str == NULL)
587 : 0 : return default_value;
588 [ # # ]: 0 : else if (g_str_equal (str, "0"))
589 : 0 : return FALSE;
590 [ # # ]: 0 : else if (g_str_equal (str, "1"))
591 : 0 : return TRUE;
592 : : else
593 : 0 : g_warning ("Invalid value ‘%s’ for user setting ‘%s’; expecting ‘0’ or ‘1’",
594 : : str, key);
595 : :
596 : 0 : return default_value;
597 : : }
598 : :
599 : : static gboolean
600 : 0 : mws_connection_monitor_nm_get_connection_details (MwsConnectionMonitor *monitor,
601 : : const gchar *id,
602 : : MwsConnectionDetails *out_details)
603 : : {
604 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (monitor);
605 : :
606 : : /* Does the connection with @id exist? */
607 : 0 : const GPtrArray *active_connections = nm_client_get_active_connections (self->client);
608 : 0 : NMActiveConnection *active_connection = NULL;
609 [ # # ]: 0 : for (gsize i = 0; i < active_connections->len; i++)
610 : : {
611 : 0 : NMActiveConnection *c = NM_ACTIVE_CONNECTION (g_ptr_array_index (active_connections, i));
612 [ # # ]: 0 : if (g_str_equal (nm_active_connection_get_id (c), id))
613 : : {
614 : 0 : active_connection = c;
615 : 0 : break;
616 : : }
617 : : }
618 : :
619 [ # # ]: 0 : if (active_connection == NULL)
620 : 0 : return FALSE;
621 : :
622 : : /* Early return if the connection was found, but the caller doesn’t want the details. */
623 [ # # ]: 0 : if (out_details == NULL)
624 : 0 : return TRUE;
625 : :
626 : : /* To work out whether the active connection is metered, combine the metered
627 : : * status of all the #NMDevices which form it, plus the metered settings of
628 : : * the active connection’s #NMSettingConnection (if it exists).
629 : : * We are being conservative here, on the assumption
630 : : * that a client could download using any active network connection, not just
631 : : * the primary one. In most cases, the distinction is immaterial as we’d
632 : : * expect only a single active network connection on most machines. */
633 : 0 : MwsMetered connection_metered = MWS_METERED_GUESS_NO;
634 : 0 : MwsMetered devices_metered = MWS_METERED_UNKNOWN;
635 : :
636 : 0 : NMConnection *connection = NM_CONNECTION (nm_active_connection_get_connection (active_connection));
637 [ # # ]: 0 : NMSettingConnection *setting = (connection != NULL) ? nm_connection_get_setting_connection (connection) : NULL;
638 : :
639 [ # # ]: 0 : if (setting != NULL)
640 : : connection_metered =
641 : 0 : mws_metered_from_nm_metered (nm_setting_connection_get_metered (setting));
642 : :
643 : 0 : const GPtrArray *devices = nm_active_connection_get_devices (active_connection);
644 : :
645 [ # # ]: 0 : for (gsize i = 0; i < devices->len; i++)
646 : : {
647 : 0 : NMDevice *device = g_ptr_array_index (devices, i);
648 : :
649 : : /* Sort out metered status. */
650 : 0 : devices_metered = mws_metered_combine_pessimistic (mws_metered_from_nm_metered (nm_device_get_metered (device)),
651 : : devices_metered);
652 : : }
653 : :
654 : : /* Get the connection’s tariff information (if set). These keys/values are
655 : : * set on the #NMSettingUser for the primary connection by
656 : : * gnome-control-center, and may be absent. */
657 : 0 : NMSettingUser *setting_user =
658 [ # # ]: 0 : (connection != NULL) ? NM_SETTING_USER (nm_connection_get_setting (connection, NM_TYPE_SETTING_USER)) : NULL;
659 : :
660 : : /* TODO: If we want to load a default value from eos-autoupdater.conf (see
661 : : * https://phabricator.endlessm.com/T20818#542708), we should plumb it in here
662 : : * (but do the async loading of the file somewhere else, earlier). */
663 : 0 : const gboolean allow_downloads_when_metered_default = FALSE;
664 : 0 : const gboolean allow_downloads_default = TRUE;
665 : :
666 : 0 : gboolean allow_downloads_when_metered = allow_downloads_when_metered_default;
667 : 0 : gboolean allow_downloads = allow_downloads_default;
668 : 0 : g_autoptr(MwtTariff) tariff = NULL;
669 : :
670 [ # # ]: 0 : if (setting_user != NULL)
671 : : {
672 : 0 : allow_downloads_when_metered = setting_user_get_boolean (setting_user,
673 : : "connection.allow-downloads-when-metered",
674 : : allow_downloads_when_metered_default);
675 : 0 : allow_downloads = setting_user_get_boolean (setting_user,
676 : : "connection.allow-downloads",
677 : : allow_downloads_default);
678 : :
679 : 0 : gboolean tariff_enabled = setting_user_get_boolean (setting_user,
680 : : "connection.tariff-enabled",
681 : : FALSE);
682 : : const gchar *tariff_variant_str;
683 : 0 : tariff_variant_str = nm_setting_user_get_data (setting_user,
684 : : "connection.tariff");
685 : :
686 [ # # # # : 0 : g_debug ("%s: Connection ‘%s’ has:\n"
# # ]
687 : : " • connection.allow-downloads-when-metered: %s\n"
688 : : " • connection.allow-downloads: %s\n"
689 : : " • connection.tariff-enabled: %s\n"
690 : : " • connection.tariff: %s",
691 : : G_STRFUNC, id, allow_downloads_when_metered ? "yes" : "no",
692 : : allow_downloads ? "yes" : "no", tariff_enabled ? "yes" : "no",
693 : : tariff_variant_str);
694 : :
695 [ # # # # ]: 0 : if (tariff_enabled && tariff_variant_str != NULL)
696 : 0 : {
697 : 0 : g_autoptr(GError) local_error = NULL;
698 : 0 : g_autoptr(GVariant) tariff_variant = NULL;
699 : 0 : tariff_variant = g_variant_parse (NULL, tariff_variant_str,
700 : : NULL, NULL, &local_error);
701 : 0 : g_autoptr(MwtTariffLoader) loader = mwt_tariff_loader_new ();
702 : :
703 [ # # # # ]: 0 : if (tariff_variant != NULL &&
704 : 0 : mwt_tariff_loader_load_from_variant (loader, tariff_variant,
705 : : &local_error))
706 : 0 : tariff = g_object_ref (mwt_tariff_loader_get_tariff (loader));
707 : :
708 [ # # ]: 0 : if (local_error != NULL)
709 : : {
710 [ # # ]: 0 : g_assert (tariff == NULL);
711 : 0 : g_warning ("connection.tariff contained an invalid tariff ‘%s’: %s",
712 : : tariff_variant_str, local_error->message);
713 : 0 : g_clear_error (&local_error);
714 : : }
715 : : }
716 [ # # # # ]: 0 : else if (tariff_enabled && tariff_variant_str == NULL)
717 : : {
718 : 0 : g_warning ("connection.tariff is not set even though "
719 : : "connection.tariff-enabled is 1");
720 : : }
721 : : }
722 : :
723 : 0 : out_details->metered = mws_metered_combine_pessimistic (devices_metered,
724 : : connection_metered);
725 : 0 : out_details->allow_downloads_when_metered = allow_downloads_when_metered;
726 : 0 : out_details->allow_downloads = allow_downloads;
727 : 0 : out_details->tariff = g_steal_pointer (&tariff);
728 : :
729 : 0 : return TRUE;
730 : : }
731 : :
732 : : static void
733 : 0 : active_connection_added_cb (NMClient *client,
734 : : NMActiveConnection *active_connection,
735 : : gpointer user_data)
736 : : {
737 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (user_data);
738 : :
739 : : /* Add to the connection IDs list and emit a signal. */
740 : 0 : const gchar *id = nm_active_connection_get_id (active_connection);
741 : :
742 : 0 : g_debug ("%s: Adding active connection ‘%s’.", G_STRFUNC, id);
743 : :
744 [ # # ]: 0 : g_clear_pointer (&self->cached_connection_ids, g_strfreev);
745 : 0 : active_connection_connect (self, active_connection);
746 : :
747 : : /* FIXME: In the future, we may want to handle the primary connection
748 : : * (nm_client_get_primary_connection()) differently from other
749 : : * connections. Probably best to tie that in with the rest of the
750 : : * multi-path stuff. */
751 : 0 : g_autoptr(GPtrArray) added = g_ptr_array_new_with_free_func (NULL);
752 : 0 : g_ptr_array_add (added, (gpointer) id);
753 : 0 : g_signal_emit_by_name (self, "connections-changed", added, NULL);
754 : 0 : }
755 : :
756 : : static void
757 : 0 : active_connection_removed_cb (NMClient *client,
758 : : NMActiveConnection *active_connection,
759 : : gpointer user_data)
760 : : {
761 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (user_data);
762 : :
763 : : /* Remove from the connection IDs list and emit a signal. */
764 : 0 : const gchar *id = nm_active_connection_get_id (active_connection);
765 : :
766 : 0 : g_debug ("%s: Removing active connection ‘%s’.", G_STRFUNC, id);
767 : :
768 [ # # ]: 0 : g_clear_pointer (&self->cached_connection_ids, g_strfreev);
769 : 0 : active_connection_disconnect (self, active_connection);
770 : :
771 : 0 : g_autoptr(GPtrArray) removed = g_ptr_array_new_with_free_func (NULL);
772 : 0 : g_ptr_array_add (removed, (gpointer) id);
773 : 0 : g_signal_emit_by_name (self, "connections-changed", NULL, removed);
774 : 0 : }
775 : :
776 : : static void
777 : 0 : active_connection_notify_cb (GObject *obj,
778 : : GParamSpec *pspec,
779 : : gpointer user_data)
780 : : {
781 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (user_data);
782 : 0 : NMActiveConnection *active_connection = NM_ACTIVE_CONNECTION (obj);
783 : 0 : NMConnection *connection = NM_CONNECTION (nm_active_connection_get_connection (active_connection));
784 : :
785 : 0 : const gchar *active_connection_id = NULL;
786 : 0 : active_connection_id = nm_active_connection_get_id (active_connection);
787 : :
788 [ # # ]: 0 : g_debug ("%s: Active connection ‘%s’ notifying property ‘%s’.",
789 : : G_STRFUNC, active_connection_id,
790 : : (pspec != NULL) ? g_param_spec_get_name (pspec) : "(all)");
791 : :
792 [ # # ]: 0 : if (connection != NULL)
793 : 0 : connection_connect (self, connection, active_connection);
794 : :
795 [ # # ]: 0 : if (g_cancellable_is_cancelled (self->cancellable))
796 : 0 : return;
797 : :
798 : : /* Don’t bother working out what changed; just assume that it will probably
799 : : * change our scheduling. */
800 : 0 : g_signal_emit_by_name (self, "connection-details-changed", active_connection_id);
801 : : }
802 : :
803 : : static void
804 : 0 : active_connection_state_changed_cb (NMActiveConnection *active_connection,
805 : : guint new_state,
806 : : guint reason,
807 : : gpointer user_data)
808 : : {
809 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (user_data);
810 : 0 : NMConnection *connection = NM_CONNECTION (nm_active_connection_get_connection (active_connection));
811 : :
812 : 0 : const gchar *active_connection_id = NULL;
813 : 0 : active_connection_id = nm_active_connection_get_id (active_connection);
814 : :
815 : 0 : g_debug ("%s: Active connection ‘%s’ state changed to %u (reason: %u).",
816 : : G_STRFUNC, active_connection_id, new_state, reason);
817 : :
818 [ # # ]: 0 : if (connection != NULL)
819 : 0 : connection_connect (self, connection, active_connection);
820 : :
821 [ # # ]: 0 : if (g_cancellable_is_cancelled (self->cancellable))
822 : 0 : return;
823 : :
824 : 0 : g_signal_emit_by_name (self, "connection-details-changed", active_connection_id);
825 : : }
826 : :
827 : : static void
828 : 0 : device_added_cb (NMClient *client,
829 : : NMDevice *device,
830 : : gpointer user_data)
831 : : {
832 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (user_data);
833 : :
834 : 0 : g_debug ("%s: Adding device ‘%s’.", G_STRFUNC, nm_device_get_iface (device));
835 : 0 : device_connect (self, device);
836 : 0 : }
837 : :
838 : : static void
839 : 0 : device_removed_cb (NMClient *client,
840 : : NMDevice *device,
841 : : gpointer user_data)
842 : : {
843 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (user_data);
844 : :
845 : 0 : g_debug ("%s: Removing device ‘%s’.", G_STRFUNC, nm_device_get_iface (device));
846 : 0 : device_disconnect (self, device);
847 : 0 : }
848 : :
849 : : static void
850 : 0 : device_notify_cb (GObject *obj,
851 : : GParamSpec *pspec,
852 : : gpointer user_data)
853 : : {
854 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (user_data);
855 : 0 : NMDevice *device = NM_DEVICE (obj);
856 : :
857 : 0 : NMActiveConnection *active_connection = nm_device_get_active_connection (device);
858 : 0 : const gchar *active_connection_id = NULL;
859 [ # # ]: 0 : active_connection_id = (active_connection != NULL) ? nm_active_connection_get_id (active_connection) : "(none)";
860 : :
861 [ # # ]: 0 : g_debug ("%s: Device ‘%s’ (active connection ‘%s’) notifying property ‘%s’.",
862 : : G_STRFUNC, nm_device_get_iface (device), active_connection_id,
863 : : (pspec != NULL) ? g_param_spec_get_name (pspec) : "(all)");
864 : :
865 [ # # ]: 0 : if (g_cancellable_is_cancelled (self->cancellable))
866 : 0 : return;
867 : :
868 : : /* Don’t bother working out what changed; just assume that it will probably
869 : : * change our scheduling. */
870 [ # # ]: 0 : if (active_connection != NULL)
871 : 0 : g_signal_emit_by_name (self, "connection-details-changed", active_connection_id);
872 : : }
873 : :
874 : : static void
875 : 0 : device_state_changed_cb (NMDevice *device,
876 : : guint new_state,
877 : : guint old_state,
878 : : guint reason,
879 : : gpointer user_data)
880 : : {
881 : 0 : MwsConnectionMonitorNm *self = MWS_CONNECTION_MONITOR_NM (user_data);
882 : :
883 : 0 : NMActiveConnection *active_connection = nm_device_get_active_connection (device);
884 : 0 : const gchar *active_connection_id = NULL;
885 [ # # ]: 0 : active_connection_id = (active_connection != NULL) ? nm_active_connection_get_id (active_connection) : "(none)";
886 : :
887 : 0 : g_debug ("%s: Device ‘%s’ (active connection ‘%s’) state changed from %u to "
888 : : "%u (reason: %u).",
889 : : G_STRFUNC, nm_device_get_iface (device), active_connection_id,
890 : : old_state, new_state, reason);
891 : :
892 [ # # ]: 0 : if (g_cancellable_is_cancelled (self->cancellable))
893 : 0 : return;
894 : :
895 [ # # ]: 0 : if (active_connection != NULL)
896 : 0 : g_signal_emit_by_name (self, "connection-details-changed", active_connection_id);
897 : : }
898 : :
899 : : static void
900 : 0 : connection_changed_cb (NMConnection *connection,
901 : : gpointer user_data)
902 : : {
903 : 0 : ConnectionChangedData *data = user_data;
904 : :
905 : : /* Don’t bother working out what changed; just assume that it will probably
906 : : * change our scheduling. */
907 : 0 : g_signal_emit_by_name (data->connection_monitor, "connection-details-changed",
908 : : nm_active_connection_get_id (data->active_connection));
909 : 0 : }
910 : :
911 : : /**
912 : : * mws_connection_monitor_nm_new_from_client:
913 : : * @client: an #NMClient
914 : : * @error: return location for a #GError, or %NULL
915 : : *
916 : : * Create a #MwsConnectionMonitorNm object to wrap the given existing @client.
917 : : *
918 : : * Returns: (transfer full): a new #MwsConnectionMonitorNm wrapping @client
919 : : * Since: 0.1.0
920 : : */
921 : : MwsConnectionMonitorNm *
922 : 0 : mws_connection_monitor_nm_new_from_client (NMClient *client,
923 : : GError **error)
924 : : {
925 [ # # # # : 0 : g_return_val_if_fail (NM_IS_CLIENT (client), NULL);
# # # # ]
926 [ # # # # ]: 0 : g_return_val_if_fail (error == NULL || *error == NULL, NULL);
927 : :
928 : 0 : return g_initable_new (MWS_TYPE_CONNECTION_MONITOR_NM, NULL, error,
929 : : "client", client,
930 : : NULL);
931 : : }
932 : :
933 : : /**
934 : : * mws_connection_monitor_nm_new_async:
935 : : * @cancellable: (nullable): a #GCancellable, or %NULL
936 : : * @callback: callback to invoke on completion
937 : : * @user_data: user data to pass to @callback
938 : : *
939 : : * Create a new #MwsConnectionMonitorNm and set it up. This is an asynchronous
940 : : * process which might fail; object instantiation must be finished (or the error
941 : : * returned) by calling mws_connection_monitor_nm_new_finish().
942 : : *
943 : : * Since: 0.1.0
944 : : */
945 : : void
946 : 0 : mws_connection_monitor_nm_new_async (GCancellable *cancellable,
947 : : GAsyncReadyCallback callback,
948 : : gpointer user_data)
949 : : {
950 [ # # # # : 0 : g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
# # # # #
# ]
951 : :
952 : 0 : g_async_initable_new_async (MWS_TYPE_CONNECTION_MONITOR_NM, G_PRIORITY_DEFAULT,
953 : : cancellable, callback, user_data,
954 : : "client", NULL,
955 : : NULL);
956 : : }
957 : :
958 : : /**
959 : : * mws_connection_monitor_nm_new_finish:
960 : : * @result: asynchronous operation result
961 : : * @error: return location for a #GError
962 : : *
963 : : * Finish initialising a #MwsConnectionMonitorNm. See
964 : : * mws_connection_monitor_nm_new_async().
965 : : *
966 : : * Returns: (transfer full): initialised #MwsConnectionMonitorNm, or %NULL on error
967 : : * Since: 0.1.0
968 : : */
969 : : MwsConnectionMonitorNm *
970 : 0 : mws_connection_monitor_nm_new_finish (GAsyncResult *result,
971 : : GError **error)
972 : : {
973 [ # # # # : 0 : g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
# # # # ]
974 [ # # # # ]: 0 : g_return_val_if_fail (error == NULL || *error == NULL, NULL);
975 : :
976 : 0 : g_autoptr(GObject) source_object = g_async_result_get_source_object (result);
977 : 0 : return MWS_CONNECTION_MONITOR_NM (g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
978 : : result, error));
979 : : }
|